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:
john
2026-07-08 12:44:23 +08:00
parent 1c8e0db256
commit 8d01553469
17 changed files with 2587 additions and 189 deletions
+27 -2
View File
@@ -185,6 +185,9 @@ import { createMemoryV2AdminConfigService } from './memory-v2-admin-config.mjs';
import { createWechatScheduleLlmConfigService } from './wechat-schedule-llm-config.mjs';
import { createExperienceService } from './experience-service.mjs';
import { attachAsrRoutes } from './asr-proxy.mjs';
import { attachPageDataRoutes } from './page-data-routes.mjs';
import { createPageDataService } from './page-data-service.mjs';
import { createPageDataPublicService, isPageDataPublicPath } from './page-data-public-service.mjs';
import { isNativeH5ApiPath } from './policies.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -362,6 +365,8 @@ let scheduleReminderWorker = null;
let llmProviderService = null;
let wordFilterService = null;
let authPool = null;
let pageDataService = null;
let pageDataPublicService = null;
async function bootstrapUserAuth() {
try {
@@ -375,6 +380,19 @@ async function bootstrapUserAuth() {
scheduleService = createScheduleService(pool, {
defaultTimezone: process.env.H5_DEFAULT_TIMEZONE || 'Asia/Shanghai',
});
pageDataService = createPageDataService({
getUserAuth: () => userAuth,
getPool: () => authPool,
resolveWorkspaceRoot: async (user) => {
if (user?.workspaceRoot) return user.workspaceRoot;
if (!userAuth || !user?.id) return null;
return userAuth.resolveWorkingDir(user.id);
},
});
pageDataPublicService = createPageDataPublicService({
getPool: () => authPool,
resolveH5Root: () => __dirname,
});
feedbackService = createFeedbackService(pool);
mindSpace = createMindSpaceService(pool, {
maxFileBytes: mindSpaceServerRuntime.maxFileBytes,
@@ -1833,10 +1851,11 @@ api.use(async (req, res, next) => {
}
const plazaPublic = isPlazaPublicRead(req.path, req.method);
const pageDataPublic = isPageDataPublicPath(req.path, req.method);
if (userAuth && tkmindProxy) {
if (req.userSessionError) {
if (plazaPublic) return next();
if (plazaPublic || pageDataPublic) return next();
return res.status(503).json({ message: '用户认证服务不可用,请稍后重试' });
}
try {
@@ -1844,7 +1863,7 @@ api.use(async (req, res, next) => {
const me = await userAuth.getMe(req.userToken);
if (me) req.currentUser = me;
}
if (plazaPublic) return next();
if (plazaPublic || pageDataPublic) return next();
if (!req.userSession) {
return res.status(401).json({ message: '未授权,请重新登录' });
}
@@ -1863,6 +1882,12 @@ api.use(async (req, res, next) => {
});
attachAsrRoutes(api, { sendError, sendData });
attachPageDataRoutes(api, {
sendError,
sendData,
getPageDataService: () => pageDataService,
getPageDataPublicService: () => pageDataPublicService,
});
api.get('/config/blocked-words', async (_req, res) => {
await userAuthReady;