import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/db"; import { decrypt } from "@/lib/utils/encryption"; import { searchPlacesMultiQuery } from "@/lib/services/googlemaps"; import { bulkSearchDomains, type DecisionMakerCategory } from "@/lib/services/anymailfinder"; 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 mapsCredential = await prisma.apiCredential.findUnique({ where: { service: "googlemaps" } }); if (!mapsCredential?.value) { return NextResponse.json({ error: "Google Maps API key not configured" }, { status: 400 }); } const mapsApiKey = decrypt(mapsCredential.value); if (enrichEmails) { const anymailCred = await prisma.apiCredential.findUnique({ where: { service: "anymailfinder" } }); if (!anymailCred?.value) { 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 anymailCred = await prisma.apiCredential.findUnique({ where: { service: "anymailfinder" } }); if (!anymailCred?.value) throw new Error("Anymailfinder key missing"); const anymailKey = decrypt(anymailCred.value); 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 }, }); } } catch (err) { const message = err instanceof Error ? err.message : String(err); await prisma.job.update({ where: { id: jobId }, data: { status: "failed", error: message }, }); } }