diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts index 18c3d63..45ff7ff 100644 --- a/frontend/src/hooks.server.ts +++ b/frontend/src/hooks.server.ts @@ -89,9 +89,15 @@ export const handle: Handle = async ({ event, resolve }) => { setActiveChild(event.cookies, activeId); return resolve(event); } - } catch { + console.error( + `[diag] hooks child wrong-role activeId=${activeId} role=${record.role} path=${event.url.pathname}` + ); + } catch (e) { // Expired/revoked/malformed — leave the cookie; the picker switch // re-mints it server-side. Fall through to the single session. + console.error( + `[diag] hooks child authRefresh-fail activeId=${activeId} path=${event.url.pathname} err=${e instanceof Error ? e.message : e}` + ); } } } @@ -109,8 +115,11 @@ export const handle: Handle = async ({ event, resolve }) => { if (freshToken !== token) { setSessionCookie(event.cookies, freshToken); } - } catch { + } catch (e) { // Expired, malformed, or revoked — drop it and treat as logged out. + console.error( + `[diag] hooks single authRefresh-fail path=${event.url.pathname} err=${e instanceof Error ? e.message : e}` + ); clearSessionCookie(event.cookies); } } diff --git a/frontend/src/lib/server/services/completions.ts b/frontend/src/lib/server/services/completions.ts index 41c38cc..cf2e2f9 100644 --- a/frontend/src/lib/server/services/completions.ts +++ b/frontend/src/lib/server/services/completions.ts @@ -67,10 +67,16 @@ export async function toggle(pb: any, famId: string, memberId: string, body: { a let chore: any; try { chore = await pbAdmin.getOne('assigned_chores', assignedChoreId); - } catch { + } catch (e) { + console.error( + `[diag] toggle CHORE_GONE(getOne-fail) member=${memberId} fam=${famId} chore=${assignedChoreId} date=${date} err=${e instanceof Error ? e.message : e}` + ); throw new Error('CHORE_GONE: this chore was changed — refresh to get the latest list'); } if (!chore || chore.famId !== famId) { + console.error( + `[diag] toggle CHORE_GONE(fam-mismatch) member=${memberId} sessionFam=${famId} chore=${assignedChoreId} choreFam=${chore?.famId} choreMember=${chore?.memberId} date=${date}` + ); throw new Error('CHORE_GONE: this chore was changed — refresh to get the latest list'); } const isTodo = chore?.isTodo; diff --git a/frontend/src/routes/api/completions/toggle/+server.ts b/frontend/src/routes/api/completions/toggle/+server.ts index 0b1fe7e..e8ff735 100644 --- a/frontend/src/routes/api/completions/toggle/+server.ts +++ b/frontend/src/routes/api/completions/toggle/+server.ts @@ -5,13 +5,23 @@ import { createServices } from '$lib/server/services'; export async function POST(event: RequestEvent) { const u = event.locals.user; - if (!u || !event.locals.pbToken) throw error(401, 'Unauthorized'); + if (!u || !event.locals.pbToken) { + console.error(`[diag] toggle 401 role=${u?.role} hasToken=${!!event.locals.pbToken}`); + throw error(401, 'Unauthorized'); + } const pb = createPbClient(event.locals.pbToken); const body = await event.request.json().catch(() => ({})); try { const s = createServices(pb, { id: u.id, role: u.role }); - return json(await s.completions.toggle(u.famId, body)); + const out = await s.completions.toggle(u.famId, body); + console.log( + `[diag] toggle ok member=${u.id} chore=${body.assignedChoreId} date=${body.date} completed=${(out as any)?.completed}` + ); + return json(out); } catch (e) { + console.error( + `[diag] toggle 400 member=${u.id} role=${u.role} fam=${u.famId} chore=${body.assignedChoreId} date=${body.date} err=${e instanceof Error ? e.message : e}` + ); return json({ error: e instanceof Error ? e.message : 'toggle failed' }, { status: 400 }); } } \ No newline at end of file diff --git a/frontend/src/routes/api/debug/chore/+server.ts b/frontend/src/routes/api/debug/chore/+server.ts new file mode 100644 index 0000000..a46c80d --- /dev/null +++ b/frontend/src/routes/api/debug/chore/+server.ts @@ -0,0 +1,52 @@ +import { json, error } from '@sveltejs/kit'; +import type { RequestEvent } from '@sveltejs/kit'; +import { createPbClient, pbAdmin } from '$lib/server/pocketbase'; + +// TEMP diagnostic for the recurring prod CHORE_GONE. Compares what the client +// claims against server truth from both angles (superuser getOne — the toggle +// path — and the user's own list read — the kanban path). Authenticated, +// own-fam only, no tokens ever logged or returned. +export async function GET(event: RequestEvent) { + const u = event.locals.user; + if (!u || !event.locals.pbToken) throw error(401, 'Unauthorized'); + const assignedChoreId = event.url.searchParams.get('assignedChoreId') || ''; + + let suChore: any = null; + let suError = ''; + if (assignedChoreId) { + try { + suChore = await pbAdmin.getOne('assigned_chores', assignedChoreId); + } catch (e) { + suError = e instanceof Error ? e.message : String(e); + } + } + + let userSeesIt: boolean | null = null; + let userListError = ''; + try { + const pb = createPbClient(event.locals.pbToken); + const list = await pb + .collection('assigned_chores') + .getFullList({ filter: `famId = '${u.famId}'` }); + if (assignedChoreId) userSeesIt = list.some((a: any) => a.id === assignedChoreId); + } catch (e) { + userListError = e instanceof Error ? e.message : String(e); + } + + const out = { + session: { id: u.id, role: u.role, famId: u.famId }, + asked: assignedChoreId, + superuser: suChore + ? { + found: true, + famId: suChore.famId, + memberId: suChore.memberId, + isTodo: !!suChore.isTodo, + famMatch: suChore.famId === u.famId + } + : { found: false, error: suError }, + userList: { seesIt: userSeesIt, error: userListError } + }; + console.log(`[diag] debug-chore ${JSON.stringify(out)}`); + return json(out); +} diff --git a/frontend/src/routes/api/todos/+server.ts b/frontend/src/routes/api/todos/+server.ts index 6a34e1b..9aac115 100644 --- a/frontend/src/routes/api/todos/+server.ts +++ b/frontend/src/routes/api/todos/+server.ts @@ -10,7 +10,10 @@ import { weekStart, addDaysStr, todayInTz, resolveTz } from '@shared/timezone'; // frequency, this-week start/completeBy (so it expires and purges naturally). export async function POST(event: RequestEvent) { const u = event.locals.user; - if (!u) throw error(401, 'Unauthorized'); + if (!u) { + console.error('[diag] todos 401 (no session)'); + throw error(401, 'Unauthorized'); + } const body = await event.request.json().catch(() => ({})); const name = typeof body.name === 'string' ? body.name.trim().slice(0, 80) : ''; if (!name) throw error(400, 'Give your todo a name'); @@ -43,8 +46,12 @@ export async function POST(event: RequestEvent) { startDate: today, completeBy }); + console.log(`[diag] todo ok member=${u.id} fam=${u.famId} todo=${record.id} name=${name}`); return json({ record }); } catch (e) { + console.error( + `[diag] todo 400 member=${u.id} role=${u.role} fam=${u.famId} name=${name} err=${e instanceof Error ? e.message : e}` + ); throw error(400, e instanceof Error ? e.message : 'Could not add todo'); } }