dates
This commit is contained in:
@@ -2,6 +2,8 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { isAuthenticatedNextjs } from "@convex-dev/auth/nextjs/server";
|
||||
import { z } from 'zod'
|
||||
import type { EnhancedProductAnalysis, Opportunity, DorkQuery } from '@/lib/types'
|
||||
import { logServer } from "@/lib/server-logger";
|
||||
import { appendSerperAgeModifiers, SerperAgeFilter } from "@/lib/serper-date-filters";
|
||||
|
||||
// Search result from any source
|
||||
interface SearchResult {
|
||||
@@ -31,11 +33,14 @@ const bodySchema = z.object({
|
||||
problem: z.string(),
|
||||
searchTerms: z.array(z.string())
|
||||
}))
|
||||
})
|
||||
}),
|
||||
minAgeDays: z.number().min(0).max(365).optional(),
|
||||
maxAgeDays: z.number().min(0).max(365).optional(),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
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");
|
||||
@@ -45,16 +50,34 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const { analysis } = bodySchema.parse(body)
|
||||
const { analysis, minAgeDays, maxAgeDays } = bodySchema.parse(body)
|
||||
const ageFilters: SerperAgeFilter = {
|
||||
minAgeDays,
|
||||
maxAgeDays,
|
||||
}
|
||||
|
||||
if (!process.env.SERPER_API_KEY) {
|
||||
await logServer({
|
||||
level: "warn",
|
||||
message: "Serper API key missing",
|
||||
labels: ["api", "search", "config", "warn"],
|
||||
requestId,
|
||||
source: "api/search",
|
||||
});
|
||||
return NextResponse.json(
|
||||
{ error: 'SERPER_API_KEY is not configured. Add it to your environment to run searches.' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
console.log(`🔍 Finding opportunities for: ${analysis.productName}`)
|
||||
await logServer({
|
||||
level: "info",
|
||||
message: "Finding opportunities",
|
||||
labels: ["api", "search", "start"],
|
||||
payload: { productName: analysis.productName, filters: ageFilters },
|
||||
requestId,
|
||||
source: "api/search",
|
||||
});
|
||||
|
||||
// Sort queries by priority
|
||||
const sortedQueries = analysis.dorkQueries
|
||||
@@ -69,22 +92,50 @@ export async function POST(request: NextRequest) {
|
||||
// Execute searches
|
||||
for (const query of sortedQueries) {
|
||||
try {
|
||||
console.log(` Searching: ${query.query.substring(0, 60)}...`)
|
||||
const results = await searchGoogle(query.query, 5)
|
||||
await logServer({
|
||||
level: "info",
|
||||
message: "Searching query",
|
||||
labels: ["api", "search", "query"],
|
||||
payload: { query: query.query, platform: query.platform },
|
||||
requestId,
|
||||
source: "api/search",
|
||||
});
|
||||
const results = await searchGoogle(query.query, 5, ageFilters, requestId)
|
||||
allResults.push(...results)
|
||||
|
||||
// Small delay to avoid rate limiting
|
||||
await new Promise(r => setTimeout(r, 500))
|
||||
} catch (e) {
|
||||
console.error(` Search failed for query: ${query.query.substring(0, 40)}`)
|
||||
await logServer({
|
||||
level: "error",
|
||||
message: "Search failed for query",
|
||||
labels: ["api", "search", "query", "error"],
|
||||
payload: { query: query.query, error: String(e) },
|
||||
requestId,
|
||||
source: "api/search",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` Found ${allResults.length} raw results`)
|
||||
await logServer({
|
||||
level: "info",
|
||||
message: "Search complete",
|
||||
labels: ["api", "search", "results"],
|
||||
payload: { rawResults: allResults.length },
|
||||
requestId,
|
||||
source: "api/search",
|
||||
});
|
||||
|
||||
// Analyze and score opportunities
|
||||
const opportunities = await analyzeOpportunities(allResults, analysis as EnhancedProductAnalysis)
|
||||
console.log(` ✓ Analyzed ${opportunities.length} opportunities`)
|
||||
await logServer({
|
||||
level: "info",
|
||||
message: "Opportunities analyzed",
|
||||
labels: ["api", "search", "analyze"],
|
||||
payload: { analyzed: opportunities.length },
|
||||
requestId,
|
||||
source: "api/search",
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
@@ -100,7 +151,18 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Search error:', error)
|
||||
await logServer({
|
||||
level: "error",
|
||||
message: "Search error",
|
||||
labels: ["api", "search", "error"],
|
||||
payload: {
|
||||
message: error?.message,
|
||||
stack: error?.stack,
|
||||
filters: ageFilters,
|
||||
},
|
||||
requestId: request.headers.get("x-request-id") ?? undefined,
|
||||
source: "api/search",
|
||||
});
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to find opportunities' },
|
||||
@@ -109,23 +171,42 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
async function searchGoogle(query: string, num: number): Promise<SearchResult[]> {
|
||||
return searchSerper(query, num)
|
||||
async function searchGoogle(
|
||||
query: string,
|
||||
num: number,
|
||||
filters?: SerperAgeFilter,
|
||||
requestId?: string
|
||||
): Promise<SearchResult[]> {
|
||||
return searchSerper(query, num, filters, requestId)
|
||||
}
|
||||
|
||||
async function searchSerper(query: string, num: number): Promise<SearchResult[]> {
|
||||
async function searchSerper(
|
||||
query: string,
|
||||
num: number,
|
||||
filters?: SerperAgeFilter,
|
||||
requestId?: string
|
||||
): Promise<SearchResult[]> {
|
||||
const filteredQuery = appendSerperAgeModifiers(query, filters)
|
||||
const response = await fetch('https://google.serper.dev/search', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-API-KEY': process.env.SERPER_API_KEY!,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ q: query, num })
|
||||
body: JSON.stringify({ q: filteredQuery, num })
|
||||
})
|
||||
|
||||
if (!response.ok) throw new Error('Serper API error')
|
||||
|
||||
const data = await response.json()
|
||||
await logServer({
|
||||
level: "info",
|
||||
message: "Serper response received",
|
||||
labels: ["api", "search", "serper", "response"],
|
||||
payload: { query: filteredQuery, num, filters, data },
|
||||
requestId,
|
||||
source: "api/search",
|
||||
});
|
||||
return (data.organic || []).map((r: any) => ({
|
||||
title: r.title,
|
||||
url: r.link,
|
||||
|
||||
Reference in New Issue
Block a user