804f13bc04
Reject /api/page-data/ legacy passthrough, verify live API before send, and resolve delivery links to Portal (8081) instead of Vite (5173). Add local repair/verify scripts for daily-register Page Data flow. Co-authored-by: Cursor <cursoragent@cursor.com>
196 lines
6.6 KiB
JavaScript
196 lines
6.6 KiB
JavaScript
#!/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);
|