import { ensureMindSpaceConfig, loadMindSpaceConfig } from '../mindspace-config.mjs'; import { resolveMindSpaceRybbitConfig } from '../mindspace-rybbit.mjs'; import { createMindSearchConfigService } from '../mindsearch-config.mjs'; import { createMindSpaceService } from '../mindspace.mjs'; import { assertMindSpaceServerAdapterContract, createMindSpaceServerAdapter, } from '../mindspace-server-adapter.mjs'; import { createPageDataPublicService } from '../page-data-public-service.mjs'; import { createPageDataService } from '../page-data-service.mjs'; import { ensureAlgorithmConfig, loadAlgorithmConfig, recalculateHotScores, } from '../plaza-algorithm.mjs'; import { createPlazaEventService } from '../plaza-events.mjs'; import { createPlazaInteractionService } from '../plaza-interactions.mjs'; import { createPlazaOpsService } from '../plaza-ops.mjs'; import { createPlazaPostService, formatPostRow } from '../plaza-posts.mjs'; import { createPlazaRecommendService } from '../plaza-recommend.mjs'; import { createPlazaRedis } from '../plaza-redis.mjs'; import { isPassiveCanaryRuntime } from './portal-runtime-role.mjs'; import { createPlazaSeoService } from '../plaza-seo.mjs'; import { startPlazaTasks, writebackPublications, } from '../plaza-tasks.mjs'; import { createScheduleService } from '../schedule-service.mjs'; import { createFeedbackService } from '../user-feedback.mjs'; import { PUBLISH_KEY_UUID } from '../user-publish.mjs'; import { initSchema } from '../db.mjs'; export async function bootstrapPortalDomainServices({ pool, h5Root, env = process.env, runtime, analyticsConfig = {}, getUserAuth = () => null, getSessionSnapshotService = () => null, onMindSearchSchemaReady = () => {}, workspaceMaintenanceEnabled = true, logger = console, initSchemaFn = initSchema, createMindSearchConfigServiceFn = createMindSearchConfigService, ensureMindSpaceConfigFn = ensureMindSpaceConfig, loadMindSpaceConfigFn = loadMindSpaceConfig, createScheduleServiceFn = createScheduleService, createPageDataServiceFn = createPageDataService, createPageDataPublicServiceFn = createPageDataPublicService, createFeedbackServiceFn = createFeedbackService, createMindSpaceServiceFn = createMindSpaceService, createMindSpaceServerAdapterFn = createMindSpaceServerAdapter, assertMindSpaceServerAdapterContractFn = assertMindSpaceServerAdapterContract, ensureAlgorithmConfigFn = ensureAlgorithmConfig, loadAlgorithmConfigFn = loadAlgorithmConfig, createPlazaRedisFn = createPlazaRedis, createPlazaSeoServiceFn = createPlazaSeoService, createPlazaInteractionServiceFn = createPlazaInteractionService, createPlazaEventServiceFn = createPlazaEventService, createPlazaRecommendServiceFn = createPlazaRecommendService, createPlazaPostServiceFn = createPlazaPostService, createPlazaOpsServiceFn = createPlazaOpsService, startPlazaTasksFn = startPlazaTasks, recalculateHotScoresFn = recalculateHotScores, writebackPublicationsFn = writebackPublications, formatPostRowFn = formatPostRow, publishKeyUuid = PUBLISH_KEY_UUID, } = {}) { if (!pool || !runtime) { throw new Error( 'bootstrapPortalDomainServices requires pool and runtime', ); } const mindSearchConfigService = createMindSearchConfigServiceFn(pool); await mindSearchConfigService.ensureSchema(); await onMindSearchSchemaReady({ pool, mindSearchConfigService, }); await initSchemaFn(pool); await ensureMindSpaceConfigFn(pool, { env }); const storedMindSpaceConfig = await loadMindSpaceConfigFn(pool, { env, includeAnalyticsSecret: true, }); let resolvedAnalyticsConfig = analyticsConfig; if (storedMindSpaceConfig?.analytics) { resolvedAnalyticsConfig = { ...analyticsConfig, ...storedMindSpaceConfig.analytics, enabled: Boolean( storedMindSpaceConfig.analytics.enabled && storedMindSpaceConfig.analytics.websiteId && storedMindSpaceConfig.analytics.idSecret, ), hostPath: '/analytics', scriptPath: '/analytics/script.js', }; } const resolvedRybbitConfig = resolveMindSpaceRybbitConfig(env, { idSecret: storedMindSpaceConfig?.analytics?.idSecret ?? '', }); const scheduleService = createScheduleServiceFn(pool, { defaultTimezone: env.H5_DEFAULT_TIMEZONE || 'Asia/Shanghai', }); const pageDataService = createPageDataServiceFn({ getUserAuth, getPool: () => pool, resolveWorkspaceRoot: async (user) => { if (user?.workspaceRoot) return user.workspaceRoot; const userAuth = getUserAuth(); if (!userAuth || !user?.id) return null; return userAuth.resolveWorkingDir(user.id); }, }); const pageDataPublicService = createPageDataPublicServiceFn({ getPool: () => pool, resolveH5Root: () => h5Root, }); const feedbackService = createFeedbackServiceFn(pool); const mindSpace = createMindSpaceServiceFn(pool, { maxFileBytes: runtime.maxFileBytes, aiDailyLimit: runtime.aiDailyLimit, publicPageLimit: runtime.publicPageLimit, monthlyViewLimit: runtime.monthlyViewLimit, scheduleService, }); const resolveUserIdForAgentSession = async (sessionId) => { const [rows] = await pool.query( `SELECT user_id FROM h5_user_sessions WHERE agent_session_id = ? LIMIT 1`, [sessionId], ); return rows[0]?.user_id ?? null; }; const mindSpaceRuntimeAdapter = assertMindSpaceServerAdapterContractFn( createMindSpaceServerAdapterFn({ pool, h5Root, env, maxFileBytes: runtime.maxFileBytes, publicPageLimit: runtime.publicPageLimit, resolveUserIdForAgentSession, resolveWorkspaceRoot: (userId) => getUserAuth().resolveWorkingDir(userId), resolveSessionSnapshot: (sessionId) => getSessionSnapshotService()?.get?.(sessionId), syncWorkspaceAssetsEnabled: workspaceMaintenanceEnabled, remote: runtime.remote, logger, }), ); await mindSpaceRuntimeAdapter.assertReady?.(); const mindSpaceServiceFacade = mindSpaceRuntimeAdapter.serviceFacade; const mindSpaceChatSave = mindSpaceRuntimeAdapter.chatSaveService; const mindSpaceConversationArtifacts = mindSpaceRuntimeAdapter.conversationArtifactService; const mindSpacePublicFinish = mindSpaceRuntimeAdapter.publicFinishService; const mindSpaceWorkspacePublicationDelivery = mindSpaceRuntimeAdapter .workspacePublicationDeliveryService; const mindSpaceConversationPackageRegistry = mindSpaceRuntimeAdapter.conversationPackageRegistry; const mindSpaceAssets = mindSpaceRuntimeAdapter.assetService; const mindSpacePages = mindSpaceRuntimeAdapter.pageService; const mindSpacePageSync = mindSpaceRuntimeAdapter.pageSyncService; const mindSpacePageLiveEdit = mindSpaceRuntimeAdapter.pageLiveEditService; const mindSpaceAssetAgent = mindSpaceRuntimeAdapter.assetAgentService; const mindSpacePublications = mindSpaceRuntimeAdapter.publicationService; const workspacePageDeliver = mindSpaceRuntimeAdapter .workspacePageDeliveryService; const resolveUserIdByDirKey = async (dirKey) => { let userId = dirKey; if (!publishKeyUuid.test(dirKey)) { const [rows] = await pool.query( `SELECT id FROM h5_users WHERE username = ? LIMIT 1`, [dirKey], ); userId = rows[0]?.id; } return userId ?? null; }; await ensureAlgorithmConfigFn(pool); const plazaAlgorithmConfig = await loadAlgorithmConfigFn(pool); const plazaRedis = await createPlazaRedisFn( env.PLAZA_REDIS_URL, pool, ); if (plazaRedis.enabled) { logger.log('Plaza Redis enabled'); } const plazaSeo = createPlazaSeoServiceFn(pool); const plazaInteractions = createPlazaInteractionServiceFn(pool, { formatPostRow: formatPostRowFn, plazaRedis, }); const plazaEvents = createPlazaEventServiceFn(pool); const plazaRecommend = createPlazaRecommendServiceFn(pool, { eventService: plazaEvents, formatPostRow: formatPostRowFn, loadViewerReactions: (viewerId, postIds) => plazaInteractions.loadViewerReactions( viewerId, postIds, ), algorithmConfig: plazaAlgorithmConfig, }); let plazaOps = null; const plazaPosts = createPlazaPostServiceFn(pool, { loadViewerReactions: (viewerId, postIds) => plazaInteractions.loadViewerReactions( viewerId, postIds, ), plazaRedis, algorithmConfig: plazaAlgorithmConfig, recommendService: plazaRecommend, onPostPublished: (postId) => plazaSeo?.notifyPostPublished(postId), loadFeaturedPosts: async (viewerId) => { if (!plazaOps) { return { homepage_banner: [], trending: [], category_top: {}, }; } return plazaOps.loadActiveFeaturedPosts(viewerId); }, }); plazaOps = createPlazaOpsServiceFn(pool, { formatPostRow: formatPostRowFn, reviewPost: (...args) => plazaPosts.reviewPost(...args), invalidateFeedCaches: () => plazaRedis?.invalidateFeedCaches?.(), }); if (isPassiveCanaryRuntime(env)) { logger.log('Passive candidate runtime: Plaza background tasks disabled'); } else { startPlazaTasksFn({ pool, plazaRedis, recalculateHotScores: recalculateHotScoresFn, writebackPublications: writebackPublicationsFn, }); } return { mindSearchConfigService, mindSpaceAnalyticsConfig: resolvedAnalyticsConfig, mindSpaceRybbitConfig: resolvedRybbitConfig, scheduleService, pageDataService, pageDataPublicService, feedbackService, mindSpace, mindSpaceRuntimeAdapter, mindSpaceServiceFacade, mindSpaceChatSave, mindSpaceConversationArtifacts, mindSpacePublicFinish, mindSpaceWorkspacePublicationDelivery, mindSpaceConversationPackageRegistry, mindSpaceAssets, mindSpacePages, mindSpacePageSync, mindSpacePageLiveEdit, mindSpaceAssetAgent, mindSpacePublications, workspacePageDeliver, resolveUserIdByDirKey, plazaRedis, plazaSeo, plazaInteractions, plazaEvents, plazaRecommend, plazaPosts, plazaOps, mindSpaceCleanup: mindSpaceRuntimeAdapter.cleanupService, }; }