Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | 14x 14x 14x 14x 13x 13x 12x 9x 9x 9x 9x 9x 14x 14x | import { error, redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { sortCompletedWorkouts, type CompletedWorkoutRow } from './historyList';
// ---------------------------------------------------------------------------
// Load function
//
// Authorization: request-scoped `locals.supabase` carries the user JWT so RLS
// gates the query. Defence-in-depth: we also apply .eq('athlete_id', user.id)
// explicitly so the RLS policy and the query predicate both agree.
// ---------------------------------------------------------------------------
export const load: PageServerLoad = async ({
parent,
locals: { supabase, safeGetSession },
}) => {
// `parent()` inherits the root layout's session guard.
await parent();
// Defensive auth check — belt-and-suspenders (root layout also gates).
const { user } = await safeGetSession();
if (!user) redirect(303, '/login');
// Fetch up to 100 completed workouts for this athlete, newest first.
// Embedded workout_logs (0-or-1) provides the log summary fields we need.
// Pagination is deferred — no UI truncation indicator for this milestone.
const { data: rows, error: wErr } = await supabase
.from('workouts')
.select(
`
id, scheduled_date, session_type,
workout_logs ( overall_feeling, was_scaled, completed_at )
`,
)
.eq('athlete_id', user.id)
.eq('status', 'completed')
.order('scheduled_date', { ascending: false })
.order('id', { ascending: false })
.limit(100); // pagination deferred per plan; no truncation indicator in this milestone
if (wErr) error(500, 'Failed to load workout history');
// Normalise: flatten the embedded workout_logs relation (0-or-1) to a flat view.
// PostgREST detects the to-one relationship from the UNIQUE(workout_id) constraint
// on workout_logs and embeds it as a single object (not an array), so handle both
// shapes: object → use directly, array → take the first element. The old
// array-only assumption silently dropped overall_feeling + was_scaled (#422 E2E).
// Capitalise session_type once server-side (single source of truth).
const mapped: CompletedWorkoutRow[] = (rows ?? []).map((row) => {
const wl = row.workout_logs as unknown;
const log = (Array.isArray(wl) ? (wl[0] ?? null) : (wl ?? null)) as {
overall_feeling?: string | null;
was_scaled?: boolean | null;
} | null;
const sessionType = row.session_type as string;
const sessionTypeDisplay = sessionType.charAt(0).toUpperCase() + sessionType.slice(1);
return {
id: row.id,
scheduled_date: row.scheduled_date,
session_type: sessionType,
session_type_display: sessionTypeDisplay,
overall_feeling: log?.overall_feeling ?? null,
was_scaled: log?.was_scaled ?? false,
};
});
// Re-assert ordering rule defensively (DB is primary/efficient path; helper
// encodes the same rule for testability — mirrors selectNextWorkout precedent).
const workouts = sortCompletedWorkouts(mapped);
return { workouts };
};
|