import { createUserDataSpaceService } from './user-data-space-service.mjs'; import { ensureHealthPageDataForUser } from './health-page-data-bootstrap.mjs'; import { HEALTH_EVENTS_DATASET } from './health-page-data-schema.mjs'; import { healthEventFingerprint, mapHealthEventToPgPayload, mapPgHealthEventRow, } from './health-event-serialize.mjs'; const DATASET_NAME = HEALTH_EVENTS_DATASET.name; export function createPageDataHealthEventStore({ resolveWorkspaceRoot, logger = console, } = {}) { if (typeof resolveWorkspaceRoot !== 'function') { throw new Error('createPageDataHealthEventStore 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 list(userId, { status = 'open', limit = 100 } = {}) { const service = await getUserDataSpace(userId); if (!service) return []; try { const result = await service.readDatasetRows(DATASET_NAME, { limit: Math.min(Math.max(limit, 1), 200), orderBy: 'detected_at', orderDir: 'desc', }); return (result.rows ?? []) .map((row) => mapPgHealthEventRow(row, userId)) .filter((row) => !status || row.status === status); } catch (error) { if (error?.code === 'dataset_not_found') return []; throw error; } } return { backend: 'page_data', list, async listUnreadAlerts(userId, { limit = 20 } = {}) { const rows = await list(userId, { status: 'open', limit: 200 }); return rows .filter((row) => row.severity === 'watch' || row.severity === 'alert') .slice(0, limit); }, async acknowledge(userId, eventId) { const openRows = await list(userId, { status: 'open', limit: 200 }); const existing = openRows.find((row) => Number(row.id) === Number(eventId)); if (!existing?.id) return null; const service = await getUserDataSpace(userId); if (!service) return null; const { row } = await service.updateDatasetRow(DATASET_NAME, existing.id, { status: 'acknowledged' }); return mapPgHealthEventRow(row, userId); }, async acknowledgeAll(userId) { const openRows = await listUnreadAlerts(userId, { limit: 200 }); let count = 0; for (const row of openRows) { const updated = await this.acknowledge(userId, row.id); if (updated) count += 1; } return count; }, async insertIfNew(userId, event, { now = Date.now() } = {}) { const openRows = await list(userId, { status: 'open', limit: 200 }); const fingerprint = healthEventFingerprint(event); const duplicate = openRows.find((row) => healthEventFingerprint(row) === fingerprint); if (duplicate) return { row: duplicate, created: false }; const service = await getUserDataSpace(userId); if (!service) { const error = Object.assign(new Error('用户健康事件空间不可用'), { code: 'health_storage_unavailable' }); throw error; } try { const { row } = await service.insertDatasetRow( DATASET_NAME, mapHealthEventToPgPayload(event, now), ); return { row: mapPgHealthEventRow(row, userId), created: true }; } catch (error) { logger.warn?.('Page Data health event insert failed:', error); throw error; } }, }; }