app 装配 mail:channel 作首个订阅方、出站经 mail.send、mail.send 工具真发信(dryRun 只校验)、轮询工作者;上传件存取抽出共用

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
This commit is contained in:
xcs
2026-08-26 02:05:50 +08:00
co-authored by Claude Fable 5
parent 58358210e2
commit 0c6e8e2c1b
10 changed files with 449 additions and 48 deletions
+4 -28
View File
@@ -5,7 +5,7 @@
* 「治理动作本身留痕入事件流 —— 治理也被治理。」
*/
import { sql } from 'drizzle-orm';
import { extractText } from '../upload-extract.ts';
import { readUpload, storeUpload, UPLOAD_CAP } from '../upload-store.ts';
import type { Hono } from 'hono';
import type { ApiError } from '@contracts';
import {
@@ -721,9 +721,6 @@ export function registerGovernanceOpsRoutes(app: Hono<AppEnv>, container: Contai
/* ── 上传件:附件要能真正进活动 ─────────────────────────────────── */
/** 上限 8MB —— 超了当场拒,不静默截断(截断过的文件比没有更害人) */
const UPLOAD_CAP = 8 * 1024 * 1024;
app.post(uploadFileRoute.path, async (c) => {
const parsed = uploadFileRoute.body.safeParse(await c.req.json());
if (!parsed.success) {
@@ -744,37 +741,16 @@ export function registerGovernanceOpsRoutes(app: Hono<AppEnv>, container: Contai
return c.json(body, 400);
}
// 读文本就在入口这一步做完:活动那一侧读得懂的只有文字,读不出就如实记 note
const got = extractText(name, contentType, bytes);
const me = staffOf(c);
const ref = crypto.randomUUID();
await container.db.execute(sql`
insert into sys.upload (id, name, content_type, size, bytes, text, note, uploaded_by)
values (${ref}, ${name}, ${contentType}, ${bytes.length}, ${Buffer.from(bytes)},
${got.text ?? null}, ${got.note}, ${me?.id ?? null})`);
return c.json({
ref,
name,
contentType,
size: bytes.length,
...(got.text === undefined ? {} : { text: got.text }),
note: got.note,
});
return c.json(await storeUpload(container.db, { name, contentType, bytes, uploadedBy: staffOf(c)?.id ?? null }));
});
app.get(downloadFileRoute.path, async (c) => {
const rows = await container.db.execute<{ name: string; content_type: string; bytes: Buffer }>(
sql`select name, content_type, bytes from sys.upload where id = ${c.req.param('id')}`,
);
const hit = rowsOf<{ name: string; content_type: string; bytes: Buffer }>(rows)[0];
const hit = await readUpload(container.db, c.req.param('id'));
if (hit === undefined) {
const body: ApiError = { code: 'not-found', message: '这个文件不在了' };
return c.json(body, 404);
}
return c.json({
name: hit.name,
contentType: hit.content_type,
dataBase64: Buffer.from(hit.bytes).toString('base64'),
});
return c.json({ name: hit.name, contentType: hit.contentType, dataBase64: Buffer.from(hit.bytes).toString('base64') });
});
/* ── 试跑日志:历次试跑的存档,按实例管理 ────────────────────────── */