Procházet zdrojové kódy

chore: audit blocked forum relationships

彭峰 před 2 měsíci
rodič
revize
65c952e1b8

+ 2 - 1
docs/migration/admin-migration-status.md

@@ -28,8 +28,9 @@
 - 旧 `Guest/MsgList/ReplyGuest/MsgShow/Msg_API` 已迁为 `saveGuestbook/guestbookReply/guestbookBatch`:留言列表支持帐套分页、标题搜索和每批 1–100 条审核/取消审核/回收/恢复;详情可白名单编辑标题与正文,管理员回复继承原分类和父 `gid`,再以事务锁原子分配新 `gid/sourceKey`。永久删除会拒绝仍有回复的留言;普通留言新增继续只走已上线 H5 留言云函数。`gid/parentid/cateid/userid/status/sourceKey/gdate/ip` 均通用只读。
 - 旧 `Guest/Bar` 共用的分类新增、编辑、推荐与删除已迁为 `saveGuestCategory/guestCategoryBatch`:新增分类用事务锁原子分配 `cateid/orderId/sourceKey`,`gtype=0` 留言分类固定顶级,`gtype=1` 贴吧分类支持父子结构并阻断循环;同父同类型名称防重,删除前同时检查下级分类、留言与帖子引用。贴吧发帖频率仍依赖旧 `GuestConfig` 文件,版块清空与用户级贴吧授权也尚未开放,因此对应动作保守标为部分实现。
 - 旧 `Bar/TieList/TieDetail/TieMove/Tie_API` 的帖子工作流已迁为 `barBatch`:每批 1–100 条支持审核/取消审核、回收/恢复、全局/版块置顶、取消排序、沉底、精华/取消精华和移动贴吧分类;移动会验证目标为同帐套 `gtype=1` 分类。`GuestBar` 在通用资源中改为结构只读,避免直接篡改帖子 ID、作者、父回复、状态、排序和精华标记。旧“勋章”动作依赖未迁移奖章关系表,继续保守标为部分实现而不伪造成功。
+- 旧 `BarAuthSet/BarAuthSet_Submit` 经逐浪官方源码模型复核,依赖独立 `ZL_Guest_BarAuth` 用户版块关系(`Uid/BarID/Look/Send/Reply`),并非仅靠 `Guestcate.needLog/postAuth` 即可等价实现;目标 109 个 Parse Schema 中只有 `GuestBar/Guestcate`,没有该权限关系或 `ZL_Guest_Medals` 勋章关系。管理员网关新增 `barAuthorization/barMedal` 显式 501 响应,避免界面或调用方误以为保存成功,并将两项授权动作列入最终数据阻塞清单。
 - `_User`、`CommonModel`、`Node` 与 `Special` 表格已补齐当前页全选和批量工具栏,可直接批量停用/解锁/移动用户组,审核、回收、恢复、移动内容节点,回收、恢复、移动栏目,以及移动专题;翻页或重新查询会清空选择,避免误操作隐藏页记录。
-- 迁移状态页已接入从反编译基线自动生成的 1,042 项旧后台动作台账,可按模块、action、原因与状态搜索筛选;当前保守登记为 36 项已实现、30 项部分实现、112 项数据阻塞、189 项基础设施阻塞、675 项待逐项核验。生成器和 `admin-coverage:check` 会阻止台账与源码基线漂移。
+- 迁移状态页已接入从反编译基线自动生成的 1,042 项旧后台动作台账,可按模块、action、原因与状态搜索筛选;当前保守登记为 36 项已实现、30 项部分实现、114 项数据阻塞、189 项基础设施阻塞、673 项待逐项核验。生成器和 `admin-coverage:check` 会阻止台账与源码基线漂移。
 - Parse 登录只在浏览器保存当前 `sessionToken`;`masterKey` 只在部署进程中使用。
 - 云函数网关强制校验管理员身份、`company` 帐套、类白名单、字段白名单和敏感字段过滤。
 - `_Session` 不开放,`Function` 源码不开放通用查询或编辑;用户密码只允许专用重置操作。

+ 6 - 0
scripts/deploy-admin-functions.mjs

@@ -678,6 +678,12 @@ async function handler(request, response) {
       for (const target of targets) await audit({ ...context, company: target.get('company') || context.company }, 'guest-category-' + action, 'Guestcate', target.id);
       return response.json({ success: true, data: { action, updated: targets.length, results: action === 'delete' ? [] : targets.map(serializeObject) } });
     }
