feat: Page Data dev loop, adm Code Run UI, and customer order demo

Add verify→Aider→OpenHands dev loop, memindadm config/history UI, and john4
customer-order Page Data demo with scenario + verification scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-23 22:11:18 +08:00
parent 239c41f935
commit dab6140f99
22 changed files with 1795 additions and 14 deletions
+155
View File
@@ -0,0 +1,155 @@
#!/usr/bin/env node
/**
* 验证 john4 客户下单系统 Page Data 链路(insert + admin read)。
*/
import { loadH5Environment } from './load-env.mjs';
import {
createReporter,
loginViaApi,
resolvePortalBase,
} from './scenario-test-lib.mjs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { PUBLISH_ROOT_DIR } from '../user-publish.mjs';
import fs from 'node:fs/promises';
const repoRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
const USER_ID = process.env.CUSTOMER_ORDER_USER_ID ?? '32035858-9a20-425b-89da-c118ef0779aa';
const DATASET = 'customer_orders';
const FORM_PAGE = 'customer-order-form.html';
const ADMIN_PAGE = 'customer-order-admin.html';
loadH5Environment(import.meta.dirname);
async function findPageIdFromPolicy(publishKey) {
const policyDir = path.join(repoRoot, PUBLISH_ROOT_DIR, publishKey, '.mindspace', 'page-data-policies');
const entries = await fs.readdir(policyDir);
for (const name of entries.filter((item) => item.endsWith('.json'))) {
const raw = await fs.readFile(path.join(policyDir, name), 'utf8');
const policy = JSON.parse(raw);
if (policy?.datasets?.[DATASET]?.insert) {
return { pageId: policy.pageId, fileName: name };
}
}
return null;
}
async function main() {
const port = Number(process.env.H5_PORT ?? 8081);
const baseUrl = resolvePortalBase(port);
const reporter = createReporter();
console.log('==> 客户下单系统 Page Data 验证');
console.log(` Portal: ${baseUrl}`);
console.log(` 用户: ${USER_ID}\n`);
const auth = await loginViaApi(baseUrl, { username: 'john4', password: '888888' }, reporter);
const publishKey = auth.user?.id ?? USER_ID;
const formUrl = `${baseUrl}/MindSpace/${publishKey}/public/${FORM_PAGE}`;
const adminUrl = `${baseUrl}/MindSpace/${publishKey}/public/${ADMIN_PAGE}`;
for (const [label, url] of [['下单页', formUrl], ['后台页', adminUrl]]) {
const res = await fetch(url, { headers: { Cookie: auth.cookie } });
const html = await res.text();
if (res.status !== 200) {
reporter.fail(`${label} HTTP`, `${res.status}`);
} else {
reporter.pass(`${label} HTTP 200`, url);
}
if (!html.includes('page-data-client.js')) {
reporter.fail(`${label} 脚本`, '缺少 page-data-client.js');
} else {
reporter.pass(`${label} Page Data 客户端`, '已引用');
}
}
const formMeta = await findPageIdFromPolicy(publishKey);
if (!formMeta?.pageId) {
reporter.fail('Page Data policy', '未找到 customer_orders insert policy');
process.exit(reporter.summary());
}
reporter.pass('Page Data policy', `${formMeta.fileName} pageId=${formMeta.pageId}`);
const insertPayload = {
customer_name: '测试客户',
phone: '13800138000',
product_name: '演示商品A',
quantity: '2',
address: '上海市浦东新区测试路 1 号',
remark: '自动化验证',
status: '待处理',
};
const insertRes = await fetch(
`${baseUrl}/api/public/pages/${formMeta.pageId}/data/${DATASET}/rows`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json', Cookie: auth.cookie },
body: JSON.stringify(insertPayload),
},
);
const insertBody = await insertRes.json().catch(() => ({}));
if (!insertRes.ok) {
reporter.fail('下单 insert', `${insertRes.status} ${JSON.stringify(insertBody)}`);
} else {
reporter.pass('下单 insert', `row id=${insertBody?.data?.id ?? insertBody?.id ?? 'ok'}`);
}
const adminMeta = await (async () => {
const policyDir = path.join(repoRoot, PUBLISH_ROOT_DIR, publishKey, '.mindspace', 'page-data-policies');
const entries = await fs.readdir(policyDir);
for (const name of entries.filter((item) => item.endsWith('.json'))) {
const policy = JSON.parse(await fs.readFile(path.join(policyDir, name), 'utf8'));
if (policy?.datasets?.[DATASET]?.read) return policy.pageId;
}
return null;
})();
if (!adminMeta) {
reporter.fail('后台 policy', '未找到 read policy');
} else {
const tokenRes = await fetch(`${baseUrl}/api/public/pages/${adminMeta}/data-auth`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Cookie: auth.cookie },
body: JSON.stringify({ password: '88888888' }),
});
const tokenBody = await tokenRes.json().catch(() => ({}));
const token = tokenBody?.data?.token ?? tokenBody?.token;
if (!tokenRes.ok || !token) {
reporter.fail('后台口令认证', `${tokenRes.status}`);
} else {
const listRes = await fetch(
`${baseUrl}/api/public/pages/${adminMeta}/data/${DATASET}?limit=20`,
{ headers: { 'x-page-data-token': token, Cookie: auth.cookie } },
);
const listBody = await listRes.json().catch(() => ({}));
const rows = listBody?.data?.rows ?? listBody?.rows ?? [];
const hit = rows.some((row) => row.customer_name === insertPayload.customer_name && row.product_name === insertPayload.product_name);
if (!listRes.ok || !hit) {
reporter.fail('后台读订单', `rows=${rows.length}, 未找到测试订单`);
} else {
reporter.pass('后台读订单', `${rows.length} 条,含测试订单`);
}
}
}
const sqlitePath = path.join(repoRoot, PUBLISH_ROOT_DIR, publishKey, '.mindspace', 'private-data.sqlite');
try {
await fs.stat(sqlitePath);
reporter.pass('用户数据空间', 'private-data.sqlite 存在');
} catch {
reporter.pass('用户数据空间', 'PostgreSQL 模式(无本地 sqlite 文件)');
}
console.log('\n--- 访问入口 ---');
console.log(`下单页: ${formUrl}`);
console.log(`后台页: ${adminUrl} (口令 88888888`);
process.exit(reporter.summary());
}
main().catch((err) => {
console.error(err instanceof Error ? err.stack ?? err.message : err);
process.exit(1);
});