feat: cleanup support address and inactive address (#671)

This commit is contained in:
Dream Hunter
2025-06-18 17:31:15 +08:00
committed by GitHub
parent c694b07380
commit 9ac9cd46b0
6 changed files with 115 additions and 29 deletions

View File

@@ -5,6 +5,7 @@
- fix: |UI| 修复 User 查看收件箱,不选择地址时,关键词查询不生效
- fix: 修复自动清理任务,时间为 0 时不生效的问题
- feat: 清理功能增加 创建 n 天前地址清理n 天前未活跃地址清理
## v0.10.0

View File

@@ -11,10 +11,12 @@ const cleanupModel = ref({
cleanMailsDays: 30,
enableUnknowMailsAutoCleanup: false,
cleanUnknowMailsDays: 30,
enableAddressAutoCleanup: false,
cleanAddressDays: 30,
enableSendBoxAutoCleanup: false,
cleanSendBoxDays: 30,
enableAddressAutoCleanup: false,
cleanAddressDays: 30,
enableInactiveAddressAutoCleanup: false,
cleanInactiveAddressDays: 30,
})
const { t } = useI18n({
@@ -24,6 +26,8 @@ const { t } = useI18n({
mailBoxLabel: 'Cleanup the inbox before n days',
mailUnknowLabel: "Cleanup the unknow mail before n days",
sendBoxLabel: "Cleanup the sendbox before n days",
addressCreateLabel: "Cleanup the address created before n days",
inactiveAddressLabel: "Cleanup the inactive address before n days",
cleanupNow: "Cleanup now",
autoCleanup: "Auto cleanup",
cleanupSuccess: "Cleanup success",
@@ -35,6 +39,8 @@ const { t } = useI18n({
mailBoxLabel: '清理 n 天前的收件箱',
mailUnknowLabel: "清理 n 天前的无收件人邮件",
sendBoxLabel: "清理 n 天前的发件箱",
addressCreateLabel: "清理 n 天前创建的地址",
inactiveAddressLabel: "清理 n 天前的未活跃地址",
autoCleanup: "自动清理",
cleanupSuccess: "清理成功",
cleanupNow: "立即清理",
@@ -86,9 +92,14 @@ onMounted(async () => {
<template>
<div class="center">
<n-card :bordered="false" embedded>
<n-alert :show-icon="false" :bordered="false">
<n-alert :show-icon="false" :bordered="false" type="warning">
<span>{{ t('cronTip') }}</span>
</n-alert>
<n-flex justify="end">
<n-button @click="save" type="primary" :loading="loading">
{{ t('save') }}
</n-button>
</n-flex>
<n-form :model="cleanupModel">
<n-form-item-row :label="t('mailBoxLabel')">
<n-checkbox v-model:checked="cleanupModel.enableMailsAutoCleanup">
@@ -126,9 +137,30 @@ onMounted(async () => {
{{ t('cleanupNow') }}
</n-button>
</n-form-item-row>
<n-button @click="save" type="primary" block :loading="loading">
{{ t('save') }}
</n-button>
<n-form-item-row :label="t('addressCreateLabel')">
<n-checkbox v-model:checked="cleanupModel.enableAddressAutoCleanup">
{{ t('autoCleanup') }}
</n-checkbox>
<n-input-number v-model:value="cleanupModel.cleanAddressDays" :placeholder="t('tip')" />
<n-button @click="cleanup('addressCreated', cleanupModel.cleanAddressDays)">
<template #icon>
<n-icon :component="CleaningServicesFilled" />
</template>
{{ t('cleanupNow') }}
</n-button>
</n-form-item-row>
<n-form-item-row :label="t('inactiveAddressLabel')">
<n-checkbox v-model:checked="cleanupModel.enableInactiveAddressAutoCleanup">
{{ t('autoCleanup') }}
</n-checkbox>
<n-input-number v-model:value="cleanupModel.cleanInactiveAddressDays" :placeholder="t('tip')" />
<n-button @click="cleanup('inactiveAddress', cleanupModel.cleanInactiveAddressDays)">
<template #icon>
<n-icon :component="CleaningServicesFilled" />
</template>
{{ t('cleanupNow') }}
</n-button>
</n-form-item-row>
</n-form>
</n-card>
</div>

View File

@@ -17,13 +17,11 @@ export default {
return c.json({ success: true })
},
getCleanup: async (c: Context<HonoCustomType>) => {
const value = await getJsonSetting(c, CONSTANTS.AUTO_CLEANUP_KEY);
const cleanupSetting = new CleanupSettings(value);
const cleanupSetting = await getJsonSetting<CleanupSettings>(c, CONSTANTS.AUTO_CLEANUP_KEY);
return c.json(cleanupSetting)
},
saveCleanup: async (c: Context<HonoCustomType>) => {
const value = await c.req.json();
const cleanupSetting = new CleanupSettings(value);
const cleanupSetting = await c.req.json<CleanupSettings>();
await saveSetting(c, CONSTANTS.AUTO_CLEANUP_KEY, JSON.stringify(cleanupSetting));
return c.json({ success: true })
}

View File

@@ -155,6 +155,18 @@ export const cleanup = async (
}
console.log(`Cleanup ${cleanType} before ${cleanDays} days`);
switch (cleanType) {
case "inactiveAddress":
await batchDeleteAddressWithData(
c,
`updated_at < datetime('now', '-${cleanDays} day')`
)
break;
case "addressCreated":
await batchDeleteAddressWithData(
c,
`created_at < datetime('now', '-${cleanDays} day')`
)
break;
case "mails":
await c.env.DB.prepare(`
DELETE FROM raw_mails WHERE created_at < datetime('now', '-${cleanDays} day')`
@@ -177,6 +189,37 @@ export const cleanup = async (
return true;
}
const batchDeleteAddressWithData = async (
c: Context<HonoCustomType>,
addressQueryCondition: string,
): Promise<boolean> => {
await c.env.DB.prepare(
`DELETE FROM raw_mails WHERE address IN ( ` +
`SELECT name FROM address WHERE ${addressQueryCondition})`
).run();
await c.env.DB.prepare(
`DELETE FROM sendbox WHERE address IN ( ` +
`SELECT name FROM address WHERE ${addressQueryCondition})`
).run();
await c.env.DB.prepare(
`DELETE FROM auto_reply_mails WHERE address IN ( ` +
`SELECT name FROM address WHERE ${addressQueryCondition})`
).run();
await c.env.DB.prepare(
`DELETE FROM address_sender WHERE address IN ( ` +
`SELECT name FROM address WHERE ${addressQueryCondition})`
).run();
await c.env.DB.prepare(
`DELETE FROM users_address WHERE address_id IN ( ` +
`SELECT id FROM address WHERE ${addressQueryCondition})`
).run();
// delete address
await c.env.DB.prepare(`
DELETE FROM address WHERE ${addressQueryCondition}`
).run();
return true;
}
/**
* TODO: need senbox delete?
*/
@@ -214,13 +257,19 @@ export const deleteAddressWithData = async (
const { success: sendAccess } = await c.env.DB.prepare(
`DELETE FROM address_sender WHERE address = ? `
).bind(address).run();
const { success: sendboxSuccess } = await c.env.DB.prepare(
`DELETE FROM sendbox WHERE address = ? `
).bind(address).run();
const { success: addressSuccess } = await c.env.DB.prepare(
`DELETE FROM users_address WHERE address_id = ? `
).bind(address_id).run();
const { success: autoReplySuccess } = await c.env.DB.prepare(
`DELETE FROM auto_reply_mails WHERE address = ? `
).bind(address).run();
const { success } = await c.env.DB.prepare(
`DELETE FROM address WHERE name = ? `
).bind(address).run();
if (!success || !mailSuccess || !addressSuccess || !sendAccess) {
if (!success || !mailSuccess || !sendboxSuccess || !addressSuccess || !sendAccess || !autoReplySuccess) {
throw new Error("Failed to delete address")
}
return true;

View File

@@ -32,7 +32,7 @@ export type WebhookMail = {
parsedHtml: string;
}
export class CleanupSettings {
export type CleanupSettings = {
enableMailsAutoCleanup: boolean | undefined;
cleanMailsDays: number;
@@ -40,23 +40,12 @@ export class CleanupSettings {
cleanUnknowMailsDays: number;
enableSendBoxAutoCleanup: boolean | undefined;
cleanSendBoxDays: number;
constructor(data: CleanupSettings | undefined | null) {
const {
enableMailsAutoCleanup, cleanMailsDays,
enableUnknowMailsAutoCleanup, cleanUnknowMailsDays,
enableSendBoxAutoCleanup, cleanSendBoxDays
} = data || {};
this.enableMailsAutoCleanup = enableMailsAutoCleanup;
this.cleanMailsDays = cleanMailsDays || 0;
this.enableUnknowMailsAutoCleanup = enableUnknowMailsAutoCleanup;
this.cleanUnknowMailsDays = cleanUnknowMailsDays || 0;
this.enableSendBoxAutoCleanup = enableSendBoxAutoCleanup;
this.cleanSendBoxDays = cleanSendBoxDays || 0;
}
enableAddressAutoCleanup: boolean | undefined;
cleanAddressDays: number;
enableInactiveAddressAutoCleanup: boolean | undefined;
cleanInactiveAddressDays: number;
}
export class GeoData {
ip: string;

View File

@@ -6,11 +6,14 @@ import { CleanupSettings } from './models';
export async function scheduled(event: ScheduledEvent, env: Bindings, ctx: any) {
console.log("Scheduled event: ", event);
const value = await getJsonSetting(
const autoCleanupSetting = await getJsonSetting<CleanupSettings>(
{ env: env, } as Context<HonoCustomType>,
CONSTANTS.AUTO_CLEANUP_KEY
);
const autoCleanupSetting = new CleanupSettings(value);
if (!autoCleanupSetting) {
console.log("No auto cleanup settings found, skipping cleanup.");
return;
}
console.log("autoCleanupSetting:", JSON.stringify(autoCleanupSetting));
if (autoCleanupSetting.enableMailsAutoCleanup) {
await cleanup(
@@ -33,4 +36,18 @@ export async function scheduled(event: ScheduledEvent, env: Bindings, ctx: any)
autoCleanupSetting.cleanSendBoxDays
);
}
if (autoCleanupSetting.enableInactiveAddressAutoCleanup) {
await cleanup(
{ env: env, } as Context<HonoCustomType>,
"inactiveAddress",
autoCleanupSetting.cleanInactiveAddressDays
);
}
if (autoCleanupSetting.enableAddressAutoCleanup) {
await cleanup(
{ env: env, } as Context<HonoCustomType>,
"addressCreated",
autoCleanupSetting.cleanAddressDays
);
}
}