diff --git a/app/api/analysis/route.ts b/app/api/analysis/route.ts index ff9a803..86c54c8 100644 --- a/app/api/analysis/route.ts +++ b/app/api/analysis/route.ts @@ -13,6 +13,7 @@ import { requireAuthUser, unauthorizedResponse, } from "@/lib/auth-helpers"; +import { mapCreateAnalysisError } from "@/lib/create-analysis-errors"; import { getMembership } from "@/lib/org/access"; import { createLogger } from "@/lib/logger"; import type { ApiResponse } from "@/types"; @@ -138,14 +139,18 @@ export async function POST( }, }); } catch (error) { - log.error("Failed to create analysis session", { error: String(error) }); + const mapped = mapCreateAnalysisError(error); + log.error("Failed to create analysis session", { + error: String(error), + code: mapped.code, + }); return NextResponse.json( { success: false as const, - error: "Failed to create analysis session.", - code: "INTERNAL_ERROR", + error: mapped.message, + code: mapped.code, }, - { status: 500 } + { status: mapped.status } ); } } diff --git a/lib/auth-helpers.ts b/lib/auth-helpers.ts index 58d462e..4223474 100644 --- a/lib/auth-helpers.ts +++ b/lib/auth-helpers.ts @@ -1,11 +1,19 @@ import { auth } from "@/auth"; import { NextResponse } from "next/server"; +import { prisma } from "@/lib/prisma"; import type { ApiResponse } from "@/types"; export async function requireAuthUser() { const session = await auth(); const userId = session?.user?.id; if (!userId) return null; + + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { id: true }, + }); + if (!user) return null; + return { session, userId }; } diff --git a/lib/create-analysis-errors.test.ts b/lib/create-analysis-errors.test.ts new file mode 100644 index 0000000..665aae9 --- /dev/null +++ b/lib/create-analysis-errors.test.ts @@ -0,0 +1,30 @@ +import { describe, it, expect } from "vitest"; +import { Prisma } from "@prisma/client"; +import { mapCreateAnalysisError } from "./create-analysis-errors"; + +describe("mapCreateAnalysisError", () => { + it("maps database connection failures to DB_UNAVAILABLE", () => { + const error = new Prisma.PrismaClientInitializationError( + "tenant/user postgres.eccziupihegciupibxis not found", + "5.0.0" + ); + + const mapped = mapCreateAnalysisError(error); + + expect(mapped.code).toBe("DB_UNAVAILABLE"); + expect(mapped.status).toBe(503); + expect(mapped.message).toMatch(/Database unavailable|Service temporarily unavailable/); + }); + + it("maps foreign key violations to UNAUTHORIZED", () => { + const error = new Prisma.PrismaClientKnownRequestError( + "Foreign key constraint failed", + { code: "P2003", clientVersion: "5.0.0" } + ); + + const mapped = mapCreateAnalysisError(error); + + expect(mapped.code).toBe("UNAUTHORIZED"); + expect(mapped.status).toBe(401); + }); +}); diff --git a/lib/create-analysis-errors.ts b/lib/create-analysis-errors.ts new file mode 100644 index 0000000..34bf6bc --- /dev/null +++ b/lib/create-analysis-errors.ts @@ -0,0 +1,52 @@ +import { Prisma } from "@prisma/client"; + +function isDatabaseUnavailable(error: unknown): boolean { + if (error instanceof Prisma.PrismaClientInitializationError) return true; + const message = String(error); + return ( + message.includes("ENOTFOUND") || + message.includes("Can't reach database server") || + message.includes("tenant/user") || + message.includes("P1001") + ); +} + +export function mapCreateAnalysisError(error: unknown): { + message: string; + status: number; + code: string; +} { + if (error instanceof Prisma.PrismaClientKnownRequestError) { + if (error.code === "P2003") { + return { + message: "Session expired. Please sign in again.", + status: 401, + code: "UNAUTHORIZED", + }; + } + if (error.code === "P2002") { + return { + message: "Could not allocate a unique share link. Please retry.", + status: 409, + code: "CONFLICT", + }; + } + } + + if (isDatabaseUnavailable(error)) { + return { + message: + process.env.NODE_ENV === "development" + ? "Database unavailable. Check DATABASE_URL / DIRECT_URL in .env.local and that your Supabase project is active." + : "Service temporarily unavailable. Try again shortly.", + status: 503, + code: "DB_UNAVAILABLE", + }; + } + + return { + message: "Failed to create analysis session.", + status: 500, + code: "INTERNAL_ERROR", + }; +}