Files
memind/scripts/repair-daily-todo-local.mjs
T
john 804f13bc04 fix(page-data): harden structural delivery and Portal base URL
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>
2026-07-13 09:35:40 +08:00

196 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 交付链接必须用 Portal8081),不要用 Vite UI5173)。');
const passed = insert.status === 201 && admin.listStatus === 200;
console.log(`\n${passed ? '✔ 修复完成,链路可用' : '✘ 仍有问题,请检查上方输出'}`);
await pool.end();
process.exit(passed ? 0 : 1);