import { stripPageDataMetaFields } from './page-data-captcha.mjs'; import { mapPageDataPublicError } from './page-data-public-service.mjs'; function requireUser(req, res, sendError) { if (!req.currentUser?.id) { sendError(res, req, 401, 'unauthorized', '未授权,请重新登录'); return null; } return req.currentUser; } function parsePositiveInt(value, fallback) { const parsed = Number(value); if (!Number.isFinite(parsed) || parsed < 0) return fallback; return Math.floor(parsed); } function parseRowPayload(req, { stripMeta = false } = {}) { const payload = req.body?.row && typeof req.body.row === 'object' ? req.body.row : req.body; if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { return null; } return stripMeta ? stripPageDataMetaFields(payload) : payload; } function handlePublicError(res, req, sendError, error) { const mapped = mapPageDataPublicError(error); return sendError(res, req, mapped.status, mapped.code, mapped.message); } export function attachPageDataRoutes(api, deps) { const { sendData, sendError, getPageDataService, getPageDataPublicService } = deps; api.get('/page-data', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const service = getPageDataService?.(); if (!service) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const datasets = await service.listDatasets(user); return sendData(res, req, { datasets }); } catch (error) { const status = error?.status ?? 400; return sendError(res, req, status, error?.code ?? 'page_data_failed', error?.message ?? '读取失败'); } }); api.get('/page-data/policies', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const publicService = getPageDataPublicService?.(); if (!publicService) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const policies = await publicService.listOwnerPolicies(user); return sendData(res, req, { policies }); } catch (error) { return handlePublicError(res, req, sendError, error); } }); api.get('/page-data/policies/:pageId', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const publicService = getPageDataPublicService?.(); if (!publicService) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const policy = await publicService.getOwnerPolicy(user, req.params.pageId); if (!policy) return sendError(res, req, 404, 'policy_not_found', '页面数据策略未配置'); return sendData(res, req, { policy }); } catch (error) { return handlePublicError(res, req, sendError, error); } }); api.post('/page-data/policies/:pageId/apply-publish', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const publicService = getPageDataPublicService?.(); if (!publicService) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); const datasetName = String(req.body?.datasetName ?? req.body?.dataset ?? '').trim(); if (!datasetName) return sendError(res, req, 400, 'invalid_request', '缺少 datasetName'); try { const policy = await publicService.saveOwnerPolicyFromPublish(user, req.params.pageId, { datasetName, capabilities: req.body?.capabilities ?? {}, }); return sendData(res, req, { policy }); } catch (error) { return handlePublicError(res, req, sendError, error); } }); api.put('/page-data/policies/:pageId', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const publicService = getPageDataPublicService?.(); if (!publicService) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const policy = await publicService.saveOwnerPolicy(user, req.params.pageId, req.body ?? {}); return sendData(res, req, { policy }); } catch (error) { return handlePublicError(res, req, sendError, error); } }); api.get('/page-data/policies/:pageId/logs', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const service = getPageDataService?.(); if (!service) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const result = await service.listPageLogs(user, req.params.pageId, { limit: parsePositiveInt(req.query.limit, 100), offset: parsePositiveInt(req.query.offset, 0), }); return sendData(res, req, result); } catch (error) { const status = error?.status ?? 400; return sendError(res, req, status, error?.code ?? 'page_data_failed', error?.message ?? '读取失败'); } }); api.get('/page-data/policies/:pageId/ops', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const publicService = getPageDataPublicService?.(); if (!publicService) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const overview = await publicService.getPageOpsOverview(user, req.params.pageId); return sendData(res, req, overview); } catch (error) { return handlePublicError(res, req, sendError, error); } }); api.post('/page-data/policies/:pageId/tokens/revoke', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const publicService = getPageDataPublicService?.(); if (!publicService) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const result = await publicService.revokePageDataTokens(user, req.params.pageId, { token: req.body?.token ?? null, revokeAll: Boolean(req.body?.revokeAll ?? req.body?.revoke_all), }); return sendData(res, req, result); } catch (error) { return handlePublicError(res, req, sendError, error); } }); api.post('/page-data/policies/:pageId/password/reset', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const publicService = getPageDataPublicService?.(); if (!publicService) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); const password = String(req.body?.password ?? '').trim(); if (!password) return sendError(res, req, 400, 'invalid_request', '缺少新口令'); try { const result = await publicService.resetPageDataPassword(user, req.params.pageId, password); return sendData(res, req, result); } catch (error) { return handlePublicError(res, req, sendError, error); } }); api.post('/page-data/policies/:pageId/datasets/:dataset/close', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const publicService = getPageDataPublicService?.(); if (!publicService) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const result = await publicService.closePageDataset(user, req.params.pageId, req.params.dataset); return sendData(res, req, result); } catch (error) { return handlePublicError(res, req, sendError, error); } }); api.get('/page-data/:dataset/export', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const service = getPageDataService?.(); if (!service) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const exported = await service.exportDataset(user, req.params.dataset, { format: req.query.format, include_deleted: req.query.include_deleted, limit: parsePositiveInt(req.query.limit, 1000), }); if (exported.format === 'csv') { res.setHeader('content-type', 'text/csv; charset=utf-8'); res.setHeader( 'content-disposition', `attachment; filename="${encodeURIComponent(exported.dataset)}.csv"`, ); return res.status(200).send(exported.content); } return sendData(res, req, exported); } catch (error) { const status = error?.status ?? 400; return sendError(res, req, status, error?.code ?? 'page_data_failed', error?.message ?? '导出失败'); } }); api.get('/page-data/:dataset', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const service = getPageDataService?.(); if (!service) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const result = await service.listRows(user, req.params.dataset, { limit: parsePositiveInt(req.query.limit, undefined), offset: parsePositiveInt(req.query.offset, 0), orderBy: req.query.order_by, orderDir: req.query.order_dir, include_deleted: req.query.include_deleted, }); return sendData(res, req, { dataset: result.dataset.name, rows: result.rows, limit: result.limit, offset: result.offset, }); } catch (error) { const status = error?.status ?? 400; return sendError(res, req, status, error?.code ?? 'page_data_failed', error?.message ?? '读取失败'); } }); api.get('/page-data/:dataset/schema', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const service = getPageDataService?.(); if (!service) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const schema = await service.getSchema(user, req.params.dataset); return sendData(res, req, schema); } catch (error) { const status = error?.status ?? 400; return sendError(res, req, status, error?.code ?? 'page_data_failed', error?.message ?? '读取失败'); } }); api.get('/page-data/:dataset/stats', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const service = getPageDataService?.(); if (!service) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const stats = await service.getStats(user, req.params.dataset); return sendData(res, req, stats); } catch (error) { const status = error?.status ?? 400; return sendError(res, req, status, error?.code ?? 'page_data_failed', error?.message ?? '读取失败'); } }); api.post('/page-data/:dataset/rows', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const service = getPageDataService?.(); if (!service) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); const payload = parseRowPayload(req, { stripMeta: true }); if (!payload) return sendError(res, req, 400, 'invalid_request', '提交数据必须是 JSON 对象'); try { const result = await service.insertRow(user, req.params.dataset, payload); return sendData(res, req, { dataset: result.dataset.name, row: result.row }, 201); } catch (error) { const status = error?.status ?? 400; return sendError(res, req, status, error?.code ?? 'page_data_failed', error?.message ?? '写入失败'); } }); api.post('/page-data/:dataset/rows/:rowId/restore', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const service = getPageDataService?.(); if (!service) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const result = await service.restoreRow(user, req.params.dataset, req.params.rowId); return sendData(res, req, result); } catch (error) { const status = error?.status ?? 400; return sendError(res, req, status, error?.code ?? 'page_data_failed', error?.message ?? '恢复失败'); } }); api.patch('/page-data/:dataset/rows/:rowId', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const service = getPageDataService?.(); if (!service) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); const payload = parseRowPayload(req, { stripMeta: true }); if (!payload) return sendError(res, req, 400, 'invalid_request', '更新数据必须是 JSON 对象'); try { const result = await service.updateRow(user, req.params.dataset, req.params.rowId, payload); return sendData(res, req, { dataset: result.dataset.name, row: result.row }); } catch (error) { const status = error?.status ?? 400; return sendError(res, req, status, error?.code ?? 'page_data_failed', error?.message ?? '更新失败'); } }); api.delete('/page-data/:dataset/rows/:rowId', async (req, res) => { const user = requireUser(req, res, sendError); if (!user) return; const service = getPageDataService?.(); if (!service) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const result = await service.softDeleteRow(user, req.params.dataset, req.params.rowId); return sendData(res, req, { dataset: result.dataset.name, row: result.row, deleted: true }); } catch (error) { const status = error?.status ?? 400; return sendError(res, req, status, error?.code ?? 'page_data_failed', error?.message ?? '删除失败'); } }); api.post('/public/pages/:pageId/data-auth', async (req, res) => { const publicService = getPageDataPublicService?.(); if (!publicService) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); const password = String(req.body?.password ?? '').trim(); if (!password) return sendError(res, req, 400, 'invalid_request', '缺少页面口令'); try { const auth = await publicService.authenticate(req.params.pageId, password, req); return sendData(res, req, auth); } catch (error) { return handlePublicError(res, req, sendError, error); } }); api.get('/public/pages/:pageId/data/:dataset', async (req, res) => { const publicService = getPageDataPublicService?.(); if (!publicService) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const result = await publicService.listRows(req.params.pageId, req.params.dataset, req, { limit: parsePositiveInt(req.query.limit, undefined), offset: parsePositiveInt(req.query.offset, 0), orderBy: req.query.order_by, orderDir: req.query.order_dir, }); return sendData(res, req, { dataset: result.dataset.name, rows: result.rows, limit: result.limit, offset: result.offset, }); } catch (error) { return handlePublicError(res, req, sendError, error); } }); api.get('/public/pages/:pageId/data/:dataset/schema', async (req, res) => { const publicService = getPageDataPublicService?.(); if (!publicService) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const schema = await publicService.getSchema(req.params.pageId, req.params.dataset, req); return sendData(res, req, schema); } catch (error) { return handlePublicError(res, req, sendError, error); } }); api.get('/public/pages/:pageId/data/:dataset/stats', async (req, res) => { const publicService = getPageDataPublicService?.(); if (!publicService) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const stats = await publicService.getStats(req.params.pageId, req.params.dataset, req); return sendData(res, req, stats); } catch (error) { return handlePublicError(res, req, sendError, error); } }); api.post('/public/pages/:pageId/data/:dataset/rows', async (req, res) => { const publicService = getPageDataPublicService?.(); if (!publicService) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); const payload = parseRowPayload(req, { stripMeta: true }); if (!payload) return sendError(res, req, 400, 'invalid_request', '提交数据必须是 JSON 对象'); try { const result = await publicService.insertRow(req.params.pageId, req.params.dataset, req, payload); return sendData(res, req, { dataset: result.dataset.name, row: result.row }, 201); } catch (error) { return handlePublicError(res, req, sendError, error); } }); api.patch('/public/pages/:pageId/data/:dataset/rows/:rowId', async (req, res) => { const publicService = getPageDataPublicService?.(); if (!publicService) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); const payload = parseRowPayload(req, { stripMeta: true }); if (!payload) return sendError(res, req, 400, 'invalid_request', '更新数据必须是 JSON 对象'); try { const result = await publicService.updateRow( req.params.pageId, req.params.dataset, req.params.rowId, req, payload, ); return sendData(res, req, { dataset: result.dataset.name, row: result.row }); } catch (error) { return handlePublicError(res, req, sendError, error); } }); api.delete('/public/pages/:pageId/data/:dataset/rows/:rowId', async (req, res) => { const publicService = getPageDataPublicService?.(); if (!publicService) return sendError(res, req, 503, 'feature_disabled', 'Page Data API 未启用'); try { const result = await publicService.softDeleteRow( req.params.pageId, req.params.dataset, req.params.rowId, req, ); return sendData(res, req, result); } catch (error) { return handlePublicError(res, req, sendError, error); } }); }