Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 47 additions & 9 deletions src/app/api/cv/analyze/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
// 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
Expand Down Expand Up @@ -74,26 +102,28 @@ 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"
);
const { classifyContributions } = await import(
"@/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,
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/cv/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions supabase/migrations/cv_intelligence.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
);

Expand Down
Loading