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:
@@ -137,3 +137,90 @@ test('user-data-space-service supports update and soft delete', async () => {
|
||||
const listed = service.readDatasetRows('tasks', { limit: 10 });
|
||||
assert.equal(listed.rows.length, 0);
|
||||
});
|
||||
|
||||
test('exportDatasetRows supports json and csv output', async () => {
|
||||
const workspaceRoot = createTempWorkspace();
|
||||
const service = createUserDataSpaceService({ workspaceRoot });
|
||||
await service.executeSql(`CREATE TABLE contacts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
phone TEXT
|
||||
);`);
|
||||
await service.upsertDataset({
|
||||
name: 'contacts',
|
||||
table: 'contacts',
|
||||
actions: ['read', 'insert'],
|
||||
columns: {
|
||||
read: ['id', 'name', 'phone'],
|
||||
insert: ['name', 'phone'],
|
||||
},
|
||||
});
|
||||
await service.insertDatasetRow('contacts', { name: '李四', phone: '13900000002' });
|
||||
const json = service.exportDatasetRows('contacts', { format: 'json' });
|
||||
assert.equal(json.rowCount, 1);
|
||||
assert.equal(json.rows[0].name, '李四');
|
||||
const csv = service.exportDatasetRows('contacts', { format: 'csv' });
|
||||
assert.match(csv.content, /name,phone/);
|
||||
assert.match(csv.content, /李四/);
|
||||
});
|
||||
|
||||
test('restoreSoftDeletedRow clears deleted_at', async () => {
|
||||
const workspaceRoot = createTempWorkspace();
|
||||
const service = createUserDataSpaceService({ workspaceRoot });
|
||||
await service.executeSql(`CREATE TABLE items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
deleted_at TEXT,
|
||||
deleted_by TEXT
|
||||
);`);
|
||||
await service.upsertDataset({
|
||||
name: 'items',
|
||||
table: 'items',
|
||||
actions: ['read', 'insert', 'soft_delete'],
|
||||
columns: {
|
||||
read: ['id', 'title', 'deleted_at'],
|
||||
insert: ['title'],
|
||||
soft_delete: ['id'],
|
||||
},
|
||||
});
|
||||
const inserted = await service.insertDatasetRow('items', { title: '可恢复项' });
|
||||
await service.softDeleteRowForDataset(service.getDataset('items'), inserted.row.id, { deletedBy: 'test' });
|
||||
const hidden = service.readDatasetRows('items', { limit: 10 });
|
||||
assert.equal(hidden.rows.length, 0);
|
||||
const restored = await service.restoreSoftDeletedRow('items', inserted.row.id);
|
||||
assert.equal(restored.restored, true);
|
||||
const visible = service.readDatasetRows('items', { limit: 10 });
|
||||
assert.equal(visible.rows.length, 1);
|
||||
assert.equal(visible.rows[0].title, '可恢复项');
|
||||
});
|
||||
|
||||
test('readRowsForDataset applies rowScope where clause', async () => {
|
||||
const workspaceRoot = createTempWorkspace();
|
||||
const service = createUserDataSpaceService({ workspaceRoot });
|
||||
await service.executeSql(`CREATE TABLE scoped_items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
created_by_user_id TEXT
|
||||
);`);
|
||||
await service.upsertDataset({
|
||||
name: 'scoped_items',
|
||||
table: 'scoped_items',
|
||||
actions: ['read', 'insert'],
|
||||
columns: {
|
||||
read: ['id', 'title', 'created_by_user_id'],
|
||||
insert: ['title'],
|
||||
},
|
||||
});
|
||||
await service.insertRowForDataset(service.getDataset('scoped_items'), { title: 'A' }, {
|
||||
rowScope: { ownerColumn: 'created_by_user_id', visitorUserId: 'user-a' },
|
||||
});
|
||||
await service.insertRowForDataset(service.getDataset('scoped_items'), { title: 'B' }, {
|
||||
rowScope: { ownerColumn: 'created_by_user_id', visitorUserId: 'user-b' },
|
||||
});
|
||||
const scoped = service.readRowsForDataset(service.getDataset('scoped_items'), {
|
||||
limit: 10,
|
||||
rowScope: { whereClause: `"created_by_user_id" = 'user-a'` },
|
||||
});
|
||||
assert.equal(scoped.rows.length, 1);
|
||||
assert.equal(scoped.rows[0].title, 'A');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user