import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/db"; import { getApiKey } from "@/lib/utils/apiKey"; import { searchPlacesMultiQuery } from "@/lib/services/googlemaps"; import { bulkSearchDomains, type DecisionMakerCategory } from "@/lib/services/anymailfinder"; import { sinkLeadsToVault } from "@/lib/services/leadVault"; export async function POST(req: NextRequest) { try { const body = await req.json() as { queries: string[]; maxResultsPerQuery: number; languageCode: string; categories: DecisionMakerCategory[]; enrichEmails: boolean; }; const { queries, maxResultsPerQuery, languageCode, categories, enrichEmails } = body; if (!queries?.length) { return NextResponse.json({ error: "No search queries provided" }, { status: 400 }); } const mapsApiKey = await getApiKey("googlemaps"); if (!mapsApiKey) return NextResponse.json({ error: "Google Maps API key not configured" }, { status: 400 }); if (enrichEmails && !(await getApiKey("anymailfinder"))) { return NextResponse.json({ error: "Anymailfinder API key not configured" }, { status: 400 }); } const job = await prisma.job.create({ data: { type: "maps", status: "running", config: JSON.stringify({ queries, maxResultsPerQuery, languageCode, categories, enrichEmails }), totalLeads: 0, }, }); runMapsEnrich(job.id, body, mapsApiKey).catch(console.error); return NextResponse.json({ jobId: job.id }); } catch (err) { console.error("POST /api/jobs/maps-enrich error:", err); return NextResponse.json({ error: "Failed to start job" }, { status: 500 }); } } async function runMapsEnrich( jobId: string, params: { queries: string[]; maxResultsPerQuery: number; languageCode: string; categories: DecisionMakerCategory[]; enrichEmails: boolean; }, mapsApiKey: string ) { try { // 1. Search Google Maps const places = await searchPlacesMultiQuery( params.queries, mapsApiKey, params.maxResultsPerQuery, params.languageCode, async (_done, _total) => { await prisma.job.update({ where: { id: jobId }, data: { totalLeads: _done } }); } ); await prisma.job.update({ where: { id: jobId }, data: { totalLeads: places.length }, }); // 2. Store raw Google Maps results immediately for (const place of places) { await prisma.leadResult.create({ data: { jobId, companyName: place.name || null, domain: place.domain || null, source: JSON.stringify({ address: place.address, phone: place.phone, website: place.website, placeId: place.placeId, }), }, }); } // 3. Optionally enrich with Anymailfinder if (params.enrichEmails && places.length > 0) { const anymailKey = await getApiKey("anymailfinder"); if (!anymailKey) throw new Error("Anymailfinder key missing"); const domains = places.filter(p => p.domain).map(p => p.domain!); // Map domain → placeId for updating results const domainToResultId = new Map(); const existingResults = await prisma.leadResult.findMany({ where: { jobId }, select: { id: true, domain: true }, }); for (const r of existingResults) { if (r.domain) domainToResultId.set(r.domain, r.id); } let emailsFound = 0; const enrichResults = await bulkSearchDomains( domains, params.categories, anymailKey, async (_completed, total) => { await prisma.job.update({ where: { id: jobId }, data: { totalLeads: total } }); } ); for (const result of enrichResults) { const hasEmail = !!result.valid_email; if (hasEmail) emailsFound++; const resultId = domainToResultId.get(result.domain || ""); if (!resultId) continue; await prisma.leadResult.update({ where: { id: resultId }, data: { contactName: result.person_full_name || null, contactTitle: result.person_job_title || null, email: result.email || null, confidence: result.valid_email ? 1.0 : result.email_status === "risky" ? 0.5 : 0, linkedinUrl: result.person_linkedin_url || null, }, }); await prisma.job.update({ where: { id: jobId }, data: { emailsFound } }); } await prisma.job.update({ where: { id: jobId }, data: { status: "complete", emailsFound, totalLeads: places.length }, }); } else { await prisma.job.update({ where: { id: jobId }, data: { status: "complete", totalLeads: places.length }, }); } // Sync to LeadVault const finalResults = await prisma.leadResult.findMany({ where: { jobId } }); await sinkLeadsToVault( finalResults.map(r => { let src: { phone?: string; address?: string } = {}; try { src = JSON.parse(r.source || "{}"); } catch { /* ignore */ } return { domain: r.domain, companyName: r.companyName, contactName: r.contactName, contactTitle: r.contactTitle, email: r.email, linkedinUrl: r.linkedinUrl, emailConfidence: r.confidence, phone: src.phone || null, address: src.address || null, }; }), "maps", params.queries.join(", "), jobId, ); } catch (err) { const message = err instanceof Error ? err.message : String(err); await prisma.job.update({ where: { id: jobId }, data: { status: "failed", error: message }, }); } }