This commit is contained in:
2026-02-04 12:51:41 +00:00
parent 4fdbfb0fb3
commit f1e13f87f6
19 changed files with 722 additions and 67 deletions

View File

@@ -6,6 +6,7 @@ import { z } from 'zod'
import { generateSearchQueries, getDefaultPlatforms } from '@/lib/query-generator'
import { executeSearches, scoreOpportunities } from '@/lib/search-executor'
import type { EnhancedProductAnalysis, SearchConfig, PlatformConfig } from '@/lib/types'
import { logServer } from "@/lib/server-logger";
const searchSchema = z.object({
projectId: z.string(),
@@ -23,12 +24,15 @@ const searchSchema = z.object({
})),
strategies: z.array(z.string()),
maxResults: z.number().default(50)
minAgeDays: z.number().min(0).max(365).optional(),
maxAgeDays: z.number().min(0).max(365).optional()
})
})
export async function POST(request: NextRequest) {
let jobId: string | undefined
try {
const requestId = request.headers.get("x-request-id") ?? undefined;
if (!(await isAuthenticatedNextjs())) {
const redirectUrl = new URL("/auth", request.url);
const referer = request.headers.get("referer");
@@ -41,9 +45,21 @@ export async function POST(request: NextRequest) {
const parsed = searchSchema.parse(body)
const { projectId, config } = parsed
jobId = parsed.jobId
const ageFilters = {
minAgeDays: config.minAgeDays,
maxAgeDays: config.maxAgeDays,
}
if (!process.env.SERPER_API_KEY) {
const errorMessage = "SERPER_API_KEY is not configured. Add it to your environment to run searches."
await logServer({
level: "warn",
message: "Serper API key missing",
labels: ["api", "opportunities", "config", "warn"],
payload: { projectId },
requestId,
source: "api/opportunities",
});
if (jobId) {
await fetchMutation(
api.searchJobs.update,
@@ -84,19 +100,43 @@ export async function POST(request: NextRequest) {
const analysis = searchContext.context as EnhancedProductAnalysis
console.log('🔍 Starting opportunity search...')
console.log(` Product: ${analysis.productName}`)
console.log(` Platforms: ${config.platforms.filter(p => p.enabled).map(p => p.name).join(', ')}`)
console.log(` Strategies: ${config.strategies.join(', ')}`)
await logServer({
level: "info",
message: "Starting opportunity search",
labels: ["api", "opportunities", "start"],
payload: {
projectId,
productName: analysis.productName,
platforms: config.platforms.filter((p) => p.enabled).map((p) => p.name),
strategies: config.strategies,
filters: ageFilters,
},
requestId,
source: "api/opportunities",
});
// Generate queries
console.log(' Generating search queries...')
await logServer({
level: "info",
message: "Generating search queries",
labels: ["api", "opportunities", "queries"],
payload: { projectId },
requestId,
source: "api/opportunities",
});
const enforcedConfig: SearchConfig = {
...(config as SearchConfig),
maxResults: Math.min((config as SearchConfig).maxResults || 50, 50),
}
const queries = generateSearchQueries(analysis as EnhancedProductAnalysis, enforcedConfig)
console.log(` ✓ Generated ${queries.length} queries`)
await logServer({
level: "info",
message: "Generated search queries",
labels: ["api", "opportunities", "queries"],
payload: { projectId, count: queries.length },
requestId,
source: "api/opportunities",
});
if (jobId) {
await fetchMutation(
api.searchJobs.update,
@@ -106,9 +146,23 @@ export async function POST(request: NextRequest) {
}
// Execute searches
console.log(' Executing searches...')
const searchResults = await executeSearches(queries)
console.log(` ✓ Found ${searchResults.length} raw results`)
await logServer({
level: "info",
message: "Executing searches",
labels: ["api", "opportunities", "search"],
payload: { projectId, queryCount: queries.length },
requestId,
source: "api/opportunities",
});
const searchResults = await executeSearches(queries, ageFilters)
await logServer({
level: "info",
message: "Searches complete",
labels: ["api", "opportunities", "search"],
payload: { projectId, rawResults: searchResults.length },
requestId,
source: "api/opportunities",
});
if (jobId) {
await fetchMutation(
api.searchJobs.update,
@@ -130,9 +184,23 @@ export async function POST(request: NextRequest) {
const filteredResults = searchResults.filter((result) => !existingSet.has(result.url))
// Score and rank
console.log(' Scoring opportunities...')
await logServer({
level: "info",
message: "Scoring opportunities",
labels: ["api", "opportunities", "score"],
payload: { projectId, candidateResults: filteredResults.length },
requestId,
source: "api/opportunities",
});
const opportunities = scoreOpportunities(filteredResults, analysis as EnhancedProductAnalysis)
console.log(` ✓ Scored ${opportunities.length} opportunities`)
await logServer({
level: "info",
message: "Opportunities scored",
labels: ["api", "opportunities", "score"],
payload: { projectId, scored: opportunities.length },
requestId,
source: "api/opportunities",
});
if (jobId) {
await fetchMutation(
api.searchJobs.update,
@@ -179,7 +247,14 @@ export async function POST(request: NextRequest) {
} catch (error: any) {
const errorMessage =
error instanceof Error ? error.message : typeof error === "string" ? error : "Search failed"
console.error("❌ Opportunity search error:", errorMessage)
await logServer({
level: "error",
message: "Opportunity search error",
labels: ["api", "opportunities", "error"],
payload: { message: errorMessage },
requestId: request.headers.get("x-request-id") ?? undefined,
source: "api/opportunities",
});
if (jobId) {
try {