Files
memind/server/portal-domain-services-bootstrap.mjs
T
john fde6503bdf
Memind CI / Test, build, and release guards (push) Has been cancelled
feat(mindspace): add SEO/GEO delivery, page template catalog, and admin hooks
Enable optional SEO/GEO injection and discovery routes for confirmed public pages while keeping private pages noindex. Add premium page template skills, portal catalog API, template shop UI, and Baidu push gated by memind_adm config.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-10 08:06:12 +08:00

325 lines
11 KiB
JavaScript

import { ensureMindSpaceConfig, loadMindSpaceConfig, loadMindSpaceConfigCached } 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 { createScheduledTaskService } from '../scheduled-task-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,
loadMindSpaceConfigCachedFn = loadMindSpaceConfigCached,
createScheduleServiceFn = createScheduleService,
createScheduledTaskServiceFn = createScheduledTaskService,
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 scheduledTaskService = createScheduledTaskServiceFn(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: async (postId) => {
if (!plazaSeo) return { pushed: false, reason: 'plaza_unavailable' };
const config = await loadMindSpaceConfigCachedFn(pool, { env }).catch(() => null);
if (!config?.seoGeo?.enabled || !config?.seoGeo.seo?.baiduPush) {
return { pushed: false, reason: 'disabled' };
}
return 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,
scheduledTaskService,
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,
};
}