Files
website/src/routes/trackers/health/+page.server.ts
Stan Runge 8aa689af64
All checks were successful
Deploy production / deploy (push) Successful in 2m22s
health: drop hair_progress; owner = Stan's account email only
Regenerated the (not yet deployed) health migration without hair_progress.
2026-10-24 00:22:24 +00:00

111 lines
3.5 KiB
TypeScript

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<string, unknown> = { 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 };
}
};