feat(albums): soft-delete albums (admin+owner, hidden & irreversible, both app entries)
契约 deleteAlbum(DELETE /albums/{albumId} → 204 幂等)+ status enum 'deleted':
- 服务端:softDelete(canView 404→canManage 403 防枚举;乐观锁 claim +
outbox album.updated priority=high + audit album.delete);列表/详情/更新/
成员/资产/分类/水印/照片/展示位绑定全部 deleted→404;公开端 cn
(status!=='active')与 ov(isAlbumVisibleOv)天然屏蔽;ov 副本经
album.updated 载荷 status 直投自动下线
- Admin:列表表格操作「删除」+ 详情页头「删除相册」(确认含不可恢复说明)
- App:操作面板「删除相册」(红字、二次确认、成功退回列表并刷新)
- 验证:API 56/56、App 397/397(analyze 0)、admin typecheck/lint/test/build 绿
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -624,4 +624,45 @@ describe('UI-01 契约扩展:cover / preview / scope=own (e2e)', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('cover 回退 banner(宣传图/封面共用:仅 banner 相册 → cover=banner 对象)', async () => {
|
||||
// 直种仅 banner 相册(复用本文件资产直种模式)。
|
||||
const album = await prisma.albums.create({
|
||||
data: {
|
||||
publicToken: makePublicToken(),
|
||||
ownerId: 'u-ui01-coverfb',
|
||||
startAt: new Date('2026-10-03T02:00:00.000Z'),
|
||||
endAt: new Date('2026-10-03T12:00:00.000Z'),
|
||||
defaultLocale: 'zh-CN',
|
||||
},
|
||||
});
|
||||
await prisma.album_translations.create({
|
||||
data: { albumId: album.id, locale: 'zh-CN', title: '仅宣传图相册' },
|
||||
});
|
||||
const key = `photo-live/dev/albums/${album.id}/assets/u01assetbannerfb00000000.jpg`;
|
||||
await putObject(key, Buffer.from([0x51, 0x52, 0x53]));
|
||||
await prisma.album_assets.create({
|
||||
data: {
|
||||
id: 'u01assetbannerfb00000000',
|
||||
albumId: album.id,
|
||||
kind: 'banner',
|
||||
locale: null,
|
||||
objectKey: key,
|
||||
state: 'effective',
|
||||
expectedBytes: 3,
|
||||
contentType: 'image/jpeg',
|
||||
},
|
||||
});
|
||||
|
||||
const res = await listAlbums(adminToken);
|
||||
expect(res.status).toBe(200);
|
||||
const item = (res.body.items as Array<{ id: string; cover: { url: string } | null }>).find(
|
||||
(row) => row.id === album.id,
|
||||
);
|
||||
expect(item).toBeDefined();
|
||||
expect(item?.cover).not.toBeNull();
|
||||
const fetched = await fetch(item!.cover!.url);
|
||||
expect(fetched.status).toBe(200);
|
||||
expect(Buffer.from(await fetched.arrayBuffer()).length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user