semillero-2-AI-ATS/app/webhook-test/route.ts

145 lines
4.9 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { createServerSupabaseClient } from "@/lib/supabase";
import { generateEmbedding } from "@/lib/embeddings";
import fs from "fs/promises";
import path from "path";
export async function GET(request: NextRequest) {
try {
const supabase = createServerSupabaseClient();
const origin = request.nextUrl.origin;
// 1. Fetch or create a Job Vacancy for testing
let job = null;
const { data: existingJobs, error: jobsFetchError } = await supabase
.from("jobs")
.select("*")
.limit(1);
if (jobsFetchError) {
return NextResponse.json({ error: `Failed to fetch jobs: ${jobsFetchError.message}` }, { status: 500 });
}
if (existingJobs && existingJobs.length > 0) {
job = existingJobs[0];
} else {
// Create a default job if none exist
const title = "Senior AI Research Engineer";
const requirementsText = "We are seeking a Senior AI Research Engineer with expert knowledge in Large Language Models, PyTorch, LangChain, and agentic reasoning architectures.";
const embedding = await generateEmbedding(requirementsText);
const { data: newJob, error: jobInsertError } = await supabase
.from("jobs")
.insert({
title,
requirements: { text: requirementsText },
embedding,
})
.select("*")
.single();
if (jobInsertError || !newJob) {
return NextResponse.json({ error: `Failed to create mock job: ${jobInsertError?.message}` }, { status: 500 });
}
job = newJob;
}
// 2. Read the Curriculum Vitae test asset
const cvPath = path.join(process.cwd(), "test-assets", "curriculum-vitae-english.pdf");
let fileBuffer;
try {
fileBuffer = await fs.readFile(cvPath);
} catch (fsError) {
return NextResponse.json({
error: `Could not read test CV asset at ${cvPath}. Please ensure it exists. Detailed error: ${fsError instanceof Error ? fsError.message : fsError}`
}, { status: 400 });
}
const testMode = request.nextUrl.searchParams.get("testMode") !== "false";
// 3. Prepare Form Data for parse-cv endpoint
const formData = new FormData();
const fileBlob = new Blob([fileBuffer], { type: "application/pdf" });
formData.append("file", fileBlob, "curriculum-vitae-english.pdf");
formData.append("jobId", job.id);
formData.append("isTest", testMode ? "true" : "false");
// 4. Send POST request to local parse-cv API
const parseCvUrl = `${origin}/candidates/api/parse-cv`;
let parseResult;
try {
const parseResponse = await fetch(parseCvUrl, {
method: "POST",
body: formData,
});
if (!parseResponse.ok) {
const errText = await parseResponse.text();
return NextResponse.json({
error: `Parse-CV API returned non-OK status: ${parseResponse.status} - ${errText}`
}, { status: 500 });
}
parseResult = await parseResponse.json();
} catch (fetchError) {
return NextResponse.json({
error: `Failed to call local parse-cv route: ${fetchError instanceof Error ? fetchError.message : fetchError}`
}, { status: 500 });
}
const { candidateId, interviewId, n8nResponse } = parseResult;
// 5. Verification - Poll the Supabase `scores` table to verify n8n updated the DB
let scoreRecord = null;
let verificationAttempts = 0;
const maxAttempts = 10;
const delayMs = 1500;
for (let i = 0; i < maxAttempts; i++) {
verificationAttempts++;
// Wait for n8n execution to finish and write back
await new Promise((resolve) => setTimeout(resolve, delayMs));
const { data: score, error: scoreError } = await supabase
.from("scores")
.select("*")
.eq("candidate_id", candidateId)
.eq("interview_id", interviewId)
.maybeSingle();
if (score && !scoreError) {
scoreRecord = score;
break;
}
}
return NextResponse.json({
status: scoreRecord ? "success" : "completed_with_pending_evaluation",
message: scoreRecord
? "Pipeline test executed and verified successfully!"
: "Pipeline executed but AI evaluation write-back is pending or failed.",
testDetails: {
jobUsed: {
id: job.id,
title: job.title,
status: existingJobs && existingJobs.length > 0 ? "reused" : "created",
},
parseCvResponse: {
candidateId,
interviewId,
n8nWebhookResponse: n8nResponse,
},
verification: {
attempts: verificationAttempts,
verified: !!scoreRecord,
scoreData: scoreRecord,
}
}
});
} catch (error: unknown) {
console.error("Error in webhook-test API:", error);
const errorMessage = error instanceof Error ? error.message : "Internal Server Error";
return NextResponse.json({ error: errorMessage }, { status: 500 });
}
}