import { redirect } from '@sveltejs/kit'; import type { RequestEvent } from '@sveltejs/kit'; import { pbUser } from '$lib/server/pocketbase'; import { pbAdmin } from '$lib/server/pocketbase'; import { servicesFor } from '$lib/server/servicesFor'; import { issueAccess, createChild } from '$lib/server/member-otp'; import { slugify } from '@shared/slugify'; function famIdOf(event: RequestEvent): string { if (!event.locals.user) throw redirect(303, '/login'); return event.locals.user.famId; } export async function load(event: RequestEvent) { const famId = famIdOf(event); const pb = pbUser(event); const [members, fam, seasons] = await Promise.all([ pb.collection('users').getFullList({ filter: `famId = '${famId}' && role = 'child'` }), pbAdmin.getOne('fams', famId), pb.collection('seasons').getFullList({ filter: `famId = '${famId}'` }) ]); return { members, fam, seasons }; } export const actions = { addMember: async (event: RequestEvent) => { const famId = famIdOf(event); const famSlug: string = event.params.fam as string; const fd = await event.request.formData(); const name = (fd.get('name') as string) || ''; const colour = (fd.get('colour') as string) || '#6366f1'; if (!name) return { error: 'Name required' }; try { await createChild({ famId, famSlug, name, colour }); return { ok: true }; } catch (e) { return { error: e instanceof Error ? e.message : 'Failed to add member' }; } }, issueAccess: async (event: RequestEvent) => { const famId = famIdOf(event); const famSlug: string = event.params.fam as string; const fd = await event.request.formData(); const name = (fd.get('name') || '').toString().trim(); if (!name) return { error: 'Select a child to invite' }; try { const result = await issueAccess({ famId, famSlug, name }); return { ok: true, ...result }; } catch (e) { return { error: e instanceof Error ? e.message : 'Failed to issue access' }; } }, renameFam: async (event: RequestEvent) => { const famId = famIdOf(event); const fd = await event.request.formData(); const name = fd.get('name') as string; if (!name) return { error: 'Name required' }; const record = await pbUser(event) .collection('fams') .update(famId, { name, slug: slugify(name) }); return { name: record.name, slug: record.slug }; }, deleteMember: async (event: RequestEvent) => { const famId = famIdOf(event); const fd = await event.request.formData(); const id = fd.get('id') as string; if (!id) return { error: 'Member ID required' }; await pbUser(event).collection('users').delete(id); return { ok: true }; }, updatePayday: async (event: RequestEvent) => { const famId = famIdOf(event); const fd = await event.request.formData(); const payday = parseInt(fd.get('payday') as string, 10); if (isNaN(payday) || payday < 0 || payday > 6) return { error: 'Payday must be 0-6' }; const paydayTime = fd.get('paydayTime') as string; if (paydayTime && !/^\d{2}:\d{2}$/.test(paydayTime)) return { error: 'Payday time must be HH:MM' }; const timezone = fd.get('timezone') as string; if (timezone && timezone !== 'auto' && !/^[A-Za-z_+-]+\/[A-Za-z_+-]+$/.test(timezone)) { return { error: 'Timezone must be an IANA name or auto' }; } const patch: Record = { payday }; if (paydayTime) patch.paydayTime = paydayTime; if (timezone) patch.timezone = timezone; const record = await pbUser(event).collection('fams').update(famId, patch); return { payday: record.payday, paydayTime: record.paydayTime, timezone: record.timezone }; }, createSeason: async (event: RequestEvent) => { const famId = famIdOf(event); const fd = await event.request.formData(); const name = fd.get('name') as string; const color = fd.get('color') as string; if (!name) return { error: 'Name required' }; return await pbUser(event) .collection('seasons') .create({ famId, name, color: color || '#6366f1', active: true }); }, deleteSeason: async (event: RequestEvent) => { const famId = famIdOf(event); const fd = await event.request.formData(); const id = fd.get('id') as string; if (!id) return { error: 'Season ID required' }; const pb = pbUser(event); const assigned = await pb.collection('assigned_chores').getFullList({ filter: `famId = '${famId}'` }); const toDelete = (Array.isArray(assigned) ? assigned : []).filter( (a: any) => a.seasonIds?.includes(id) ); const deletedIds = toDelete.map((a: any) => a.id); await Promise.all( toDelete.map((a: any) => pb.collection('assigned_chores').delete(a.id)) ); await pb.collection('seasons').delete(id); return { deletedChoreIds: deletedIds }; }, // Compute endpoints — in-process. completeWeek: async (event: RequestEvent) => { const famId = famIdOf(event); try { const s = servicesFor(event); const result = await s.fam.completeWeek(famId); return { success: true, result }; } catch (e) { return { error: e instanceof Error ? e.message : 'Failed to complete week' }; } }, generateData: async (event: RequestEvent) => { const famId = famIdOf(event); const fd = await event.request.formData(); const days = parseInt((fd.get('days') as string) || '7', 10); try { const s = servicesFor(event); const result = await s.debug.generateData(famId, days); return { success: true, result }; } catch (e) { return { error: e instanceof Error ? e.message : 'Failed to generate data' }; } } };