diff --git a/app/server/db.mjs b/app/server/db.mjs index 2ede27e..53e8562 100644 --- a/app/server/db.mjs +++ b/app/server/db.mjs @@ -733,6 +733,14 @@ function listFootprints(d, query) { d.prepare("INSERT INTO footprint_fts(footprint_fts, rowid, search_text) SELECT 'delete', id, search_text FROM footprint WHERE id=?") .run(payload.id); softDelete(d, 'footprint', payload.id); + // 级联清理附件:墓碑(同步到其他端)+ 磁盘原图/缩略图 + const atts = d.prepare("SELECT * FROM attachment WHERE owner_type='footprint' AND owner_id=? AND deleted_at IS NULL").all(payload.id); + for (const a of atts) { + softDelete(d, 'attachment', a.id); + for (const rel of [a.rel_path, a.thumb_rel_path]) { + if (rel) { try { fs.unlinkSync(path.join(DATA_DIR, rel)); } catch {} } + } + } }); return { ok: true }; } diff --git a/scripts/test-integration.mjs b/scripts/test-integration.mjs index 064882f..3515544 100644 --- a/scripts/test-integration.mjs +++ b/scripts/test-integration.mjs @@ -70,6 +70,11 @@ const attThumb = await api(A, 'POST', '/api/attachment_thumb', { id: attAdd.body ok(attThumb.body?.data?.data_b64 === Buffer.from('THUMBWEBPDATA').toString('base64'), 'attachment_thumb 返回缩略图内容', attThumb.body); const attRead2 = await api(A, 'POST', '/api/attachment_read', { id: attAdd.body?.data?.item?.id }, T); ok(attRead2.body?.data?.data_b64 === Buffer.from('ORIGDATA').toString('base64'), 'attachment_read 原图不受影响', attRead2.body); +// 删除整条记录 → 附件级联清理 +const fpDel = await api(A, 'POST', '/api/footprint_delete', { id: fpId }, T); +ok(fpDel.body?.ok === true, '删除整条记录'); +const attAfter = await api(A, 'POST', '/api/attachment_list', { owner_id: fpId }, T); +ok((attAfter.body?.data?.items || []).length === 0, '记录删除后附件级联清空', attAfter.body); // 邀请码:增 const c1 = await api(A, 'POST', '/admin/invites/create', { code: 'xiong-friend-2025', remark: '给学弟的码' }, T); @@ -111,7 +116,7 @@ const login2 = await api(B, 'POST', '/auth/login', { username: 'demo', password: ok(login2.status === 200, '重启后 demo 可登录'); const T2 = login2.body.data.token; const fp2 = await api(B, 'POST', '/api/footprint_list', { query: { page: 1, page_size: 50 } }, T2); -ok(fp2.body?.data?.total === 17, '重启后仍是 17 条(无重复播种)', fp2.body?.data?.total); +ok(fp2.body?.data?.total === 16, '重启后仍是 16 条(测试足迹已被级联删除)', fp2.body?.data?.total); const list2 = (await api(B, 'GET', '/admin/invites', undefined, T2)).body.data.invites; ok(list2.some((i) => i.code === 'XIONG-FRIEND-2025'), '邀请码跨重启持久化');