Files
memind/server/portal-domain-services-bootstrap.mjs
john 286069449b
Memind CI / Test, build, and release guards (push) Failing after 2m14s
feat: add guarded portal canary release
2026-07-26 19:51:44 +08:00

332 lines
11 KiB
JavaScript

import { ensureMindSpaceConfig, loadMindSpaceConfig } from '../mindspace-config.mjs';
import { createMindSearchConfigService } from '../mindsearch-config.mjs';
import { createMindSpaceService } from '../mindspace.mjs';
import {
assertMindSpaceServerAdapterContract,
createMindSpaceServerAdapter,
} from '../mindspace-server-adapter.mjs';
import {
resolveMindSpaceRuntimeConfig,
} from '../mindspace-runtime-config.mjs';
import { createWorkspacePageDeliverService } from '../mindspace-workspace-page-deliver.mjs';
import { ensurePageDataHtmlPagesBound } from '../page-data-workspace-ensure.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 = () => {},
registerPublicHtmlArtifactsForConversation,
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,
createWorkspacePageDeliverServiceFn =
createWorkspacePageDeliverService,
ensurePageDataHtmlPagesBoundFn =
ensurePageDataHtmlPagesBound,
resolveMindSpaceRuntimeConfigFn =
resolveMindSpaceRuntimeConfig,
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',
);
}
if (
typeof registerPublicHtmlArtifactsForConversation !==
'function'
) {
throw new Error(
'bootstrapPortalDomainServices requires artifact registration',
);
}
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 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),
registerPublicHtmlArtifactsForConversation,
syncWorkspaceAssetsEnabled:
workspaceMaintenanceEnabled,
remote: runtime.remote,
logger,
}),
);
mindSpaceRuntimeAdapter.assertReady?.();
const mindSpaceServiceFacade =
mindSpaceRuntimeAdapter.serviceFacade;
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 =
createWorkspacePageDeliverServiceFn({
pool,
pageService: mindSpacePages,
publicationService: mindSpacePublications,
pageSyncService: mindSpacePageSync,
pageDataEnsure: {
ensurePageDataHtmlPagesBound:
ensurePageDataHtmlPagesBoundFn,
},
h5Root,
storageRoot:
resolveMindSpaceRuntimeConfigFn(h5Root, env)
.storageRoot,
findPageByRelativePath:
mindSpacePages.findPageByRelativePath.bind(
mindSpacePages,
),
logger,
});
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,
scheduleService,
pageDataService,
pageDataPublicService,
feedbackService,
mindSpace,
mindSpaceRuntimeAdapter,
mindSpaceServiceFacade,
mindSpaceConversationPackageRegistry,
mindSpaceAssets,
mindSpacePages,
mindSpacePageSync,
mindSpacePageLiveEdit,
mindSpaceAssetAgent,
mindSpacePublications,
workspacePageDeliver,
resolveUserIdByDirKey,
plazaRedis,
plazaSeo,
plazaInteractions,
plazaEvents,
plazaRecommend,
plazaPosts,
plazaOps,
mindSpaceCleanup:
mindSpaceRuntimeAdapter.cleanupService,
};
}