From cadcf753a74b8f77b9920485de4fc2c1936f9859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Tue, 24 Mar 2026 23:37:05 +0100 Subject: [PATCH] Gracefully handle missing DB in Planner routes Wrap session API and loader in try/catch to return 503 instead of crashing the server when PostgreSQL is unavailable. This prevents E2E test failures in CI where no DB is running. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/planner/app/routes/api.sessions.ts | 49 +++++++++++++++---------- apps/planner/app/routes/session.$id.tsx | 21 +++++++---- 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/apps/planner/app/routes/api.sessions.ts b/apps/planner/app/routes/api.sessions.ts index 66c437b..1827f7a 100644 --- a/apps/planner/app/routes/api.sessions.ts +++ b/apps/planner/app/routes/api.sessions.ts @@ -15,29 +15,40 @@ export async function action({ request }: Route.ActionArgs) { gpx?: string; }; - const session = await createSession({ callbackUrl, callbackToken }); + try { + const session = await createSession({ callbackUrl, callbackToken }); - let initialWaypoints: Array<{ lat: number; lon: number; name?: string }> | undefined; - if (gpx) { - try { - const gpxData = await parseGpxAsync(gpx); - initialWaypoints = gpxData.waypoints; - } catch (_e) { - // Continue with empty session if GPX is invalid + let initialWaypoints: Array<{ lat: number; lon: number; name?: string }> | undefined; + if (gpx) { + try { + const gpxData = await parseGpxAsync(gpx); + initialWaypoints = gpxData.waypoints; + } catch { + // Continue with empty session if GPX is invalid + } } - } - return data( - { - sessionId: session.id, - url: `/session/${session.id}`, - initialWaypoints, - }, - { status: 201 }, - ); + return data( + { + sessionId: session.id, + url: `/session/${session.id}`, + initialWaypoints, + }, + { status: 201 }, + ); + } catch (e) { + return data( + { error: "Database unavailable", details: (e as Error).message }, + { status: 503 }, + ); + } } export async function loader(_args: Route.LoaderArgs) { - const sessions = await listSessions(); - return data({ sessions }); + try { + const sessions = await listSessions(); + return data({ sessions }); + } catch { + return data({ sessions: [], error: "Database unavailable" }); + } } diff --git a/apps/planner/app/routes/session.$id.tsx b/apps/planner/app/routes/session.$id.tsx index 101c881..67b3043 100644 --- a/apps/planner/app/routes/session.$id.tsx +++ b/apps/planner/app/routes/session.$id.tsx @@ -14,15 +14,20 @@ export function meta(_args: Route.MetaArgs) { } export async function loader({ params }: Route.LoaderArgs) { - const session = await getSession(params.id); - if (!session) { - throw data({ error: "Session not found" }, { status: 404 }); + try { + const session = await getSession(params.id); + if (!session) { + throw data({ error: "Session not found" }, { status: 404 }); + } + return data({ + sessionId: session.id, + callbackUrl: session.callbackUrl ?? null, + callbackToken: session.callbackToken ?? null, + }); + } catch (e) { + if (e instanceof Response) throw e; // Re-throw data() responses + throw data({ error: "Database unavailable" }, { status: 503 }); } - return data({ - sessionId: session.id, - callbackUrl: session.callbackUrl ?? null, - callbackToken: session.callbackToken ?? null, - }); } export default function SessionPage({ loaderData }: Route.ComponentProps) {