mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-09-05 07:27:27 +08:00
feat: add cron auto clean up (#189)
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { cleanup } from '../common';
|
||||
import { CONSTANTS } from '../constants';
|
||||
import { getJsonSetting, saveSetting } from '../utils';
|
||||
|
||||
export default {
|
||||
cleanup: async (c) => {
|
||||
const { cleanType, cleanDays } = await c.req.json();
|
||||
try {
|
||||
await cleanup(c, cleanType, cleanDays);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return c.text(`Failed to cleanup ${error.message}`, 500)
|
||||
}
|
||||
return c.json({ success: true })
|
||||
},
|
||||
getCleanup: async (c) => {
|
||||
const value = await getJsonSetting(c, CONSTANTS.AUTO_CLEANUP_KEY);
|
||||
return c.json(value || {})
|
||||
},
|
||||
saveCleanup: async (c) => {
|
||||
const value = await c.req.json();
|
||||
await saveSetting(c, CONSTANTS.AUTO_CLEANUP_KEY, JSON.stringify(value));
|
||||
return c.json({ success: true })
|
||||
}
|
||||
}
|
||||
+11
-47
@@ -1,8 +1,9 @@
|
||||
import { Hono } from 'hono'
|
||||
import { Jwt } from 'hono/utils/jwt'
|
||||
import { sendAdminInternalMail } from './utils'
|
||||
import { sendAdminInternalMail, getJsonSetting, saveSetting } from './utils'
|
||||
import { newAddress } from './common'
|
||||
import { CONSTANTS } from './constants'
|
||||
import cleanup_api from './admin/cleanup_api'
|
||||
|
||||
const api = new Hono()
|
||||
|
||||
@@ -294,49 +295,16 @@ api.get('/admin/statistics', async (c) => {
|
||||
})
|
||||
});
|
||||
|
||||
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 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
|
||||
})
|
||||
})
|
||||
api.post('/admin/cleanup', cleanup_api.cleanup)
|
||||
api.get('/admin/auto_cleanup', cleanup_api.getCleanup)
|
||||
api.post('/admin/auto_cleanup', cleanup_api.saveCleanup)
|
||||
|
||||
|
||||
api.get('/admin/account_settings', async (c) => {
|
||||
try {
|
||||
const value = await c.env.DB.prepare(
|
||||
`SELECT value FROM settings where key = ?`
|
||||
).bind(CONSTANTS.ADDRESS_BLOCK_LIST_KEY).first("value");
|
||||
const value = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY);
|
||||
return c.json({
|
||||
blockList: value ? JSON.parse(value) : []
|
||||
blockList: value || []
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -349,14 +317,10 @@ api.post('/admin/account_settings', async (c) => {
|
||||
if (!blockList) {
|
||||
return c.text("Invalid blockList", 400)
|
||||
}
|
||||
await c.env.DB.prepare(
|
||||
`INSERT or REPLACE INTO settings (key, value) VALUES (?, ?)`
|
||||
+ ` ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')`
|
||||
).bind(
|
||||
CONSTANTS.ADDRESS_BLOCK_LIST_KEY,
|
||||
JSON.stringify(blockList),
|
||||
await saveSetting(
|
||||
c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY,
|
||||
JSON.stringify(blockList)
|
||||
).run();
|
||||
);
|
||||
return c.json({
|
||||
success: true
|
||||
})
|
||||
|
||||
+35
-2
@@ -1,6 +1,6 @@
|
||||
import { Jwt } from 'hono/utils/jwt'
|
||||
|
||||
import { getDomains } from './utils';
|
||||
import { getDomains, getStringValue } from './utils';
|
||||
|
||||
export const newAddress = async (c, name, domain, enablePrefix) => {
|
||||
// remove special characters
|
||||
@@ -19,7 +19,7 @@ export const newAddress = async (c, name, domain, enablePrefix) => {
|
||||
}
|
||||
// create address
|
||||
if (enablePrefix) {
|
||||
name = c.env.PREFIX + name + "@" + domain;
|
||||
name = getStringValue(c.env.PREFIX) + name + "@" + domain;
|
||||
} else {
|
||||
name = name + "@" + domain;
|
||||
}
|
||||
@@ -53,3 +53,36 @@ export const newAddress = async (c, name, domain, enablePrefix) => {
|
||||
jwt: jwt
|
||||
})
|
||||
}
|
||||
|
||||
export const cleanup = async (c, cleanType, cleanDays) => {
|
||||
if (!cleanType || !cleanDays || cleanDays < 0 || cleanDays > 30) {
|
||||
throw new Error("Invalid cleanType or cleanDays")
|
||||
}
|
||||
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 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:
|
||||
throw new Error("Invalid cleanType")
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export const CONSTANTS = {
|
||||
ADDRESS_BLOCK_LIST_KEY: 'address_block_list',
|
||||
AUTO_CLEANUP_KEY: 'auto_cleanup',
|
||||
}
|
||||
|
||||
+5
-49
@@ -1,6 +1,8 @@
|
||||
import { Hono } from 'hono'
|
||||
|
||||
import { getDomains, getPasswords, getBooleanValue } from './utils';
|
||||
import {
|
||||
getDomains, getPasswords, getBooleanValue, getJsonSetting
|
||||
} from './utils';
|
||||
import { newAddress } from './common'
|
||||
import { CONSTANTS } from './constants'
|
||||
|
||||
@@ -83,21 +85,6 @@ api.get('/api/settings', async (c) => {
|
||||
} catch (e) {
|
||||
console.warn("Failed to update address")
|
||||
}
|
||||
let auto_reply = {};
|
||||
if (getBooleanValue(c.env.ENABLE_AUTO_REPLY)) {
|
||||
const results = await c.env.DB.prepare(
|
||||
`SELECT * FROM auto_reply_mails where address = ? `
|
||||
).bind(address).first();
|
||||
if (results) {
|
||||
auto_reply = {
|
||||
subject: results.subject,
|
||||
message: results.message,
|
||||
enabled: results.enabled == 1,
|
||||
source_prefix: results.source_prefix,
|
||||
name: results.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
const { count: mailCountV1 } = await c.env.DB.prepare(
|
||||
`SELECT count(*) as count FROM mails where address = ?`
|
||||
).bind(address).first();
|
||||
@@ -106,41 +93,12 @@ api.get('/api/settings', async (c) => {
|
||||
where address = ? and enabled = 1`
|
||||
).bind(address).first("balance");
|
||||
return c.json({
|
||||
auto_reply: auto_reply,
|
||||
address: address,
|
||||
has_v1_mails: mailCountV1 && mailCountV1 > 0,
|
||||
send_balance: balance || 0,
|
||||
});
|
||||
})
|
||||
|
||||
api.post('/api/settings', async (c) => {
|
||||
const { address } = c.get("jwtPayload")
|
||||
if (!getBooleanValue(c.env.ENABLE_AUTO_REPLY)) {
|
||||
return c.text("Auto reply is disabled", 403)
|
||||
}
|
||||
const { auto_reply } = await c.req.json();
|
||||
const { name, subject, source_prefix, message, enabled } = auto_reply;
|
||||
if ((!subject || !message) && enabled) {
|
||||
return c.text("Invalid subject or message", 400)
|
||||
}
|
||||
else if (subject.length > 255 || message.length > 255) {
|
||||
return c.text("Subject or message too long", 400)
|
||||
}
|
||||
const { success } = await c.env.DB.prepare(
|
||||
`INSERT OR REPLACE INTO
|
||||
auto_reply_mails
|
||||
(name, address, source_prefix, subject, message, enabled)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?)`
|
||||
).bind(name || '', address, source_prefix || '', subject || '', message || '', enabled ? 1 : 0).run();
|
||||
if (!success) {
|
||||
return c.text("Failed to save settings", 500)
|
||||
}
|
||||
return c.json({
|
||||
success: success
|
||||
})
|
||||
})
|
||||
|
||||
api.get('/open_api/settings', async (c) => {
|
||||
// check header x-custom-auth
|
||||
let needAuth = false;
|
||||
@@ -172,10 +130,8 @@ api.get('/api/new_address', async (c) => {
|
||||
}
|
||||
// check name block list
|
||||
try {
|
||||
const value = await c.env.DB.prepare(
|
||||
`SELECT value FROM settings where key = ?`
|
||||
).bind(CONSTANTS.ADDRESS_BLOCK_LIST_KEY).first("value");
|
||||
const blockList = value ? JSON.parse(value) : [];
|
||||
const value = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY);
|
||||
const blockList = value || [];
|
||||
if (blockList.some((item) => name.includes(item))) {
|
||||
return c.text(`Name [${name}] is blocked`, 400)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { cleanup } from './common'
|
||||
import { CONSTANTS } from './constants'
|
||||
import { getJsonSetting } from './utils';
|
||||
|
||||
export async function scheduled(event, env, ctx) {
|
||||
console.log("Scheduled event: ", event);
|
||||
let autoCleanupSetting = await getJsonSetting(
|
||||
{ env: env, },
|
||||
CONSTANTS.AUTO_CLEANUP_KEY
|
||||
);
|
||||
console.log("autoCleanupSetting:", JSON.stringify(autoCleanupSetting));
|
||||
autoCleanupSetting = autoCleanupSetting || {};
|
||||
if (autoCleanupSetting.enableMailsAutoCleanup && autoCleanupSetting.cleanMailsDays > 0) {
|
||||
await cleanup(
|
||||
{ env: env, },
|
||||
"mails",
|
||||
autoCleanupSetting.cleanMailsDays
|
||||
);
|
||||
}
|
||||
if (autoCleanupSetting.enableUnknowMailsAutoCleanup && autoCleanupSetting.cleanUnknowMailsDays > 0) {
|
||||
await cleanup(
|
||||
{ env: env, },
|
||||
"mails_unknow",
|
||||
autoCleanupSetting.cleanUnknowMailsDays
|
||||
);
|
||||
}
|
||||
if (autoCleanupSetting.enableAddressAutoCleanup && autoCleanupSetting.cleanAddressDays > 0) {
|
||||
await cleanup(
|
||||
{ env: env, },
|
||||
"address",
|
||||
autoCleanupSetting.cleanAddressDays
|
||||
);
|
||||
}
|
||||
if (autoCleanupSetting.enableSendBoxAutoCleanup && autoCleanupSetting.cleanSendBoxDays > 0) {
|
||||
await cleanup(
|
||||
{ env: env, },
|
||||
"sendbox",
|
||||
autoCleanupSetting.cleanSendBoxDays
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { getBooleanValue } from "../utils";
|
||||
|
||||
|
||||
export default {
|
||||
getAutoReply: async (c) => {
|
||||
if (!getBooleanValue(c.env.ENABLE_AUTO_REPLY)) {
|
||||
return c.text("Auto reply is disabled", 403)
|
||||
}
|
||||
const { address } = c.get("jwtPayload")
|
||||
const results = await c.env.DB.prepare(
|
||||
`SELECT * FROM auto_reply_mails where address = ? `
|
||||
).bind(address).first();
|
||||
if (!results) {
|
||||
return c.json({});
|
||||
}
|
||||
return c.json({
|
||||
subject: results.subject,
|
||||
message: results.message,
|
||||
enabled: results.enabled == 1,
|
||||
source_prefix: results.source_prefix,
|
||||
name: results.name,
|
||||
})
|
||||
},
|
||||
saveAutoReply: async (c) => {
|
||||
if (!getBooleanValue(c.env.ENABLE_AUTO_REPLY)) {
|
||||
return c.text("Auto reply is disabled", 403)
|
||||
}
|
||||
const { address } = c.get("jwtPayload")
|
||||
const { auto_reply } = await c.req.json();
|
||||
const { name, subject, source_prefix, message, enabled } = auto_reply;
|
||||
if ((!subject || !message) && enabled) {
|
||||
return c.text("Invalid subject or message", 400)
|
||||
}
|
||||
else if (subject.length > 255 || message.length > 255) {
|
||||
return c.text("Subject or message too long", 400)
|
||||
}
|
||||
const { success } = await c.env.DB.prepare(
|
||||
`INSERT OR REPLACE INTO auto_reply_mails`
|
||||
+ ` (name, address, source_prefix, subject, message, enabled)`
|
||||
+ ` VALUES (?, ?, ?, ?, ?, ?)`
|
||||
).bind(
|
||||
name || '', address, source_prefix || '',
|
||||
subject || '', message || '', enabled ? 1 : 0
|
||||
).run();
|
||||
if (!success) {
|
||||
return c.text("Failed to auto_reply settings", 500)
|
||||
}
|
||||
return c.json({
|
||||
success: success
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Hono } from 'hono'
|
||||
|
||||
import auto_reply from './user/auto_reply'
|
||||
|
||||
const api = new Hono()
|
||||
|
||||
api.get('/api/auto_reply', auto_reply.getAutoReply)
|
||||
api.post('/api/auto_reply', auto_reply.saveAutoReply)
|
||||
|
||||
export { api }
|
||||
@@ -1,5 +1,45 @@
|
||||
import { createMimeMessage } from "mimetext";
|
||||
|
||||
export const getJsonSetting = async (c, key) => {
|
||||
const value = await getSetting(c, key);
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
console.error(`GetJsonSetting: Failed to parse ${key}`, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const getSetting = async (c, key) => {
|
||||
try {
|
||||
const value = await c.env.DB.prepare(
|
||||
`SELECT value FROM settings where key = ?`
|
||||
).bind(key).first("value");
|
||||
return value;
|
||||
} catch (error) {
|
||||
console.error(`GetSetting: Failed to get ${key}`, error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const saveSetting = async (c, key, value) => {
|
||||
await c.env.DB.prepare(
|
||||
`INSERT or REPLACE INTO settings (key, value) VALUES (?, ?)`
|
||||
+ ` ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')`
|
||||
).bind(key, value, value).run();
|
||||
return true;
|
||||
}
|
||||
|
||||
export const getStringValue = (value) => {
|
||||
if (typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export const getBooleanValue = (value) => {
|
||||
if (typeof value === "boolean") {
|
||||
return value;
|
||||
|
||||
@@ -3,10 +3,12 @@ import { cors } from 'hono/cors';
|
||||
import { jwt } from 'hono/jwt'
|
||||
|
||||
import { api } from './router';
|
||||
import { api as userApi } from './user_api';
|
||||
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 { scheduled } from './scheduled';
|
||||
import { getAdminPasswords, getPasswords } from './utils';
|
||||
|
||||
const app = new Hono()
|
||||
@@ -53,6 +55,7 @@ app.use('/admin/*', async (c, next) => {
|
||||
|
||||
|
||||
app.route('/', api)
|
||||
app.route('/', userApi)
|
||||
app.route('/', adminApi)
|
||||
app.route('/', apiV1)
|
||||
app.route('/', apiSendMail)
|
||||
@@ -65,4 +68,5 @@ app.all('/*', async c => c.text("Not Found", 404))
|
||||
export default {
|
||||
fetch: app.fetch,
|
||||
email: email,
|
||||
scheduled: scheduled,
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ node_compat = true
|
||||
# { pattern = "temp-email-api.xxxxx.xyz", custom_domain = true },
|
||||
# ]
|
||||
|
||||
# enable cron if you want set auto clean up
|
||||
# [triggers]
|
||||
# crons = [ "0 0 * * *" ]
|
||||
|
||||
[vars]
|
||||
PREFIX = "tmp"
|
||||
# IF YOU WANT TO MAKE YOUR SITE PRIVATE, UNCOMMENT THE FOLLOWING LINES
|
||||
|
||||
Reference in New Issue
Block a user