fix(page-data): isolate owner API from public pages

This commit is contained in:
john
2026-07-15 10:56:22 +08:00
parent 72fd88219c
commit a07cf07861
15 changed files with 200 additions and 90 deletions
+25 -11
View File
@@ -4,7 +4,7 @@ import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import express from 'express';
import { attachPageDataRoutes } from './page-data-routes.mjs';
import { attachPageDataRoutes, isLegacyPageDataApiPath } from './page-data-routes.mjs';
import { createPageDataService } from './page-data-service.mjs';
import { createUserDataSpaceService } from './user-data-space-service.mjs';
@@ -82,21 +82,21 @@ test('page data routes allow logged-in owner to read and insert dataset rows', a
user: { id: 'user-1', workspaceRoot },
});
const inserted = await requestJson(app, 'POST', '/api/page-data/feedback/rows', {
const inserted = await requestJson(app, 'POST', '/api/admin/page-data/feedback/rows', {
message: '很好用',
});
assert.equal(inserted.status, 201);
assert.equal(inserted.body.data.row.message, '很好用');
const listed = await requestJson(app, 'GET', '/api/page-data/feedback?limit=10');
const listed = await requestJson(app, 'GET', '/api/admin/page-data/feedback?limit=10');
assert.equal(listed.status, 200);
assert.equal(listed.body.data.rows.length, 1);
const schema = await requestJson(app, 'GET', '/api/page-data/feedback/schema');
const schema = await requestJson(app, 'GET', '/api/admin/page-data/feedback/schema');
assert.equal(schema.status, 200);
assert.equal(schema.body.data.dataset.name, 'feedback');
const stats = await requestJson(app, 'GET', '/api/page-data/feedback/stats');
const stats = await requestJson(app, 'GET', '/api/admin/page-data/feedback/stats');
assert.equal(stats.status, 200);
assert.equal(stats.body.data.total, 1);
});
@@ -130,21 +130,21 @@ test('page data routes allow owner update and soft delete', async () => {
user: { id: 'user-1', workspaceRoot },
});
const inserted = await requestJson(app, 'POST', '/api/page-data/tasks/rows', { title: '待办' });
const inserted = await requestJson(app, 'POST', '/api/admin/page-data/tasks/rows', { title: '待办' });
assert.equal(inserted.status, 201);
const rowId = inserted.body.data.row.id;
const updated = await requestJson(app, 'PATCH', `/api/page-data/tasks/rows/${rowId}`, {
const updated = await requestJson(app, 'PATCH', `/api/admin/page-data/tasks/rows/${rowId}`, {
status: 'done',
});
assert.equal(updated.status, 200);
assert.equal(updated.body.data.row.status, 'done');
const deleted = await requestJson(app, 'DELETE', `/api/page-data/tasks/rows/${rowId}`);
const deleted = await requestJson(app, 'DELETE', `/api/admin/page-data/tasks/rows/${rowId}`);
assert.equal(deleted.status, 200);
assert.equal(deleted.body.data.deleted, true);
const listed = await requestJson(app, 'GET', '/api/page-data/tasks?limit=10');
const listed = await requestJson(app, 'GET', '/api/admin/page-data/tasks?limit=10');
assert.equal(listed.status, 200);
assert.equal(listed.body.data.rows.length, 0);
});
@@ -168,11 +168,25 @@ test('page data routes reject unauthorized dataset action', async () => {
user: { id: 'user-1', workspaceRoot },
});
const denied = await requestJson(app, 'GET', '/api/page-data/hidden');
const denied = await requestJson(app, 'GET', '/api/admin/page-data/hidden');
assert.equal(denied.status, 403);
assert.equal(denied.body.error.code, 'action_not_allowed');
});
test('legacy page data API is permanently unavailable', async () => {
const workspaceRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'page-data-api-legacy-'));
const app = createApiApp({ workspaceRoot, user: { id: 'user-1', workspaceRoot } });
const blocked = await requestJson(app, 'POST', '/api/page-data/notes/rows', { content: '旧页面' });
assert.equal(blocked.status, 410);
assert.equal(blocked.body.error.code, 'legacy_page_data_api_removed');
});
test('legacy API path detector does not include the admin namespace', () => {
assert.equal(isLegacyPageDataApiPath('/page-data/notes/rows'), true);
assert.equal(isLegacyPageDataApiPath('/admin/page-data/notes/rows'), false);
});
test('page data routes require login', async () => {
const api = express.Router();
attachPageDataRoutes(api, {
@@ -180,7 +194,7 @@ test('page data routes require login', async () => {
sendError: (res, _req, status, code, message) => res.status(status).json({ error: { code, message } }),
getPageDataService: () => createPageDataService({ resolveWorkspaceRoot: async () => '/tmp' }),
});
const handler = api.stack.find((layer) => layer.route?.path === '/page-data/:dataset')?.route.stack[0].handle;
const handler = api.stack.find((layer) => layer.route?.path === '/admin/page-data/:dataset')?.route.stack[0].handle;
const res = createResponseRecorder();
await handler({ params: { dataset: 'demo' } }, res);
assert.equal(res.statusCode, 401);