import { redirect } from '@sveltejs/kit'; import { createPbClient } from '$lib/server/pocketbase'; import { createServices, type ChatActor } from '$lib/server/services'; import { ensureFamAccess } from '$lib/server/access'; async function paydayCheck(famId: string, pbToken: string) { try { // Best-effort: never block render on the payday heartbeat. const s = createServices(createPbClient(pbToken)); await s.fam.payday(famId); } catch {} } function actorFrom(session: { famId: string; id: string; role: string; name?: string; color?: string; }): ChatActor { return { id: session.id, type: session.role === 'parent' ? 'admin' : 'member', name: session.name || '', color: session.color || '#6366f1' }; } async function resolveChatIdentity( session: { famId: string; id: string; role: string; name?: string; color?: string; }, pbToken: string ) { try { const s = createServices(createPbClient(pbToken)); const actor = actorFrom(session); return await s.chat.me(session.famId, actor); } catch { return null; } } export async function load(event) { const session = event.locals.user; const role = session?.role || 'child'; const isParent = role === 'parent'; const pbToken = event.cookies.get('pb_token') || ''; let famId = ''; let chat: { famId: string; actor: ChatActor } | null = null; let fam: any = null; let famAccess = { disabled: false, mode: 'none' as 'none' | 'code' | 'sub' | 'canceled', reason: '' }; if (session && pbToken) { famId = session.famId; await paydayCheck(famId, pbToken); chat = await resolveChatIdentity(session, pbToken); // fams is superadmin-only (non-realtime). Fetched server-side for both // roles; also recomputes + persists the derived `active` flag. const res = await ensureFamAccess(famId).catch(() => null); if (res) { fam = res.fam; famAccess = res.access; } // Canonical URL: the [fam] segment must be the family SLUG, never the PB // id. If someone lands on /{famId}/... (a stale shortcut, bookmark, or a // login that fell back to the id), rewrite the first path segment to the // slug so the id is replaced everywhere it'd otherwise persist. const paramFam = event.params.fam; const canonicalSlug = fam?.slug; if (canonicalSlug && paramFam && paramFam !== canonicalSlug) { const rest = event.url.pathname.replace(`/${paramFam}`, '') || '/'; const qs = event.url.search; throw redirect(303, `/${encodeURIComponent(canonicalSlug)}${rest}${qs}`); } } return { // Canonical fam slug — from the URL param ([fam] routes). Client code // reads page.data.famSlug; never copy it into local $state. famSlug: event.params.fam || '', session: session ? { famId: session.famId, userId: session.id, memberName: session.name, memberColor: session.color || '', username: session.username || '', role: session.role } : null, isParent, role, famId, chat, pbToken, fam, famAccess }; }