migrate auth v2 code

This commit is contained in:
JCEEE
2026-08-16 10:11:39 +01:00
parent 3dd94b8a7c
commit c73ced7894
45 changed files with 1965 additions and 1519 deletions
@@ -1,55 +1,81 @@
import { redirect } from '@sveltejs/kit';
import { hono } from '$lib/server/hono';
import type { RequestEvent } from '@sveltejs/kit';
import { pbUser } from '$lib/server/pocketbase';
import { pbAdmin } from '$lib/server/pocketbase';
import { hono } from '$lib/server/hono';
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) {
if (!event.locals.session) throw redirect(303, '/login');
const famId = event.locals.session.famId;
const famId = famIdOf(event);
const pb = pbUser(event);
const [members, fam, seasons] = await Promise.all([
hono.admin.list(event, 'members', famId),
hono.admin.fam(event, famId),
hono.admin.list(event, 'seasons', famId),
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 = {
regenInvite: async (event: RequestEvent) => {
if (!event.locals.session) throw redirect(303, '/login');
const famId = event.locals.session.famId;
return { newCode: (await hono.admin.regenInvite(event, famId)).inviteCode };
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' };
}
},
addMember: async (event: RequestEvent) => {
if (!event.locals.session) throw redirect(303, '/login');
const famId = event.locals.session.famId;
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') as string;
if (!name) return { error: 'Name required' };
await hono.admin.create(event, 'members', famId, { name, color: '#6366f1' });
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) => {
if (!event.locals.session) throw redirect(303, '/login');
const famId = event.locals.session.famId;
const famId = famIdOf(event);
const fd = await event.request.formData();
const name = fd.get('name') as string;
if (!name) return { error: 'Name required' };
return await hono.admin.renameFam(event, famId, name);
const record = await pbUser(event)
.collection('fams')
.update(famId, { name, slug: slugify(name) });
return { name: record.name, slug: record.slug };
},
deleteMember: async (event: RequestEvent) => {
if (!event.locals.session) throw redirect(303, '/login');
const famId = event.locals.session.famId;
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 hono.admin.remove(event, 'members', famId, id);
await pbUser(event).collection('users').delete(id);
return { ok: true };
},
updatePayday: async (event: RequestEvent) => {
if (!event.locals.session) throw redirect(303, '/login');
const famId = event.locals.session.famId;
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' };
@@ -59,43 +85,48 @@ export const actions = {
if (timezone && timezone !== 'auto' && !/^[A-Za-z_+-]+\/[A-Za-z_+-]+$/.test(timezone)) {
return { error: 'Timezone must be an IANA name or auto' };
}
return await hono.admin.updatePayday(event, famId, payday, paydayTime, timezone || undefined);
const patch: Record<string, unknown> = { 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) => {
if (!event.locals.session) throw redirect(303, '/login');
const famId = event.locals.session.famId;
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 hono.admin.create(event, 'seasons', famId, { name, color: color || '#6366f1', active: true });
return await pbUser(event)
.collection('seasons')
.create({ famId, name, color: color || '#6366f1', active: true });
},
deleteSeason: async (event: RequestEvent) => {
if (!event.locals.session) throw redirect(303, '/login');
const famId = event.locals.session.famId;
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 assigned = await hono.admin.list(event, 'assigned-chores', famId);
const toDelete = (Array.isArray(assigned) ? assigned : [])
.filter((a: any) => a.seasonIds?.includes(id));
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) =>
hono.admin.remove(event, 'assigned-chores', famId, a.id)
));
await hono.admin.remove(event, 'seasons', famId, 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 — still proxied to Hono.
completeWeek: async (event: RequestEvent) => {
if (!event.locals.session) throw redirect(303, '/login');
const famId = event.locals.session.famId;
const famId = famIdOf(event);
try {
const result = await hono.admin.request(event, 'POST', `/api/admin/${famId}/complete-week`);
return { success: true, result };
@@ -105,15 +136,14 @@ export const actions = {
},
generateData: async (event: RequestEvent) => {
if (!event.locals.session) throw redirect(303, '/login');
const famId = event.locals.session.famId;
const famId = famIdOf(event);
const fd = await event.request.formData();
const days = parseInt(fd.get('days') as string || '7', 10);
const days = parseInt((fd.get('days') as string) || '7', 10);
try {
const result = await hono.admin.request(event, 'POST', `/api/admin/${famId}/debug/generate-data`, { days });
return { success: true, result };
} catch (e) {
return { error: e instanceof Error ? e.message : 'Failed to generate data' };
}
},
};
}
};