All files / src/routes/history +page.server.ts

100% Statements 16/16
87.5% Branches 14/16
100% Functions 2/2
100% Lines 14/14

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 };
};