+    if (operation === 'barAuthorization') {
+      fail(501, 'migration_blocked: 目标 Schema 缺少旧 ZL_Guest_BarAuth 用户版块权限关系(Uid、BarID、Look、Send、Reply),无法安全读取或保存逐用户浏览、发帖和回复权限');
+    }
+    if (operation === 'barMedal') {
+      fail(501, 'migration_blocked: 目标 Schema 缺少旧 ZL_Guest_Medals 勋章及用户帖子授予关系,无法安全授予或撤销帖子勋章');
+    }
     if (operation === 'barBatch') {
       const action = String(input.action || '');
       const actions = ['audit','unaudit','recycle','recover','move','top-global','top-section','top-undo','elite','elite-undo','bottom','bottom-undo'];

+ 2 - 0
scripts/generate-admin-coverage.mjs

@@ -76,6 +76,8 @@ const explicit = new Map([
   ['Content / Bar/CateAdd', ['partial', '已提供贴吧分类表单;旧版发帖频率文件配置尚未迁移']],
   ['Content / Bar/CateAdd_Submit', ['partial', '已覆盖原子编号、防重、父子循环和分类元数据;文件配置尚缺']],
   ['Content / Bar/Cate_API', ['partial', '已覆盖删除引用保护及推荐/取消推荐;清空版块帖子尚未开放']],
+  ['Content / Bar/BarAuthSet', ['blocked-data', '目标 Schema 缺少 ZL_Guest_BarAuth 用户版块权限关系,无法读取逐用户浏览、发帖和回复权限']],
+  ['Content / Bar/BarAuthSet_Submit', ['blocked-data', '目标 Schema 缺少 Uid、BarID、Look、Send、Reply 权限关系,无法安全保存旧授权']],
   ['Content / Bar/TieList', ['implemented', '已提供帐套帖子列表、标题搜索、分页与批量工作流']],
   ['Content / Bar/TieDetail', ['implemented', '已由只读帖子详情抽屉和专用工作流面板替代']],
   ['Content / Bar/TieMove', ['implemented', '已由 barBatch 目标贴吧分类校验与移动替代']],

+ 4 - 0
scripts/smoke-admin-functions.mjs

@@ -282,6 +282,10 @@ try {
   if (!temporaryGuestCategoryObjectIds[1] || !childCateid || Number(childCategory.parentId) !== rootCateid) throw new Error('贴吧子分类创建失败');
   const guestCategory = await callFunction('xiaoshu/admin/gateway', login.sessionToken, { operation: 'saveGuestCategory', className: 'Guestcate', gtype: 0, parentId: 0, fields: { catename: `冒烟留言分类-${categorySuffix}`, status: 1 } });
   temporaryGuestCategoryObjectIds.push(String(guestCategory.objectId || ''));
+  const barAuthorizationBlocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'barAuthorization', cateId: rootCateid }, 501);
+  if (!String(barAuthorizationBlocked.message).includes('ZL_Guest_BarAuth')) throw new Error('贴吧逐用户权限数据阻塞未返回明确原因');
+  const barMedalBlocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'barMedal', objectId: 'missing-post' }, 501);
+  if (!String(barMedalBlocked.message).includes('ZL_Guest_Medals')) throw new Error('贴吧勋章数据阻塞未返回明确原因');
   const temporaryBar = await jsonRequest(`${PARSE_URL}/classes/GuestBar`, { method: 'POST', body: JSON.stringify({ sourceKey: `cloud:smoke-bar:${categorySuffix}`, company, cateId: rootCateid, pid: 0, title: '后台贴吧工作流冒烟', msgContent: '冒烟正文', status: 0, orderFlag: 0, postFlag: '', cdate: { __type: 'Date', iso: new Date().toISOString() } }) }, true);
   temporaryGuestBarId = String(temporaryBar.objectId || '');
   const barAudited = await callFunction('xiaoshu/admin/gateway', login.sessionToken, { operation: 'barBatch', className: 'GuestBar', action: 'audit', objectIds: [temporaryGuestBarId] });

+ 2 - 0
src/app/admin/legacy-admin-coverage.generated.spec.ts

@@ -19,6 +19,8 @@ describe('legacy admin coverage inventory', () => {
     expect(byKey.get('Content / Guest/Msg_API')?.status).toBe('implemented');
     expect(byKey.get('Content / Guest/CateAdd_Submit')?.status).toBe('implemented');
     expect(byKey.get('Content / Bar/Cate_API')?.status).toBe('partial');
+    expect(byKey.get('Content / Bar/BarAuthSet')?.status).toBe('blocked-data');
+    expect(byKey.get('Content / Bar/BarAuthSet_Submit')?.status).toBe('blocked-data');
     expect(byKey.get('Content / Bar/TieMove')?.status).toBe('implemented');
     expect(byKey.get('User/User_API')?.status).toBe('partial');
     expect(byKey.get('Order/OrderList')?.status).toBe('blocked-data');

+ 4 - 4
src/app/admin/legacy-admin-coverage.generated.ts

@@ -419,15 +419,15 @@ export const LEGACY_ADMIN_COVERAGE: readonly LegacyAdminCoverageEntry[] = [
     "module": "Content / Bar",
     "action": "BarAuthSet",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标 Schema 缺少 ZL_Guest_BarAuth 用户版块权限关系,无法读取逐用户浏览、发帖和回复权限"
   },
   {
     "module": "Content / Bar",
     "action": "BarAuthSet_Submit",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标 Schema 缺少 Uid、BarID、Look、Send、Reply 权限关系,无法安全保存旧授权"
   },
   {
     "module": "Content / Bar",