From 32767176f08048c6a30fad796b170862b8e62744 Mon Sep 17 00:00:00 2001 From: Dream Hunter Date: Mon, 7 Apr 2025 20:17:56 +0800 Subject: [PATCH] feat: s3 attachment add delete (#625) --- CHANGELOG.md | 1 + frontend/src/views/index/Attachment.vue | 35 +++++++++++++++++++++++++ worker/src/mails_api/index.ts | 1 + worker/src/mails_api/s3_attachment.ts | 15 ++++++++++- 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1dcdffd..356cfb41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - feat: |UI| 支持 URL jwt 参数自动登录邮箱,jwt 参数会覆盖浏览器中的 jwt - fix: |CleanUP| 修复清理邮件时,清理时间超过 30 天报错的 bug - feat: admin 用户管理页面: 增加 用户地址查看功能 +- feat: | S3 附件| 增加 S3 附件删除功能 ## v0.9.0 diff --git a/frontend/src/views/index/Attachment.vue b/frontend/src/views/index/Attachment.vue index 90d6085e..7327537b 100644 --- a/frontend/src/views/index/Attachment.vue +++ b/frontend/src/views/index/Attachment.vue @@ -3,6 +3,7 @@ import { ref, h, onMounted } from 'vue'; import { useI18n } from 'vue-i18n' import { api } from '../../api' +import { NPopconfirm } from 'naive-ui'; const message = useMessage() @@ -11,10 +12,16 @@ const { t } = useI18n({ en: { download: 'Download', action: 'Action', + delete: 'Delete', + deleteConfirm: 'Are you sure to delete this attachment?', + deleteSuccess: 'Deleted successfully', }, zh: { download: '下载', action: '操作', + delete: '删除', + deleteConfirm: '确定要删除此附件吗?', + deleteSuccess: '删除成功', } } }); @@ -66,6 +73,34 @@ const columns = [ } }, { default: () => t('download') } + ), + h(NPopconfirm, + { + onPositiveClick: async () => { + try { + await api.fetch(`/api/attachment/delete`, { + method: 'POST', + body: JSON.stringify({ key: row.key }) + }); + message.success(t('deleteSuccess')); + await fetchData(); + } + catch (error) { + console.error(error); + message.error(error.message || "error"); + } + }, + }, + { + trigger: () => h(NButton, + { + tertiary: true, + type: "error", + }, + { default: () => t('delete') } + ), + default: () => t('deleteConfirm') + } ) ]) } diff --git a/worker/src/mails_api/index.ts b/worker/src/mails_api/index.ts index b89b5312..0383f122 100644 --- a/worker/src/mails_api/index.ts +++ b/worker/src/mails_api/index.ts @@ -17,6 +17,7 @@ api.get('/api/webhook/settings', webhook_settings.getWebhookSettings) api.post('/api/webhook/settings', webhook_settings.saveWebhookSettings) api.post('/api/webhook/test', webhook_settings.testWebhookSettings) api.get('/api/attachment/list', s3_attachment.list) +api.post('/api/attachment/delete', s3_attachment.deleteKey) api.post('/api/attachment/put_url', s3_attachment.getSignedPutUrl) api.post('/api/attachment/get_url', s3_attachment.getSignedGetUrl) diff --git a/worker/src/mails_api/s3_attachment.ts b/worker/src/mails_api/s3_attachment.ts index 1ee6bf8f..a12181ae 100644 --- a/worker/src/mails_api/s3_attachment.ts +++ b/worker/src/mails_api/s3_attachment.ts @@ -4,7 +4,8 @@ import { S3Client, ListObjectsV2Command, GetObjectCommand, - PutObjectCommand + PutObjectCommand, + DeleteObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; @@ -81,4 +82,16 @@ export default { } ); }, + deleteKey: async (c: Context) => { + const { address } = c.get("jwtPayload") + const { key } = await c.req.json() + const client = getS3Client(c); + await client.send( + new DeleteObjectCommand({ + Bucket: c.env.S3_BUCKET, + Key: `${address}/${key}` + }) + ); + return c.json({ success: true }); + } }