From 018a2db9e8d5d17bd9f10530be44f29ed75f38fb Mon Sep 17 00:00:00 2001 From: Yash Kumar Date: Sat, 4 Jul 2026 02:14:56 +0530 Subject: [PATCH] fix: resolve AI CV generator internal server errors --- src/app/api/cv/analyze/route.ts | 56 +++++++++++++++++++++---- src/app/api/cv/generate/route.ts | 2 +- supabase/migrations/cv_intelligence.sql | 3 ++ 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/app/api/cv/analyze/route.ts b/src/app/api/cv/analyze/route.ts index a1b584cb9..e740309c9 100644 --- a/src/app/api/cv/analyze/route.ts +++ b/src/app/api/cv/analyze/route.ts @@ -13,7 +13,35 @@ const analyzeRateLimit = new Map< >(); const WINDOW_MS = 60 * 60 * 1000; // 1 hour -const MAX_REQUESTS = 3; +const MAX_REQUESTS = process.env.NODE_ENV === "development" ? 100 : 3; + +/** + * Resolves the GitHub login string for the current session. + * Falls back to calling GET /user if the session's githubLogin is absent + * or looks like a stale/numeric value (e.g. "219068160"). + */ +async function resolveGitHubLogin( + sessionLogin: string | undefined | null, + accessToken: string +): Promise { + // Valid login: non-empty and not purely numeric + if (sessionLogin && !/^\d+$/.test(sessionLogin)) { + return sessionLogin; + } + // Fallback: resolve via GitHub REST API + const res = await fetch("https://api.github.com/user", { + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: "application/vnd.github+json", + }, + cache: "no-store", + }); + if (!res.ok) { + throw new Error(`Failed to resolve GitHub login: ${res.status}`); + } + const data = (await res.json()) as { login: string }; + return data.login; +} /** * POST /api/cv/analyze @@ -74,7 +102,13 @@ export async function POST() { return NextResponse.json(response); } - /* ── 4. Fetch & classify ─────────────────────────────────── */ + /* ── 4. Resolve login & fetch contributions ───────────────── */ + const accessToken = session.accessToken as string; + const githubLogin = await resolveGitHubLogin( + session.githubLogin as string | undefined, + accessToken + ); + const { fetchContributionData } = await import( "@/lib/cv/cv-github-fetcher" ); @@ -82,18 +116,14 @@ export async function POST() { "@/lib/cv/cv-classifier" ); - const contributionData = await fetchContributionData( - session.accessToken as string, - session.githubId - ); - + const contributionData = await fetchContributionData(accessToken, githubLogin); const analysis = classifyContributions(contributionData); - /* ── 5. Cache in Supabase (24 h TTL) ─────────────────────── */ + /* ── 5. Cache in Supabase (24 h TTL) — non-fatal ─────────── */ const now = new Date(); const expiresAt = new Date(now.getTime() + 24 * 60 * 60 * 1000); - await supabaseAdmin.from("cv_analyses").upsert( + const { error: upsertError } = await supabaseAdmin.from("cv_analyses").upsert( { user_id: userId, analysis_data: analysis, @@ -103,6 +133,14 @@ export async function POST() { { onConflict: "user_id" } ); + if (upsertError) { + // Log but don't fail — analysis is computed, just not cached. + console.warn( + "CV analyze: Supabase cache upsert failed (non-fatal):", + upsertError.message + ); + } + /* ── 6. Respond ──────────────────────────────────────────── */ const response: CVAnalyzeResponse = { analysis, cached: false }; return NextResponse.json(response); diff --git a/src/app/api/cv/generate/route.ts b/src/app/api/cv/generate/route.ts index bc58c4587..ac35433ce 100644 --- a/src/app/api/cv/generate/route.ts +++ b/src/app/api/cv/generate/route.ts @@ -18,7 +18,7 @@ const generateRateLimit = new Map< >(); const WINDOW_MS = 60 * 60 * 1000; // 1 hour -const MAX_REQUESTS = 5; +const MAX_REQUESTS = process.env.NODE_ENV === "development" ? 100 : 5; /** * POST /api/cv/generate diff --git a/supabase/migrations/cv_intelligence.sql b/supabase/migrations/cv_intelligence.sql index c8ef3fcf1..d4c96f3fe 100644 --- a/supabase/migrations/cv_intelligence.sql +++ b/supabase/migrations/cv_intelligence.sql @@ -9,6 +9,7 @@ CREATE TABLE IF NOT EXISTS cv_analyses ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id TEXT NOT NULL, analysis_data JSONB NOT NULL, + generated_at TIMESTAMPTZ NOT NULL DEFAULT now(), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), expires_at TIMESTAMPTZ NOT NULL, CONSTRAINT cv_analyses_user_id_unique UNIQUE (user_id) @@ -52,7 +53,9 @@ CREATE TABLE IF NOT EXISTS cv_generated_content ( user_id TEXT NOT NULL, role TEXT NOT NULL, content JSONB NOT NULL, + generated_at TIMESTAMPTZ NOT NULL DEFAULT now(), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + expires_at TIMESTAMPTZ NOT NULL DEFAULT (now() + interval '24 hours'), CONSTRAINT cv_generated_content_user_role_unique UNIQUE (user_id, role) );