186 lines
5.8 KiB
TypeScript
186 lines
5.8 KiB
TypeScript
import { fail, redirect } from '@sveltejs/kit';
|
|
import { pbUser, createPbClient } from '$lib/server/pocketbase';
|
|
import { createServices } from '$lib/server/services';
|
|
import { getPlatformFlags } from '$lib/server/platform';
|
|
import { pbAdmin } from '$lib/server/pocketbase';
|
|
|
|
export async function load(event) {
|
|
const session = event.locals.user;
|
|
const demoMode = (await getPlatformFlags()).demo;
|
|
const isDemoParent = session?.username === 'demo';
|
|
const famSlug = event.params.fam;
|
|
const username = event.params.username;
|
|
|
|
// Demo parent visiting a child's route → load child kanban view.
|
|
const demoParentVisitingChild = demoMode && isDemoParent && session?.username !== username;
|
|
|
|
// Parent (role=parent, session auth) → admin overview.
|
|
// Branch on role, NOT presence: a child who OTP-logged-in also has a
|
|
// `users` session (locals.user) and must not hit the parent path.
|
|
if (session && session.role === 'parent' && !demoParentVisitingChild) {
|
|
if (!(demoMode && isDemoParent)) {
|
|
if (session.name && session.username && session.username !== username) {
|
|
throw redirect(303, `/${famSlug}/${session.username}`);
|
|
}
|
|
}
|
|
const s = createServices(pbUser(event), session);
|
|
const [
|
|
members,
|
|
templates,
|
|
assigned,
|
|
summary,
|
|
fam,
|
|
rewards,
|
|
bonusConfigs,
|
|
completions,
|
|
settings
|
|
] = await Promise.all([
|
|
s.crud.list('members', session.famId),
|
|
s.crud.list('chore-templates', session.famId),
|
|
s.crud.list('assigned-chores', session.famId),
|
|
s.fam.weeklySummary(session.famId),
|
|
s.fam.get(session.famId),
|
|
s.crud.list('rewards', session.famId),
|
|
s.crud.list('bonus-configs', session.famId),
|
|
s.crud.list('completions', session.famId),
|
|
s.settings.get(session.famId)
|
|
]);
|
|
return {
|
|
role: 'parent',
|
|
members,
|
|
templates,
|
|
assigned,
|
|
summary,
|
|
fam,
|
|
famSlug,
|
|
rewards,
|
|
bonusConfigs,
|
|
completions,
|
|
settings
|
|
};
|
|
}
|
|
|
|
// Child (users auth, role=child) → kanban.
|
|
// Also demo parent visiting a child's route.
|
|
|
|
// A child should always land on their own kanban route.
|
|
if (!(demoMode && isDemoParent)) {
|
|
if (session?.username && session.username !== username) {
|
|
throw redirect(303, `/${famSlug}/${session.username}`);
|
|
}
|
|
}
|
|
|
|
const pbToken = event.cookies.get('pb_token') || '';
|
|
const famId = session?.famId || '';
|
|
// When a demo parent visits a child's route, session.id is the parent's PB
|
|
// record. Look up the actual child user by username so chores load correctly.
|
|
const childId = demoParentVisitingChild
|
|
? (await pbAdmin.getList('users', `famId='${famId}' && username='${famSlug}:${username}'`))?.[0]?.id || ''
|
|
: session?.id || '';
|
|
|
|
const empty = {
|
|
role: 'child' as const,
|
|
token: pbToken,
|
|
memberId: childId,
|
|
famId,
|
|
verified: false,
|
|
memberName: session?.name || '',
|
|
memberColor: session?.color || '',
|
|
templates: [] as never[],
|
|
assigned: [] as never[],
|
|
completions: [] as never[],
|
|
rewards: [] as never[],
|
|
bonusConfigs: [] as never[],
|
|
tallies: {} as Record<string, unknown>,
|
|
payday: 1,
|
|
paydayTime: '18:00',
|
|
timezone: 'auto'
|
|
};
|
|
|
|
if (!pbToken || !famId) return empty;
|
|
|
|
try {
|
|
const s = createServices(createPbClient(pbToken), session ?? undefined);
|
|
const chores = await s.chores.myChores(famId);
|
|
return {
|
|
role: 'child',
|
|
token: pbToken,
|
|
memberId: childId,
|
|
famId,
|
|
verified: true,
|
|
memberName: session?.name || '',
|
|
memberColor: session?.color || '',
|
|
templates: chores.templates || [],
|
|
assigned: chores.assigned || [],
|
|
completions: chores.completions || [],
|
|
rewards: chores.rewards || [],
|
|
bonusConfigs: chores.bonusConfigs || [],
|
|
tallies: {} as Record<string, unknown>,
|
|
payday: chores.payday,
|
|
paydayTime: chores.paydayTime || '18:00',
|
|
timezone: chores.timezone || 'auto'
|
|
};
|
|
} catch {
|
|
return empty;
|
|
}
|
|
}
|
|
|
|
export const actions = {
|
|
claim: async (event) => {
|
|
if (!event.locals.user) throw redirect(303, '/login');
|
|
const famId = event.locals.user.famId;
|
|
const fd = await event.request.formData();
|
|
const rewardId = fd.get('id') as string;
|
|
try {
|
|
const s = createServices(pbUser(event), event.locals.user);
|
|
const record = await s.rewards.approve(famId, rewardId);
|
|
return { record };
|
|
} catch (e) {
|
|
return { error: e instanceof Error ? e.message : 'Failed to claim reward' };
|
|
}
|
|
},
|
|
|
|
issueAll: async (event) => {
|
|
if (!event.locals.user) throw redirect(303, '/login');
|
|
const famId = event.locals.user.famId;
|
|
const fd = await event.request.formData();
|
|
const memberId = fd.get('memberId') as string;
|
|
try {
|
|
const s = createServices(pbUser(event), event.locals.user);
|
|
const result = await s.rewards.issueAll(famId, memberId);
|
|
return { count: result.count };
|
|
} catch (e) {
|
|
return { error: e instanceof Error ? e.message : 'Failed to issue rewards' };
|
|
}
|
|
},
|
|
|
|
revoke: async (event) => {
|
|
if (!event.locals.user) throw redirect(303, '/login');
|
|
const famId = event.locals.user.famId;
|
|
const fd = await event.request.formData();
|
|
const completionId = fd.get('id') as string;
|
|
try {
|
|
const s = createServices(pbUser(event), event.locals.user);
|
|
await s.completions.revoke(famId, completionId);
|
|
return { revoked: true, id: completionId };
|
|
} catch (e) {
|
|
return { error: e instanceof Error ? e.message : 'Failed to revoke' };
|
|
}
|
|
},
|
|
|
|
trigger: async (event) => {
|
|
if (!event.locals.user) throw redirect(303, '/login');
|
|
const famId = event.locals.user.famId;
|
|
const fd = await event.request.formData();
|
|
const configId = fd.get('configId') as string;
|
|
const memberId = fd.get('memberId') as string;
|
|
if (!configId) return { error: 'Config ID required' };
|
|
try {
|
|
const s = createServices(pbUser(event), event.locals.user);
|
|
const result = await s.bonuses.trigger(famId, configId, memberId || undefined);
|
|
return { records: result.records || [] };
|
|
} catch (e) {
|
|
return { error: e instanceof Error ? e.message : 'Failed to trigger' };
|
|
}
|
|
}
|
|
}; |