#!/usr/bin/env node /** * 修复 john 用户「每日待办登记」Page Data 交付: * - 前台页改为 public + insertRow * - 后台页 password + listRows/updateRow/deleteRow * * Usage: node scripts/repair-daily-todo-local.mjs */ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { createDbPool } from '../db.mjs'; import { bindWorkspaceHtmlForPageData } from '../page-data-workspace-bind.mjs'; import { resolveMindSpaceStorageRoot } from '../mindspace-runtime-config.mjs'; import { readPageAccessPolicy, writePageAccessPolicy } from '../page-data-policy-store.mjs'; import { resolvePageDataDeliveryBaseUrl } from '../user-publish.mjs'; const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); function loadEnvFile(filePath) { if (!fs.existsSync(filePath)) return; for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const eq = trimmed.indexOf('='); if (eq < 0) continue; const key = trimmed.slice(0, eq).trim(); const value = trimmed.slice(eq + 1).trim(); if (!process.env[key]) process.env[key] = value; } } loadEnvFile(path.join(root, '.env')); loadEnvFile(path.join(root, '../../.env.local')); const JOHN_USER_ID = '1c99b83b-0454-474f-a5d2-129d34506a32'; const WORKSPACE_ROOT = path.join(root, 'MindSpace', JOHN_USER_ID); const ADMIN_PASSWORD = 'adminadmin'; const DATASET = 'daily_todo'; const LOCAL_BASE = process.env.MEMIND_PAGE_DATA_DELIVERY_BASE_URL || resolvePageDataDeliveryBaseUrl(); const FORM_POLICY = { accessMode: 'public', datasets: { [DATASET]: { insert: true, read: false, columns: { insert: ['name', 'date', 'todo_items', 'priority', 'status', 'remark'], }, }, }, }; const ADMIN_POLICY = { accessMode: 'password', datasets: { [DATASET]: { insert: false, read: true, update: true, softDelete: true, columns: { read: ['id', 'name', 'date', 'todo_items', 'priority', 'status', 'remark', 'created_at', 'updated_at'], update: ['name', 'date', 'todo_items', 'priority', 'status', 'remark', 'updated_at'], soft_delete: ['id'], }, }, }, }; async function bindPages(pool, h5Root, storageRoot) { const pages = [ { relativePath: 'public/daily-todo.html', accessMode: 'public', password: null, policy: FORM_POLICY, }, { relativePath: 'public/daily-todo-admin.html', accessMode: 'password', password: ADMIN_PASSWORD, policy: ADMIN_POLICY, }, ]; const bound = []; for (const page of pages) { const result = await bindWorkspaceHtmlForPageData({ pool, h5Root, storageRoot, userId: JOHN_USER_ID, workspaceRoot: WORKSPACE_ROOT, relativePath: page.relativePath, accessMode: page.accessMode, password: page.password, pageDataPolicy: page.policy, }); bound.push({ relativePath: page.relativePath, pageId: result.pageId, workspaceUrl: result.workspaceUrl, publicationAccessMode: result.publicationAccessMode, }); } return bound; } function patchAdminPolicySoftDelete(pageId) { const policy = readPageAccessPolicy(WORKSPACE_ROOT, pageId); if (!policy?.datasets?.[DATASET]) return policy; policy.datasets[DATASET].update = true; policy.datasets[DATASET].softDelete = true; policy.datasets[DATASET].hardDelete = false; policy.datasets[DATASET].columns.update = ADMIN_POLICY.datasets[DATASET].columns.update; return writePageAccessPolicy(WORKSPACE_ROOT, policy); } async function smokeInsert(pageId) { const res = await fetch(`${LOCAL_BASE}/api/public/pages/${pageId}/data/${DATASET}/rows`, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ name: '本地验证', date: '2026-07-13', todo_items: '测试待办', priority: '普通', status: '待完成', remark: 'repair script', }), }); const body = await res.json().catch(() => ({})); return { status: res.status, body }; } async function smokeAdminList(pageId) { const authRes = await fetch(`${LOCAL_BASE}/api/public/pages/${pageId}/data-auth`, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ password: ADMIN_PASSWORD }), }); const authBody = await authRes.json().catch(() => ({})); const token = authBody?.data?.token; if (!token) return { status: authRes.status, body: authBody, listStatus: 0 }; const listRes = await fetch(`${LOCAL_BASE}/api/public/pages/${pageId}/data/${DATASET}?limit=5`, { headers: { Accept: 'application/json', 'x-page-data-token': token }, }); const listBody = await listRes.json().catch(() => ({})); return { status: authRes.status, listStatus: listRes.status, rows: listBody?.data?.rows?.length ?? 0 }; } const pool = createDbPool(); const h5Root = root; const storageRoot = resolveMindSpaceStorageRoot(h5Root); console.log('=== 每日待办登记 · 本地修复 ===\n'); const bound = await bindPages(pool, h5Root, storageRoot); console.log('✔ 页面已重新绑定\n'); for (const item of bound) { console.log(` ${item.relativePath}`); console.log(` pageId: ${item.pageId}`); console.log(` access: ${item.publicationAccessMode}`); console.log(` url: ${item.workspaceUrl}\n`); } const adminPageId = bound.find((item) => item.relativePath === 'public/daily-todo-admin.html')?.pageId; if (adminPageId) { patchAdminPolicySoftDelete(adminPageId); console.log('✔ 后台策略已启用 update + softDelete\n'); } const registerPageId = bound.find((item) => item.relativePath === 'public/daily-todo.html')?.pageId; const insert = registerPageId ? await smokeInsert(registerPageId) : { status: 0 }; const admin = adminPageId ? await smokeAdminList(adminPageId) : { listStatus: 0 }; console.log('【insert 冒烟】', JSON.stringify(insert, null, 2)); console.log('【admin 冒烟】', JSON.stringify(admin, null, 2)); console.log('\n=== 正确访问链接(Portal 8081)==='); for (const item of bound) { console.log(` ${item.relativePath}: ${item.workspaceUrl}`); } console.log(` 后台管理密码: ${ADMIN_PASSWORD}`); console.log('\n注意:Agent 交付链接必须用 Portal(8081),不要用 Vite UI(5173)。'); const passed = insert.status === 201 && admin.listStatus === 200; console.log(`\n${passed ? '✔ 修复完成,链路可用' : '✘ 仍有问题,请检查上方输出'}`); await pool.end(); process.exit(passed ? 0 : 1);