feat(media-supply): persist favorites with multi-group membership

Move media favorites from client state to PostgreSQL with server-side
search and pagination.

- Store favorites keyed by (tenant, user, group, resource) so a resource
  can belong to multiple groups; the 500-resource cap counts distinct
  resources, not memberships.
- Add favorite-group CRUD and group/global removal routes, with silent
  cleanup of delisted or invisible resources on read.
- Serve favorite resource details server-side, 10 per page, with name/ID
  search; favorites-page removal is group-scoped, resources-page removal
  is global.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 20:10:50 +08:00
parent 0bf4b73ebc
commit 67a6cbcd9e
15 changed files with 2378 additions and 518 deletions
+21 -14
View File
@@ -226,20 +226,27 @@ var routeDocs = map[string]routeDoc{
"GET /api/tenant/desktop-client/releases/downloadable": {"可下载桌面客户端版本", "返回当前租户可下载的已启用桌面客户端版本列表。"},
// --- Tenant:媒介供应 ---
"GET /api/tenant/media-supply/resources": {"媒介资源列表", "查询当前 Workspace 可购买的媒介供应资源。"},
"GET /api/tenant/media-supply/resources/by-ids": {"按 ID 查询媒介资源", "按资源 ID 集合查询媒介供应资源详情。"},
"GET /api/tenant/media-supply/resources/search-options": {"媒介资源筛选项", "返回媒介供应资源列表的筛选条件与可选项。"},
"PUT /api/tenant/media-supply/resources/:id/price": {"调整媒介资源售价", "租户侧调整指定媒介资源的销售价格。"},
"POST /api/tenant/media-supply/sync-jobs": {"同步媒介资源", "创建媒介供应资源同步任务。"},
"POST /api/tenant/media-supply/orders": {"创建媒介投放订单", "为选定媒介资源创建投放订单。"},
"GET /api/tenant/media-supply/orders": {"媒介订单列表", "分页查询当前 Workspace 的媒介投放订单。"},
"GET /api/tenant/media-supply/orders/:id": {"媒介订单详情", "读取指定媒介投放订单详情。"},
"GET /api/tenant/media-supply/wallet": {"媒介钱包状态", "读取当前 Workspace 的媒介供应钱包余额与状态。"},
"GET /api/tenant/media-supply/wallet/ledgers": {"媒介钱包流水", "分页查询媒介供应钱包收支流水。"},
"POST /api/tenant/media-supply/wallet/adjustments": {"调整媒介钱包", "创建媒介钱包余额调整记录。"},
"GET /api/tenant/media-supply/supplier-session": {"供应商会话状态", "查询媒介供应商登录会话状态。"},
"PUT /api/tenant/media-supply/supplier-session": {"导入供应商会话", "导入或更新媒介供应商登录会话。"},
"POST /api/tenant/media-supply/supplier-session/login": {"登录供应商账号", "使用已配置账号登录媒介供应商并刷新会话。"},
"GET /api/tenant/media-supply/resources": {"媒介资源列表", "查询当前 Workspace 可购买的媒介供应资源。"},
"GET /api/tenant/media-supply/resources/by-ids": {"按 ID 查询媒介资源", "按资源 ID 集合查询媒介供应资源详情。"},
"GET /api/tenant/media-supply/resources/search-options": {"媒介资源筛选项", "返回媒介供应资源列表的筛选条件与可选项。"},
"GET /api/tenant/media-supply/favorite-groups": {"常发媒体分组", "读取当前租户用户的数据库收藏分组,并静默清理已下架或不可见的媒体。"},
"POST /api/tenant/media-supply/favorite-groups": {"新建常发媒体分组", "创建用户级常发媒体分组;可在同一事务内将指定媒体加入新分组。"},
"DELETE /api/tenant/media-supply/favorite-groups/:group_id": {"删除常发媒体分组", "删除指定用户级常发媒体分组及其收藏关系,默认分组不可删除。"},
"GET /api/tenant/media-supply/favorite-groups/:group_id/resources": {"常发媒体分页", "按分组分页查询当前用户的常发媒体详情,支持媒体名称或 ID 搜索,每页固定 10 条。"},
"PUT /api/tenant/media-supply/favorite-groups/:group_id/resources/:resource_id": {"加入常发媒体分组", "幂等地将指定媒体加入目标分组,不影响该媒体在其他分组中的成员关系。"},
"DELETE /api/tenant/media-supply/favorite-groups/:group_id/resources/:resource_id": {"移出常发媒体分组", "幂等地移除指定媒体在目标分组中的成员关系,保留其他分组中的收藏。"},
"DELETE /api/tenant/media-supply/favorite-resources/:resource_id": {"全局移除常发媒体", "幂等地移除当前租户用户对指定媒体的全部分组成员关系。"},
"PUT /api/tenant/media-supply/resources/:id/price": {"调整媒介资源售价", "租户侧调整指定媒介资源的销售价格。"},
"POST /api/tenant/media-supply/sync-jobs": {"同步媒介资源", "创建媒介供应资源同步任务。"},
"POST /api/tenant/media-supply/orders": {"创建媒介投放订单", "为选定媒介资源创建投放订单。"},
"GET /api/tenant/media-supply/orders": {"媒介订单列表", "分页查询当前 Workspace 的媒介投放订单。"},
"GET /api/tenant/media-supply/orders/:id": {"媒介订单详情", "读取指定媒介投放订单详情。"},
"GET /api/tenant/media-supply/wallet": {"媒介钱包状态", "读取当前 Workspace 的媒介供应钱包余额与状态。"},
"GET /api/tenant/media-supply/wallet/ledgers": {"媒介钱包流水", "分页查询媒介供应钱包收支流水。"},
"POST /api/tenant/media-supply/wallet/adjustments": {"调整媒介钱包", "创建媒介钱包余额调整记录。"},
"GET /api/tenant/media-supply/supplier-session": {"供应商会话状态", "查询媒介供应商登录会话状态。"},
"PUT /api/tenant/media-supply/supplier-session": {"导入供应商会话", "导入或更新媒介供应商登录会话。"},
"POST /api/tenant/media-supply/supplier-session/login": {"登录供应商账号", "使用已配置账号登录媒介供应商并刷新会话。"},
// --- Tenant:内容合规 ---
"GET /api/tenant/compliance/runtime-status": {"合规运行状态", "返回当前租户内容合规模块是否启用、执行模式、词库版本与检测预算。"},