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:
浅念
2026-09-23 10:13:08 +08:00
parent 9cb960c27a
commit 8e674a902e
31 changed files with 995 additions and 258 deletions
+53 -5
View File
@@ -129,7 +129,11 @@ export interface paths {
get: operations["getAlbum"];
put?: never;
post?: never;
delete?: never;
/**
* 软删除相册
* @description 标记 status=deleted(软删除):管理端列表与公开端即刻不可见,成员/上传/ 资产等全部子资源随之 404;数据保留(物理删除走独立运维流程),不提供 恢复端点。权限同「配置相册」(公司管理员或相册创建者)。事件走 album.updated(priority=high)同步 ov 副本下线。
*/
delete: operations["deleteAlbum"];
options?: never;
head?: never;
/**
@@ -1340,7 +1344,7 @@ export interface components {
* @default active
* @enum {string}
*/
readonly status: "active" | "offline";
readonly status: "active" | "offline" | "deleted";
defaultLocale: components["schemas"]["Locale"];
/** @description 活动 IANA 时区。 */
timezone: string;
@@ -1389,8 +1393,9 @@ export interface components {
/**
* @description 相册封面摘要(UI-01,readOnly 可选扩展):当前生效封面资产
* (album_assets kind=cover、state=effective 的最新一行)的短期
* 签名 URL;未配置封面为 null。列表端点按页批量组装(非逐相册
* 查询);旧客户端忽略本字段不受影响。
* 签名 URL;未配置封面为 null。宣传图/封面共用(2026-09-23 决策):
* 未配置封面时回退最新生效宣传图(传一张即可两用)。列表端点按页
* 批量组装(非逐相册查询);旧客户端忽略本字段不受影响。
*/
readonly cover?: components["schemas"]["SignedUrl"] | null;
};
@@ -2267,7 +2272,10 @@ export interface components {
translation: components["schemas"]["PublicAlbumTranslation"];
/** @description 分类列表(按 sortOrder 升序)。 */
categories: components["schemas"]["PublicCategory"][];
/** @description 封面图签名 URL;未设置为 null。 */
/**
* @description 宣传图签名 URL;未设置为 null。宣传图/封面共用(2026-09-23 决策):
* 未配置宣传图时回退最新生效封面(传一张即可两用)。
*/
banner: components["schemas"]["SignedUrl"] | null;
/**
* Format: int64
@@ -2922,6 +2930,46 @@ export interface operations {
500: components["responses"]["InternalError"];
};
};
deleteAlbum: {
parameters: {
query?: never;
header?: never;
path: {
/** @description 相册 ID。 */
albumId: components["parameters"]["AlbumId"];
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description 已删除(幂等:重复删除同样 204)。 */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
401: components["responses"]["Unauthorized"];
/** @description 无「配置相册」权限(仅创建者或管理员,AUTH_FORBIDDEN)。 */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Error"];
};
};
/** @description 相册不存在(ALBUM_NOT_FOUND,含已删除)。 */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Error"];
};
};
};
};
updateAlbum: {
parameters: {
query?: never;