Add private health tracker (owner-only)
- health schema: vaccinations, body_measurements, workouts, nutrition_daily, sleep (generated hours), medications, hair_progress, appointments - migration with updated_at triggers; grants to personal_app only - /trackers/health overview with add/edit/delete, owner-gated (404 otherwise), noindex + private no-store, hidden from listings for non-owners - owner-only JSON/CSV import endpoint and scripts/health-import.ts
This commit is contained in:
27
src/lib/server/owner.ts
Normal file
27
src/lib/server/owner.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
// The site has one owner (Stan). Owner-only data, such as the health tracker, needs both
|
||||
// the admin role and the owner's account email, so granting someone else `admin` (e.g. to
|
||||
// moderate Animaldex) never exposes it. Override with OWNER_EMAIL if the address changes.
|
||||
const OWNER_EMAIL = (process.env.OWNER_EMAIL || 'stan@stanrunge.dev').toLowerCase();
|
||||
|
||||
type OwnerEvent = { locals: App.Locals };
|
||||
|
||||
export function isOwner(event: OwnerEvent): boolean {
|
||||
const user = event.locals.user;
|
||||
return !!user && user.role === 'admin' && user.email?.toLowerCase() === OWNER_EMAIL;
|
||||
}
|
||||
|
||||
/** Owner-only routes answer 404 to everyone else, so they don't reveal that they exist. */
|
||||
export function requireOwner(event: OwnerEvent) {
|
||||
if (!isOwner(event)) error(404, 'Not Found');
|
||||
return event.locals.user!;
|
||||
}
|
||||
|
||||
/** Paths that only the owner may see (checked in hooks.server.ts before any route runs). */
|
||||
export const ownerOnlyPrefixes = ['/trackers/health'] as const;
|
||||
|
||||
export function isOwnerOnlyPath(pathname: string): boolean {
|
||||
const path = pathname.toLowerCase().replace(/\/+$/, '');
|
||||
return ownerOnlyPrefixes.some((prefix) => path === prefix || path.startsWith(`${prefix}/`));
|
||||
}
|
||||
Reference in New Issue
Block a user