Files
famdone/frontend/src/routes/[fam]/[username]/settings/+page.server.ts
T
2026-08-17 07:41:26 +01:00

151 lines
5.3 KiB
TypeScript

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<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) => {
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' };
}
}
};