feat(page-data): add private and public dataset API (phase 1-3)
Extract UserDataSpaceService for shared SQLite access, wire logged-in Page Data routes, and add public insert plus password-token read/update/delete with policy storage, rate limits, and regression tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import { createUserDataSpaceService } from './user-data-space-service.mjs';
|
||||
|
||||
function mapServiceError(error) {
|
||||
const code = error?.code;
|
||||
if (code === 'dataset_not_found') {
|
||||
return { status: 404, code: 'dataset_not_found', message: '数据集不存在' };
|
||||
}
|
||||
if (code === 'action_not_allowed') {
|
||||
return { status: 403, code: 'action_not_allowed', message: error.message };
|
||||
}
|
||||
if (code === 'columns_not_allowed') {
|
||||
return { status: 403, code: 'columns_not_allowed', message: error.message };
|
||||
}
|
||||
if (code === 'invalid_payload' || code === 'payload_too_large' || code === 'invalid_dataset_config') {
|
||||
return { status: 400, code: code ?? 'invalid_request', message: error.message };
|
||||
}
|
||||
if (code === 'table_not_found') {
|
||||
return { status: 400, code: 'table_not_found', message: '数据集对应表不存在' };
|
||||
}
|
||||
return {
|
||||
status: 400,
|
||||
code: 'page_data_failed',
|
||||
message: error instanceof Error ? error.message : '页面数据操作失败',
|
||||
};
|
||||
}
|
||||
|
||||
export function createPageDataService(deps = {}) {
|
||||
const getUserAuth = deps.getUserAuth ?? (() => null);
|
||||
const getPool = deps.getPool ?? (() => null);
|
||||
const resolveWorkspaceRoot =
|
||||
deps.resolveWorkspaceRoot ??
|
||||
(async (user) => user?.workspaceRoot ?? null);
|
||||
|
||||
async function createServiceForUser(user) {
|
||||
if (!user?.id) {
|
||||
throw Object.assign(new Error('未授权'), { code: 'unauthorized' });
|
||||
}
|
||||
const workspaceRoot = await resolveWorkspaceRoot(user);
|
||||
if (!workspaceRoot) {
|
||||
throw Object.assign(new Error('用户工作区不存在'), { code: 'workspace_not_found' });
|
||||
}
|
||||
const pool = getPool();
|
||||
return createUserDataSpaceService({
|
||||
workspaceRoot,
|
||||
userId: user.id,
|
||||
query: pool ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
async function listRows(user, datasetName, query = {}) {
|
||||
const service = await createServiceForUser(user);
|
||||
try {
|
||||
return service.readDatasetRows(datasetName, {
|
||||
limit: query.limit,
|
||||
offset: query.offset,
|
||||
orderBy: query.orderBy ?? query.order_by,
|
||||
orderDir: query.orderDir ?? query.order_dir,
|
||||
});
|
||||
} catch (error) {
|
||||
throw mapServiceError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function getSchema(user, datasetName) {
|
||||
const service = await createServiceForUser(user);
|
||||
try {
|
||||
return service.getDatasetSchema(datasetName);
|
||||
} catch (error) {
|
||||
throw mapServiceError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function getStats(user, datasetName) {
|
||||
const service = await createServiceForUser(user);
|
||||
try {
|
||||
return service.getDatasetStats(datasetName);
|
||||
} catch (error) {
|
||||
throw mapServiceError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function insertRow(user, datasetName, payload) {
|
||||
const service = await createServiceForUser(user);
|
||||
try {
|
||||
return service.insertDatasetRow(datasetName, payload);
|
||||
} catch (error) {
|
||||
throw mapServiceError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function listDatasets(user) {
|
||||
const service = await createServiceForUser(user);
|
||||
return service.listDatasets().map((dataset) => ({
|
||||
name: dataset.name,
|
||||
table: dataset.table,
|
||||
description: dataset.description,
|
||||
actions: dataset.actions,
|
||||
}));
|
||||
}
|
||||
|
||||
return {
|
||||
getUserAuth,
|
||||
listRows,
|
||||
getSchema,
|
||||
getStats,
|
||||
insertRow,
|
||||
listDatasets,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user