feat: admin cleanup tab && admin sendbox tab (#126)

This commit is contained in:
Dream Hunter
2024-04-14 22:41:16 +08:00
committed by GitHub
parent 63cf97f5e2
commit aea8b964bb
16 changed files with 1042 additions and 359 deletions

View File

@@ -1,5 +1,6 @@
import { Hono } from 'hono'
import { Jwt } from 'hono/utils/jwt'
import { getSendbox } from './send_mail_api'
const api = new Hono()
@@ -179,6 +180,11 @@ api.post('/admin/address_sender', async (c) => {
})
})
api.get('/admin/sendbox', async (c) => {
const { address, limit, offset } = c.req.query();
return getSendbox(c, address, limit, offset);
})
api.get('/admin/statistics', async (c) => {
const { count: mailCountV1 } = await c.env.DB.prepare(`
SELECT count(*) as count FROM mails`
@@ -192,11 +198,51 @@ api.get('/admin/statistics', async (c) => {
const { count: activeUserCount7days } = await c.env.DB.prepare(`
SELECT count(*) as count FROM address where updated_at > datetime('now', '-7 day')`
).first();
const { count: sendMailCount } = await c.env.DB.prepare(`
SELECT count(*) as count FROM sendbox`
).first();
return c.json({
mailCount: (mailCountV1 || 0) + (mailCount || 0),
userCount: addressCount,
activeUserCount7days: activeUserCount7days
activeUserCount7days: activeUserCount7days,
sendMailCount: sendMailCount
})
});
api.post('/admin/cleanup', async (c) => {
const { cleanType, cleanDays } = await c.req.json();
if (!cleanType || !cleanDays || cleanDays < 0 || cleanDays > 30) {
return c.text("Invalid cleanType or cleanDays", 400)
}
console.log(`Cleanup ${cleanType} before ${cleanDays} days`);
switch (cleanType) {
case "mails":
await c.env.DB.prepare(`
DELETE FROM raw_mails WHERE created_at < datetime('now', '-${cleanDays} day')`
).run();
break;
case "mails_unknow":
await c.env.DB.prepare(`
DELETE FROM raw_mails WHERE address NOT IN
(select concat('${c.env.PREFIX}', name) from address) AND created_at < datetime('now', '-${cleanDays} day')`
).run();
break;
case "address":
await c.env.DB.prepare(`
DELETE FROM address WHERE updated_at < datetime('now', '-${cleanDays} day')`
).run();
break;
case "sendbox":
await c.env.DB.prepare(`
DELETE FROM sendbox WHERE created_at < datetime('now', '-${cleanDays} day')`
).run();
break;
default:
return c.text("Invalid cleanType", 400)
}
return c.json({
success: true
})
})
export { api }

View File

@@ -1,6 +1,8 @@
import { Hono } from 'hono'
import { Jwt } from 'hono/utils/jwt'
import { getDomains, getPasswords } from './utils';
const api = new Hono()
api.get('/api/mails', async (c) => {
@@ -124,13 +126,14 @@ api.post('/api/settings', async (c) => {
api.get('/open_api/settings', async (c) => {
// check header x-custom-auth
let needAuth = false;
if (c.env.PASSWORDS && c.env.PASSWORDS.length > 0) {
const passwords = getPasswords(c);
if (passwords && passwords.length > 0) {
const auth = c.req.raw.headers.get("x-custom-auth");
needAuth = !c.env.PASSWORDS.includes(auth);
needAuth = !passwords.includes(auth);
}
return c.json({
"prefix": c.env.PREFIX,
"domains": c.env.DOMAINS,
"domains": getDomains(c),
"needAuth": needAuth,
});
})
@@ -151,8 +154,9 @@ api.get('/api/new_address', async (c) => {
return c.text("Name too long (max 100)", 400)
}
// check domain, generate random domain
if (!domain || !c.env.DOMAINS.includes(domain)) {
domain = c.env.DOMAINS[Math.floor(Math.random() * c.env.DOMAINS.length)];
const domains = getDomains(c);
if (!domain || !domains.includes(domain)) {
domain = domains[Math.floor(Math.random() * domains.length)];
}
// create address
const emailAddress = c.env.PREFIX + name + "@" + domain

View File

@@ -110,12 +110,10 @@ api.post('/api/send_mail', async (c) => {
return c.json({ status: "ok" });
})
api.get('/api/sendbox', async (c) => {
const { address } = c.get("jwtPayload")
const getSendbox = async (c, address, limit, offset) => {
if (!address) {
return c.json({ "error": "No address" }, 400)
}
const { limit, offset } = c.req.query();
if (!limit || limit < 0 || limit > 100) {
return c.text("Invalid limit", 400)
}
@@ -137,6 +135,12 @@ api.get('/api/sendbox', async (c) => {
results: results,
count: count
})
}
api.get('/api/sendbox', async (c) => {
const { address } = c.get("jwtPayload")
const { limit, offset } = c.req.query();
return getSendbox(c, address, limit, offset);
})
export { api }
export { api, getSendbox }

47
worker/src/utils.js Normal file
View File

@@ -0,0 +1,47 @@
export const getDomains = (c) => {
if (!c.env.DOMAINS) {
return [];
}
// check if DOMAINS is an array, if not use json.parse
if (!Array.isArray(c.env.DOMAINS)) {
try {
return JSON.parse(c.env.DOMAINS);
} catch (e) {
console.error("Failed to parse DOMAINS", e);
return [];
}
}
return c.env.DOMAINS;
}
export const getPasswords = (c) => {
if (!c.env.PASSWORDS) {
return [];
}
// check if PASSWORDS is an array, if not use json.parse
if (!Array.isArray(c.env.PASSWORDS)) {
try {
return JSON.parse(c.env.PASSWORDS);
} catch (e) {
console.error("Failed to parse PASSWORDS", e);
return [];
}
}
return c.env.PASSWORDS;
}
export const getAdminPasswords = (c) => {
if (!c.env.ADMIN_PASSWORDS) {
return [];
}
// check if ADMIN_PASSWORDS is an array, if not use json.parse
if (!Array.isArray(c.env.ADMIN_PASSWORDS)) {
try {
return JSON.parse(c.env.ADMIN_PASSWORDS);
} catch (e) {
console.error("Failed to parse ADMIN_PASSWORDS", e);
return [];
}
}
return c.env.ADMIN_PASSWORDS;
}

View File

@@ -7,14 +7,16 @@ import { api as adminApi } from './admin_api';
import { api as apiV1 } from './api_v1';
import { api as apiSendMail } from './send_mail_api'
import { email } from './email';
import { getAdminPasswords, getPasswords } from './utils';
const app = new Hono()
app.use('/*', cors());
app.use('/api/*', async (c, next) => {
// check header x-custom-auth
if (c.env.PASSWORDS && c.env.PASSWORDS.length > 0) {
const passwords = getPasswords(c);
if (passwords && passwords.length > 0) {
const auth = c.req.raw.headers.get("x-custom-auth");
if (!auth || !c.env.PASSWORDS.includes(auth)) {
if (!auth || !passwords.includes(auth)) {
return c.text("Need Password", 401)
}
}
@@ -27,9 +29,10 @@ app.use('/api/*', async (c, next) => {
app.use('/admin/*', async (c, next) => {
// check header x-admin-auth
if (c.env.ADMIN_PASSWORDS && c.env.ADMIN_PASSWORDS.length > 0) {
const adminPasswords = getAdminPasswords(c);
if (adminPasswords && adminPasswords.length > 0) {
const adminAuth = c.req.raw.headers.get("x-admin-auth");
if (adminAuth && c.env.ADMIN_PASSWORDS.includes(adminAuth)) {
if (adminAuth && adminPasswords.includes(adminAuth)) {
await next();
return;
}