import { db } from '#lib/server/db/index.js'; import { healthAppointments, healthBodyMeasurements, healthMedications, healthNutritionDaily, healthSleep, healthVaccinations, healthWorkouts } from '#lib/server/db/schema/index.js'; import { requireOwner } from '#lib/server/owner.js'; import { cleanRow, drizzleTables, TIME_ZONE, writeRows } from '#lib/server/health-import.js'; import { healthTables, isHealthTable } from '#lib/health/tables.js'; import { asc, desc, eq, gte, lt } from 'drizzle-orm'; import { fail } from '@sveltejs/kit'; import type { Actions, PageServerLoad } from './$types'; const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; function today(): string { return new Intl.DateTimeFormat('en-CA', { timeZone: TIME_ZONE }).format(new Date()); } function daysAgo(day: string, n: number): string { const d = new Date(`${day}T12:00:00Z`); d.setUTCDate(d.getUTCDate() - n); return d.toISOString().slice(0, 10); } export const load: PageServerLoad = async (event) => { requireOwner(event); const now = today(); const [measurements, workouts, nutrition, sleep, medications, vaccinations, upcoming, past] = await Promise.all([ db .select() .from(healthBodyMeasurements) .orderBy(asc(healthBodyMeasurements.date), asc(healthBodyMeasurements.createdAt)), db .select() .from(healthWorkouts) .orderBy(desc(healthWorkouts.date), asc(healthWorkouts.createdAt)) .limit(30), db .select() .from(healthNutritionDaily) .where(gte(healthNutritionDaily.date, daysAgo(now, 6))) .orderBy(desc(healthNutritionDaily.date)), db .select() .from(healthSleep) .where(gte(healthSleep.date, daysAgo(now, 6))) .orderBy(desc(healthSleep.date)), db.select().from(healthMedications).orderBy(asc(healthMedications.name)), db .select() .from(healthVaccinations) .orderBy(desc(healthVaccinations.date), asc(healthVaccinations.vaccine)), db .select() .from(healthAppointments) .where(gte(healthAppointments.date, now)) .orderBy(asc(healthAppointments.date)), db .select() .from(healthAppointments) .where(lt(healthAppointments.date, now)) .orderBy(desc(healthAppointments.date)) .limit(10) ]); return { today: now, measurements, workouts, nutrition, sleep, medications, vaccinations, appointments: { upcoming, past }, seo: { title: 'Health', description: 'Private health log.', index: false } }; }; export const actions: Actions = { save: async (event) => { requireOwner(event); const form = await event.request.formData(); const table = form.get('table'); if (!isHealthTable(table)) return fail(400, { message: 'Unknown table' }); const row: Record = { id: form.get('id') || undefined }; for (const field of healthTables[table].fields) row[field.key] = form.get(field.key) ?? ''; const result = cleanRow(table, row); if (!result.ok) return fail(400, { message: result.error, table }); try { await writeRows(db, { [table]: [result.row] }); } catch (e) { return fail(400, { message: e instanceof Error ? e.message : 'Could not save', table }); } return { saved: table }; }, delete: async (event) => { requireOwner(event); const form = await event.request.formData(); const table = form.get('table'); const id = String(form.get('id') ?? ''); if (!isHealthTable(table) || !UUID.test(id)) return fail(400, { message: 'Invalid entry' }); const target = drizzleTables[table]; await db.delete(target).where(eq(target.id, id)); return { deleted: table }; } };