feat: add cron auto clean up (#189)

This commit is contained in:
Dream Hunter
2024-05-03 22:05:44 +08:00
committed by GitHub
parent 6e02e9b20b
commit 51ad37e951
16 changed files with 351 additions and 159 deletions
+1 -3
View File
@@ -5,9 +5,7 @@ import { useI18n } from 'vue-i18n'
import { useGlobalState } from '../../store' import { useGlobalState } from '../../store'
import { api } from '../../api' import { api } from '../../api'
const { const { localeCache, loading } = useGlobalState()
localeCache, loading, openSettings,
} = useGlobalState()
const message = useMessage() const message = useMessage()
const { t } = useI18n({ const { t } = useI18n({
+83 -32
View File
@@ -8,29 +8,41 @@ import { api } from '../../api'
const { localeCache, adminAuth, showAdminAuth } = useGlobalState() const { localeCache, adminAuth, showAdminAuth } = useGlobalState()
const message = useMessage() const message = useMessage()
const cleanMailsDays = ref(30) const cleanupModel = ref({
const cleanUnknowMailsDays = ref(30) enableMailsAutoCleanup: false,
const cleanAddressDays = ref(30) cleanMailsDays: 30,
const cleanSendBoxDays = ref(30) enableUnknowMailsAutoCleanup: false,
cleanUnknowMailsDays: 30,
enableAddressAutoCleanup: false,
cleanAddressDays: 30,
enableSendBoxAutoCleanup: false,
cleanSendBoxDays: 30,
})
const { t } = useI18n({ const { t } = useI18n({
locale: localeCache.value || 'zh', locale: localeCache.value || 'zh',
messages: { messages: {
en: { en: {
tip: 'Please input the cleanup days', tip: 'Please input the cleanup days',
mailBoxTip: "Clean up {day} days ago mailbox", mailBoxLabel: 'Clean up days for mailbox',
mailUnknowTip: "Clean up {day} days ago mails with unknow receiver", mailUnknowLabel: "Clean up days for unknow receiver",
addressUnActiveTip: "Clean up {day} days ago unactive address", addressUnActiveLabel: "Clean up days for unactive address",
sendBoxTip: "Clean up {day} days ago sendbox", sendBoxLabel: "Clean up days for sendbox",
cleanupNow: "Cleanup now",
autoCleanup: "Auto cleanup",
cleanupSuccess: "Cleanup success", cleanupSuccess: "Cleanup success",
save: "Save",
}, },
zh: { zh: {
tip: '请输入清理天数', tip: '请输入清理天数',
mailBoxTip: "清理{day}天前的收件箱", mailBoxLabel: '收件箱清理天数',
mailUnknowTip: "清理{day}天前的无收件人邮件", mailUnknowLabel: "无收件人邮件清理天数",
addressUnActiveTip: "清理{day}天前的未活动地址", addressUnActiveLabel: "未激活地址清理天数",
sendBoxTip: "清理{day}天前的发件箱", sendBoxLabel: "发件箱清理天数",
autoCleanup: "自动清理",
cleanupSuccess: "清理成功", cleanupSuccess: "清理成功",
cleanupNow: "立即清理",
save: "保存",
} }
} }
}); });
@@ -47,11 +59,33 @@ const cleanup = async (cleanType, cleanDays) => {
} }
} }
const fetchData = async () => {
try {
const res = await api.fetch('/admin/auto_cleanup');
if (res) Object.assign(cleanupModel.value, res);
} catch (error) {
message.error(error.message || "error");
}
}
const save = async () => {
try {
await api.fetch('/admin/auto_cleanup', {
method: 'POST',
body: JSON.stringify(cleanupModel.value)
});
message.success(t('cleanupSuccess'));
} catch (error) {
message.error(error.message || "error");
}
}
onMounted(async () => { onMounted(async () => {
if (!adminAuth.value) { if (!adminAuth.value) {
showAdminAuth.value = true; showAdminAuth.value = true;
return; return;
} }
await fetchData();
}) })
</script> </script>
@@ -59,42 +93,59 @@ onMounted(async () => {
<template> <template>
<div class="center"> <div class="center">
<n-card> <n-card>
<div class="item"> <n-form :model="cleanupModel">
<n-input-number v-model:value="cleanMailsDays" :placeholder="t('tip')" /> <n-form-item-row :label="t('mailBoxLabel')">
<n-button @click="cleanup('mails', cleanMailsDays)"> <n-checkbox v-model:checked="cleanupModel.enableMailsAutoCleanup">
{{ t('autoCleanup') }}
</n-checkbox>
<n-input-number v-model:value="cleanupModel.cleanMailsDays" :placeholder="t('tip')" />
<n-button @click="cleanup('mails', cleanupModel.cleanMailsDays)">
<template #icon> <template #icon>
<n-icon :component="CleaningServicesFilled" /> <n-icon :component="CleaningServicesFilled" />
</template> </template>
{{ t('mailBoxTip', { day: cleanMailsDays }) }} {{ t('cleanupNow') }}
</n-button> </n-button>
</div> </n-form-item-row>
<div class="item"> <n-form-item-row :label="t('mailUnknowLabel')">
<n-input-number v-model:value="cleanUnknowMailsDays" :placeholder="t('tip')" /> <n-checkbox v-model:checked="cleanupModel.enableUnknowMailsAutoCleanup">
<n-button @click="cleanup('mails_unknow', cleanUnknowMailsDays)"> {{ t('autoCleanup') }}
</n-checkbox>
<n-input-number v-model:value="cleanupModel.cleanUnknowMailsDays" :placeholder="t('tip')" />
<n-button @click="cleanup('mails_unknow', cleanupModel.cleanUnknowMailsDays)">
<template #icon> <template #icon>
<n-icon :component="CleaningServicesFilled" /> <n-icon :component="CleaningServicesFilled" />
</template> </template>
{{ t('mailUnknowTip', { day: cleanUnknowMailsDays }) }} {{ t('cleanupNow') }}
</n-button> </n-button>
</div> </n-form-item-row>
<div class="item"> <n-form-item-row :label="t('addressUnActiveLabel')">
<n-input-number v-model:value="cleanAddressDays" :placeholder="t('tip')" /> <n-checkbox v-model:checked="cleanupModel.enableAddressAutoCleanup">
<n-button @click="cleanup('address', cleanAddressDays)"> {{ t('autoCleanup') }}
</n-checkbox>
<n-input-number v-model:value="cleanupModel.cleanAddressDays" :placeholder="t('tip')" />
<n-button @click="cleanup('address', cleanupModel.cleanAddressDays)">
<template #icon> <template #icon>
<n-icon :component="CleaningServicesFilled" /> <n-icon :component="CleaningServicesFilled" />
</template> </template>
{{ t('addressUnActiveTip', { day: cleanAddressDays }) }} {{ t('cleanupNow') }}
</n-button> </n-button>
</div> </n-form-item-row>
<div class="item"> <n-form-item-row :label="t('mailBoxLabel')">
<n-input-number v-model:value="cleanSendBoxDays" :placeholder="t('tip')" /> <n-checkbox v-model:checked="cleanupModel.enableSendBoxAutoCleanup">
<n-button @click="cleanup('sendbox', cleanSendBoxDays)"> {{ t('autoCleanup') }}
</n-checkbox>
<n-input-number v-model:value="cleanupModel.cleanSendBoxDays" :placeholder="t('tip')" />
<n-button @click="cleanup('sendbox', cleanupModel.cleanSendBoxDays)">
<template #icon> <template #icon>
<n-icon :component="CleaningServicesFilled" /> <n-icon :component="CleaningServicesFilled" />
</template> </template>
{{ t('sendBoxTip', { day: cleanSendBoxDays }) }} {{ t('cleanupNow') }}
</n-button> </n-button>
</div> </n-form-item-row>
<n-button @click="save" type="primary" block :loading="loading">
{{ t('save') }}
</n-button>
</n-form>
</n-card> </n-card>
</div> </div>
</template> </template>
+15 -10
View File
@@ -41,17 +41,22 @@ const { t } = useI18n({
} }
}); });
const getSettings = async () => { const fetchData = async () => {
sourcePrefix.value = settings.value.auto_reply.source_prefix || "" try {
enableAutoReply.value = settings.value.auto_reply.enabled || false const res = await api.fetch("/api/auto_reply")
name.value = settings.value.auto_reply.name || "" sourcePrefix.value = res.source_prefix || ""
autoReplyMessage.value = settings.value.auto_reply.message || "" enableAutoReply.value = res.enabled || false
subject.value = settings.value.auto_reply.subject || "" name.value = res.name || ""
autoReplyMessage.value = res.message || ""
subject.value = res.subject || ""
} catch (error) {
message.error(error.message || "error");
}
} }
const saveSettings = async () => { const saveData = async () => {
try { try {
await api.fetch("/api/settings", { await api.fetch("/api/auto_reply", {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
auto_reply: { auto_reply: {
@@ -70,7 +75,7 @@ const saveSettings = async () => {
} }
onMounted(async () => { onMounted(async () => {
await getSettings() await fetchData()
}) })
</script> </script>
@@ -78,7 +83,7 @@ onMounted(async () => {
<div class="center"> <div class="center">
<n-card v-if="settings.address" :title='t("settings")'> <n-card v-if="settings.address" :title='t("settings")'>
<div class="right"> <div class="right">
<n-button type="primary" @click="saveSettings">{{ t('save') }}</n-button> <n-button type="primary" @click="saveData">{{ t('save') }}</n-button>
</div> </div>
<div class="left"> <div class="left">
<n-form-item :label="t('enableAutoReply')" label-placement="left"> <n-form-item :label="t('enableAutoReply')" label-placement="left">
+5 -1
View File
@@ -56,12 +56,16 @@ pnpm run deploy
`wrangler.toml` `wrangler.toml`
```bash ```toml
name = "cloudflare_temp_email" name = "cloudflare_temp_email"
main = "src/worker.js" main = "src/worker.js"
compatibility_date = "2023-08-14" compatibility_date = "2023-08-14"
node_compat = true node_compat = true
# enable cron if you want set auto clean up
# [triggers]
# crons = [ "0 0 * * *" ]
[vars] [vars]
PREFIX = "tmp" # The mailbox name prefix to be processed PREFIX = "tmp" # The mailbox name prefix to be processed
# If you want your site to be private, uncomment below and change your password # If you want your site to be private, uncomment below and change your password
@@ -20,6 +20,10 @@ compatibility_date = "2023-12-01"
# ] # ]
node_compat = true node_compat = true
# 如果你想要使用定时任务清理邮件,取消下面的注释,并修改 cron 表达式
# [triggers]
# crons = [ "0 0 * * *" ]
[vars] [vars]
PREFIX = "tmp" # 要处理的邮箱名称前缀,不需要后缀可配置为空字符串 PREFIX = "tmp" # 要处理的邮箱名称前缀,不需要后缀可配置为空字符串
# 如果你想要你的网站私有,取消下面的注释,并修改密码 # 如果你想要你的网站私有,取消下面的注释,并修改密码
+25
View File
@@ -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
View File
@@ -1,8 +1,9 @@
import { Hono } from 'hono' import { Hono } from 'hono'
import { Jwt } from 'hono/utils/jwt' import { Jwt } from 'hono/utils/jwt'
import { sendAdminInternalMail } from './utils' import { sendAdminInternalMail, getJsonSetting, saveSetting } from './utils'
import { newAddress } from './common' import { newAddress } from './common'
import { CONSTANTS } from './constants' import { CONSTANTS } from './constants'
import cleanup_api from './admin/cleanup_api'
const api = new Hono() const api = new Hono()
@@ -294,49 +295,16 @@ api.get('/admin/statistics', async (c) => {
}) })
}); });
api.post('/admin/cleanup', async (c) => { api.post('/admin/cleanup', cleanup_api.cleanup)
const { cleanType, cleanDays } = await c.req.json(); api.get('/admin/auto_cleanup', cleanup_api.getCleanup)
if (!cleanType || !cleanDays || cleanDays < 0 || cleanDays > 30) { api.post('/admin/auto_cleanup', cleanup_api.saveCleanup)
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.get('/admin/account_settings', async (c) => { api.get('/admin/account_settings', async (c) => {
try { try {
const value = await c.env.DB.prepare( const value = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY);
`SELECT value FROM settings where key = ?`
).bind(CONSTANTS.ADDRESS_BLOCK_LIST_KEY).first("value");
return c.json({ return c.json({
blockList: value ? JSON.parse(value) : [] blockList: value || []
}) })
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@@ -349,14 +317,10 @@ api.post('/admin/account_settings', async (c) => {
if (!blockList) { if (!blockList) {
return c.text("Invalid blockList", 400) return c.text("Invalid blockList", 400)
} }
await c.env.DB.prepare( await saveSetting(
`INSERT or REPLACE INTO settings (key, value) VALUES (?, ?)` c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY,
+ ` ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')`
).bind(
CONSTANTS.ADDRESS_BLOCK_LIST_KEY,
JSON.stringify(blockList),
JSON.stringify(blockList) JSON.stringify(blockList)
).run(); );
return c.json({ return c.json({
success: true success: true
}) })
+35 -2
View File
@@ -1,6 +1,6 @@
import { Jwt } from 'hono/utils/jwt' import { Jwt } from 'hono/utils/jwt'
import { getDomains } from './utils'; import { getDomains, getStringValue } from './utils';
export const newAddress = async (c, name, domain, enablePrefix) => { export const newAddress = async (c, name, domain, enablePrefix) => {
// remove special characters // remove special characters
@@ -19,7 +19,7 @@ export const newAddress = async (c, name, domain, enablePrefix) => {
} }
// create address // create address
if (enablePrefix) { if (enablePrefix) {
name = c.env.PREFIX + name + "@" + domain; name = getStringValue(c.env.PREFIX) + name + "@" + domain;
} else { } else {
name = name + "@" + domain; name = name + "@" + domain;
} }
@@ -53,3 +53,36 @@ export const newAddress = async (c, name, domain, enablePrefix) => {
jwt: jwt 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
View File
@@ -1,3 +1,4 @@
export const CONSTANTS = { export const CONSTANTS = {
ADDRESS_BLOCK_LIST_KEY: 'address_block_list', ADDRESS_BLOCK_LIST_KEY: 'address_block_list',
AUTO_CLEANUP_KEY: 'auto_cleanup',
} }
+5 -49
View File
@@ -1,6 +1,8 @@
import { Hono } from 'hono' import { Hono } from 'hono'
import { getDomains, getPasswords, getBooleanValue } from './utils'; import {
getDomains, getPasswords, getBooleanValue, getJsonSetting
} from './utils';
import { newAddress } from './common' import { newAddress } from './common'
import { CONSTANTS } from './constants' import { CONSTANTS } from './constants'
@@ -83,21 +85,6 @@ api.get('/api/settings', async (c) => {
} catch (e) { } catch (e) {
console.warn("Failed to update address") 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( const { count: mailCountV1 } = await c.env.DB.prepare(
`SELECT count(*) as count FROM mails where address = ?` `SELECT count(*) as count FROM mails where address = ?`
).bind(address).first(); ).bind(address).first();
@@ -106,41 +93,12 @@ api.get('/api/settings', async (c) => {
where address = ? and enabled = 1` where address = ? and enabled = 1`
).bind(address).first("balance"); ).bind(address).first("balance");
return c.json({ return c.json({
auto_reply: auto_reply,
address: address, address: address,
has_v1_mails: mailCountV1 && mailCountV1 > 0, has_v1_mails: mailCountV1 && mailCountV1 > 0,
send_balance: balance || 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) => { api.get('/open_api/settings', async (c) => {
// check header x-custom-auth // check header x-custom-auth
let needAuth = false; let needAuth = false;
@@ -172,10 +130,8 @@ api.get('/api/new_address', async (c) => {
} }
// check name block list // check name block list
try { try {
const value = await c.env.DB.prepare( const value = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY);
`SELECT value FROM settings where key = ?` const blockList = value || [];
).bind(CONSTANTS.ADDRESS_BLOCK_LIST_KEY).first("value");
const blockList = value ? JSON.parse(value) : [];
if (blockList.some((item) => name.includes(item))) { if (blockList.some((item) => name.includes(item))) {
return c.text(`Name [${name}] is blocked`, 400) return c.text(`Name [${name}] is blocked`, 400)
} }
+41
View File
@@ -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
);
}
}
+52
View File
@@ -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
})
}
}
+10
View File
@@ -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 }
+40
View File
@@ -1,5 +1,45 @@
import { createMimeMessage } from "mimetext"; 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) => { export const getBooleanValue = (value) => {
if (typeof value === "boolean") { if (typeof value === "boolean") {
return value; return value;
+4
View File
@@ -3,10 +3,12 @@ import { cors } from 'hono/cors';
import { jwt } from 'hono/jwt' import { jwt } from 'hono/jwt'
import { api } from './router'; import { api } from './router';
import { api as userApi } from './user_api';
import { api as adminApi } from './admin_api'; import { api as adminApi } from './admin_api';
import { api as apiV1 } from './api_v1'; import { api as apiV1 } from './api_v1';
import { api as apiSendMail } from './send_mail_api' import { api as apiSendMail } from './send_mail_api'
import { email } from './email'; import { email } from './email';
import { scheduled } from './scheduled';
import { getAdminPasswords, getPasswords } from './utils'; import { getAdminPasswords, getPasswords } from './utils';
const app = new Hono() const app = new Hono()
@@ -53,6 +55,7 @@ app.use('/admin/*', async (c, next) => {
app.route('/', api) app.route('/', api)
app.route('/', userApi)
app.route('/', adminApi) app.route('/', adminApi)
app.route('/', apiV1) app.route('/', apiV1)
app.route('/', apiSendMail) app.route('/', apiSendMail)
@@ -65,4 +68,5 @@ app.all('/*', async c => c.text("Not Found", 404))
export default { export default {
fetch: app.fetch, fetch: app.fetch,
email: email, email: email,
scheduled: scheduled,
} }
+4
View File
@@ -7,6 +7,10 @@ node_compat = true
# { pattern = "temp-email-api.xxxxx.xyz", custom_domain = true }, # { pattern = "temp-email-api.xxxxx.xyz", custom_domain = true },
# ] # ]
# enable cron if you want set auto clean up
# [triggers]
# crons = [ "0 0 * * *" ]
[vars] [vars]
PREFIX = "tmp" PREFIX = "tmp"
# IF YOU WANT TO MAKE YOUR SITE PRIVATE, UNCOMMENT THE FOLLOWING LINES # IF YOU WANT TO MAKE YOUR SITE PRIVATE, UNCOMMENT THE FOLLOWING LINES