feat(page-data): complete Phase 4-5, ops UI, and publish integration
Add visitor roles, row-level scope, owner ops APIs, MySQL policy index, Turnstile captcha, browser client SDK, publish-panel dataset binding, acceptance tests, and usage documentation. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -18,8 +18,9 @@ import { createScheduleService } from './schedule-service.mjs';
|
||||
import { resolveScheduleTimestamp } from './schedule-time.mjs';
|
||||
import { renderLongImage } from './mindspace-long-image.mjs';
|
||||
import { createUserDataSpaceService } from './user-data-space-service.mjs';
|
||||
import { writePageAccessPolicy } from './page-data-policy-store.mjs';
|
||||
import { normalizePageAccessPolicy } from './page-access-policy.mjs';
|
||||
import { writePageAccessPolicy, readPageAccessPolicy } from './page-data-policy-store.mjs';
|
||||
import { closePolicyDataset, normalizePageAccessPolicy } from './page-access-policy.mjs';
|
||||
import { upsertPageDataPolicyIndex } from './page-data-policy-index.mjs';
|
||||
|
||||
const SANDBOX_ROOT = process.argv[2]?.trim() || process.env.SANDBOX_ROOT?.trim();
|
||||
if (!SANDBOX_ROOT) {
|
||||
@@ -287,12 +288,38 @@ const ALL_TOOLS = [
|
||||
datasets: {
|
||||
type: 'object',
|
||||
description:
|
||||
'dataset 授权,例如 { registrations: { insert: true, columns: { insert: ["name","phone"] } } }',
|
||||
'dataset 授权,例如 { registrations: { insert: true, columns: { insert: ["name","phone"] }, rowPolicy: { scope: "own_rows" } } }',
|
||||
},
|
||||
defaultVisitorRole: {
|
||||
type: 'string',
|
||||
description: 'login_required 模式下未列名访问者的默认角色:deny、viewer、editor',
|
||||
},
|
||||
visitors: {
|
||||
type: 'object',
|
||||
description: '访问者角色映射,例如 { "user-2": "editor", "user-3": "viewer" }',
|
||||
},
|
||||
roles: {
|
||||
type: 'object',
|
||||
description: '可选角色权限覆盖,例如 { viewer: { read: true, insert: false } }',
|
||||
},
|
||||
},
|
||||
required: ['pageId', 'accessMode', 'datasets'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'private_data_close_page_dataset',
|
||||
description:
|
||||
'关闭已发布页面的某个 dataset 公开访问能力。写入策略文件并将 read/insert/update/delete 全部关闭。',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pageId: { type: 'string', description: '页面 ID' },
|
||||
dataset: { type: 'string', description: '要关闭的 dataset 名称' },
|
||||
ownerUserId: { type: 'string', description: '页面 owner 用户 ID,可选,默认当前会话用户' },
|
||||
},
|
||||
required: ['pageId', 'dataset'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
let quotaPool = null;
|
||||
@@ -554,12 +581,31 @@ async function callTool(name, args) {
|
||||
ownerUserId,
|
||||
accessMode: args.accessMode,
|
||||
datasets: args.datasets,
|
||||
defaultVisitorRole: args.defaultVisitorRole,
|
||||
visitors: args.visitors,
|
||||
roles: args.roles,
|
||||
},
|
||||
{ fallbackPageId: args.pageId, fallbackOwnerUserId: ownerUserId },
|
||||
);
|
||||
const saved = writePageAccessPolicy(SANDBOX, policy);
|
||||
if (isQuotaSyncConfigured()) {
|
||||
await upsertPageDataPolicyIndex(getQuotaPool(), saved).catch(() => null);
|
||||
}
|
||||
return [{ type: 'text', text: JSON.stringify(saved, null, 2) }];
|
||||
}
|
||||
case 'private_data_close_page_dataset': {
|
||||
const ownerUserId = String(args.ownerUserId ?? PRIVATE_DATA_USER_ID ?? '').trim();
|
||||
if (!ownerUserId) throw new Error('缺少 ownerUserId,无法关闭 dataset');
|
||||
const policy = readPageAccessPolicy(SANDBOX, args.pageId);
|
||||
if (!policy) throw new Error('页面数据策略不存在');
|
||||
if (policy.ownerUserId !== ownerUserId) throw new Error('无权关闭该页面 dataset');
|
||||
const closed = closePolicyDataset(policy, args.dataset);
|
||||
const saved = writePageAccessPolicy(SANDBOX, closed);
|
||||
if (isQuotaSyncConfigured()) {
|
||||
await upsertPageDataPolicyIndex(getQuotaPool(), saved).catch(() => null);
|
||||
}
|
||||
return [{ type: 'text', text: JSON.stringify(saved.datasets[args.dataset], null, 2) }];
|
||||
}
|
||||
case 'schedule_create_item': {
|
||||
const timezone = args.timezone ?? process.env.H5_DEFAULT_TIMEZONE ?? 'Asia/Shanghai';
|
||||
const startAt = resolveScheduleTimestamp({
|
||||
|
||||
Reference in New Issue
Block a user