Files
memind/health-workspace-bootstrap.mjs
T
john 2baf29b3ae feat(health): complete P0 health channel — baseline engine, page-data, MindSpace UI
Deliver encrypted health zone, observation API, baseline maturity pipeline,
page-data bindings, and H5/WeChat channel integration for health P0.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 18:04:57 +08:00

192 lines
6.2 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { ensureHealthPageDataForUser } from './health-page-data-bootstrap.mjs';
import { createUserDataSpaceService } from './user-data-space-service.mjs';
import { isMemindHealthEnabled } from './health-feature.mjs';
import { writeHealthPublicHtmlPage, resolveHealthMaterializeH5Root } from './health-report-page.mjs';
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
export const HEALTH_SHARE_SNAPSHOTS_DATASET = Object.freeze({
name: 'health_share_snapshots',
table: 'health_share_snapshots',
description: 'MeMind Health 脱敏分享摘要(公开页只读)',
actions: ['read', 'insert'],
columns: {
read: ['id', 'snapshot_key', 'scope', 'period_start', 'period_end', 'payload', 'created_at'],
insert: ['snapshot_key', 'scope', 'period_start', 'period_end', 'payload'],
},
});
export const HEALTH_SHARE_SNAPSHOTS_DDL_PG = `CREATE TABLE IF NOT EXISTS health_share_snapshots (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
snapshot_key TEXT NOT NULL UNIQUE,
scope TEXT NOT NULL,
period_start DATE NOT NULL,
period_end DATE NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
)`;
export const HEALTH_SHARE_SNAPSHOTS_DDL_SQLITE = `CREATE TABLE IF NOT EXISTS health_share_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
snapshot_key TEXT NOT NULL UNIQUE,
scope TEXT NOT NULL,
period_start TEXT NOT NULL,
period_end TEXT NOT NULL,
payload TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
)`;
export async function ensureHealthShareSnapshotDataset(userDataSpace) {
const backend = userDataSpace.privateDataDb ? 'sqlite' : 'postgres';
const ddl = backend === 'sqlite' ? HEALTH_SHARE_SNAPSHOTS_DDL_SQLITE : HEALTH_SHARE_SNAPSHOTS_DDL_PG;
await userDataSpace.executeSql(ddl);
await userDataSpace.upsertDataset(HEALTH_SHARE_SNAPSHOTS_DATASET);
}
export const HEALTH_TIMELINE_PAGE_TEMPLATE_ID = 'health-timeline-summary';
export const HEALTH_TIMELINE_PAGE_TITLE = '健康 Timeline 摘要';
export const HEALTH_TIMELINE_PUBLIC_RELATIVE_PATH = 'public/health-timeline-summary.html';
export async function ensureHealthTimelinePage({
userId,
createHealthSystemPage = null,
listPages = null,
h5Root = process.cwd(),
env = process.env,
} = {}) {
if (!userId) {
return { ok: false, skipped: true, reason: 'missing_user' };
}
if (typeof listPages === 'function') {
const existing = await listPages(userId, {
categoryCode: 'health',
limit: 20,
});
const match = (existing?.items ?? []).find(
(page) =>
page.templateId === HEALTH_TIMELINE_PAGE_TEMPLATE_ID
|| page.title === HEALTH_TIMELINE_PAGE_TITLE,
);
if (match) {
return { ok: true, skipped: true, pageId: match.id, reason: 'already_exists' };
}
}
const template = loadHealthTimelinePageTemplate();
if (!template) {
return { ok: false, skipped: true, reason: 'template_missing' };
}
if (typeof createHealthSystemPage === 'function') {
const page = await createHealthSystemPage(userId, {
title: HEALTH_TIMELINE_PAGE_TITLE,
summary: '近 14 天确认记录(加密分区,禁止完全公开)',
content: template,
templateId: HEALTH_TIMELINE_PAGE_TEMPLATE_ID,
contentFormat: 'html',
pageType: 'html',
categoryCode: 'health',
});
return { ok: true, skipped: false, pageId: page?.id ?? null, mode: 'mindspace_page' };
}
try {
const written = writeHealthPublicHtmlPage({
h5Root: resolveHealthMaterializeH5Root(h5Root, env),
userId,
relativePath: HEALTH_TIMELINE_PUBLIC_RELATIVE_PATH,
html: template,
minSize: 128,
});
return {
ok: true,
skipped: false,
pageId: null,
mode: 'workspace_file',
relativePath: written.relativePath,
};
} catch (error) {
return {
ok: false,
skipped: true,
reason: error instanceof Error ? error.message : 'timeline_file_failed',
};
}
}
export async function bootstrapHealthWorkspace({
userId,
resolveWorkspaceRoot,
createHealthSystemPage = null,
listPages = null,
env = process.env,
} = {}) {
if (!isMemindHealthEnabled(env)) {
return { ok: false, skipped: true, reason: 'health_disabled' };
}
if (!userId || typeof resolveWorkspaceRoot !== 'function') {
return { ok: false, skipped: true, reason: 'missing_context' };
}
const workspaceRoot = await resolveWorkspaceRoot(userId);
if (!workspaceRoot) {
return { ok: false, skipped: true, reason: 'workspace_not_found' };
}
const userDataSpace = createUserDataSpaceService({ workspaceRoot, userId: String(userId) });
await ensureHealthPageDataForUser(userDataSpace, userId);
await ensureHealthShareSnapshotDataset(userDataSpace);
const markerDir = path.join(workspaceRoot, '.mindspace', 'health');
const markerPath = path.join(markerDir, 'workspace-bootstrapped.json');
let already = false;
try {
if (fs.existsSync(markerPath)) already = true;
} catch {
already = false;
}
let timelinePage = null;
try {
timelinePage = await ensureHealthTimelinePage({
userId,
createHealthSystemPage,
listPages,
h5Root: env.MEMIND_H5_ROOT ?? process.cwd(),
env,
});
} catch (error) {
timelinePage = {
ok: false,
skipped: true,
reason: error instanceof Error ? error.message : 'timeline_page_failed',
};
}
if (!already) {
fs.mkdirSync(markerDir, { recursive: true });
fs.writeFileSync(
markerPath,
`${JSON.stringify({
bootstrappedAt: Date.now(),
datasets: ['health_share_snapshots'],
timelinePageId: timelinePage?.pageId ?? null,
}, null, 2)}\n`,
);
}
return {
ok: true,
workspaceRoot,
datasets: ['health_observations', 'health_documents', 'health_observation_drafts', 'health_share_snapshots'],
firstBootstrap: !already,
timelinePage,
};
}
export function loadHealthTimelinePageTemplate() {
const templatePath = path.join(repoRoot, 'templates', 'health-timeline-summary.html');
if (!fs.existsSync(templatePath)) return null;
return fs.readFileSync(templatePath, 'utf8');
}