import { createUserDataSpaceService } from './user-data-space-service.mjs'; import { ensureHealthPageDataForUser } from './health-page-data-bootstrap.mjs'; import { HEALTH_OBSERVATION_DRAFTS_DATASET } from './health-page-data-schema.mjs'; const DATASET_NAME = HEALTH_OBSERVATION_DRAFTS_DATASET.name; function parseJsonField(value, fallback = {}) { if (value == null) return fallback; if (typeof value === 'object') return value; try { return JSON.parse(String(value)); } catch { return fallback; } } function mapPgDraftRow(row, userId, sourceRef = null) { return { id: Number(row.id), userId: String(userId), draftKey: row.draft_key, metricSet: row.metric_set, extracted: parseJsonField(row.extracted), validation: parseJsonField(row.validation), status: row.status, channel: row.channel, sourceRef: sourceRef ?? parseJsonField(row.extracted)?.sourceRef ?? null, createdAt: row.created_at ? new Date(row.created_at).getTime() : Date.now(), expiresAt: row.expires_at ? new Date(row.expires_at).getTime() : Date.now(), committedAt: row.committed_at ? new Date(row.committed_at).getTime() : null, }; } export function createPageDataHealthObservationDraftStore({ resolveWorkspaceRoot, logger = console }) { if (typeof resolveWorkspaceRoot !== 'function') { throw new Error('createPageDataHealthObservationDraftStore requires resolveWorkspaceRoot'); } async function getUserDataSpace(userId) { const workspaceRoot = await resolveWorkspaceRoot(userId); if (!workspaceRoot) return null; const service = createUserDataSpaceService({ workspaceRoot, userId: String(userId) }); await ensureHealthPageDataForUser(service, userId); return service; } async function listPending(userId, { limit = 100 } = {}) { const service = await getUserDataSpace(userId); if (!service) return []; try { const result = await service.readDatasetRows(DATASET_NAME, { limit, orderBy: 'created_at', orderDir: 'desc', }); const now = Date.now(); return (result.rows ?? []) .map((row) => mapPgDraftRow(row, userId)) .filter((row) => row.status === 'pending' && row.expiresAt > now); } catch (error) { if (error?.code === 'dataset_not_found') return []; throw error; } } return { backend: 'page_data', async findByKey(userId, draftKey) { const pending = await listPending(userId); return pending.find((row) => row.draftKey === draftKey) ?? null; }, async findPendingBySourceRef(userId, sourceRef) { if (!sourceRef) return null; const pending = await listPending(userId); return pending.find((row) => row.sourceRef === sourceRef) ?? null; }, async insert(userId, draft) { const service = await getUserDataSpace(userId); if (!service) { const error = Object.assign(new Error('用户健康草稿空间不可用'), { code: 'health_storage_unavailable' }); throw error; } const extracted = { ...draft.extracted, sourceRef: draft.sourceRef ?? null }; try { const { row } = await service.insertDatasetRow(DATASET_NAME, { draft_key: draft.draftKey, metric_set: draft.metricSet, extracted: JSON.stringify(extracted), validation: JSON.stringify(draft.validation ?? {}), status: 'pending', channel: draft.channel ?? 'h5', expires_at: new Date(draft.expiresAt ?? Date.now() + 86400000).toISOString(), }); return mapPgDraftRow(row, userId, draft.sourceRef ?? null); } catch (error) { logger.warn?.('Page Data health draft insert failed:', error); throw error; } }, async update(userId, draftKey, patch) { const service = await getUserDataSpace(userId); if (!service) return null; const existing = await this.findByKey(userId, draftKey); if (!existing?.id) return null; const payload = {}; if (patch.extracted != null) payload.extracted = JSON.stringify(patch.extracted); if (patch.validation != null) payload.validation = JSON.stringify(patch.validation); if (patch.status != null) payload.status = patch.status; if (patch.committedAt != null) payload.committed_at = new Date(patch.committedAt).toISOString(); const { row } = await service.updateDatasetRow(DATASET_NAME, existing.id, payload); return mapPgDraftRow(row, userId, existing.sourceRef); }, }; }