import { NextRequest, NextResponse } from 'next/server' import { convexAuthNextjsToken, isAuthenticatedNextjs } from "@convex-dev/auth/nextjs/server"; import { fetchQuery } from "convex/nextjs"; import { api } from "@/convex/_generated/api"; 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' const searchSchema = z.object({ projectId: z.string(), config: z.object({ platforms: z.array(z.object({ id: z.string(), name: z.string(), icon: z.string(), enabled: z.boolean(), searchTemplate: z.string(), rateLimit: z.number() })), strategies: z.array(z.string()), intensity: z.enum(['broad', 'balanced', 'targeted']), maxResults: z.number().default(50) }) }) export async function POST(request: NextRequest) { try { if (!(await isAuthenticatedNextjs())) { const redirectUrl = new URL("/auth", request.url); const referer = request.headers.get("referer"); const nextPath = referer ? new URL(referer).pathname + new URL(referer).search : "/"; redirectUrl.searchParams.set("next", nextPath); return NextResponse.redirect(redirectUrl); } const body = await request.json() const { projectId, config } = searchSchema.parse(body) const token = await convexAuthNextjsToken(); const searchContext = await fetchQuery( api.projects.getSearchContext, { projectId: projectId as any }, { token } ); if (!searchContext.context) { return NextResponse.json( { error: 'No analysis available for selected sources.' }, { status: 400 } ); } 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(', ')}`) // Generate queries console.log(' Generating search queries...') const queries = generateSearchQueries(analysis as EnhancedProductAnalysis, config as SearchConfig) console.log(` ✓ Generated ${queries.length} queries`) // Execute searches console.log(' Executing searches...') const searchResults = await executeSearches(queries) console.log(` ✓ Found ${searchResults.length} raw results`) // Score and rank console.log(' Scoring opportunities...') const opportunities = scoreOpportunities(searchResults, analysis as EnhancedProductAnalysis) console.log(` ✓ Scored ${opportunities.length} opportunities`) return NextResponse.json({ success: true, data: { opportunities: opportunities.slice(0, 50), stats: { queriesGenerated: queries.length, rawResults: searchResults.length, opportunitiesFound: opportunities.length, highRelevance: opportunities.filter(o => o.relevanceScore >= 0.7).length, averageScore: opportunities.length > 0 ? opportunities.reduce((a, o) => a + o.relevanceScore, 0) / opportunities.length : 0 }, queries: queries.map(q => ({ query: q.query, platform: q.platform, strategy: q.strategy, priority: q.priority })), missingSources: searchContext.missingSources ?? [] } }) } catch (error: any) { console.error('❌ Opportunity search error:', error) if (error.name === 'ZodError') { return NextResponse.json( { error: 'Invalid request format', details: error.errors }, { status: 400 } ) } return NextResponse.json( { error: error.message || 'Failed to search for opportunities' }, { status: 500 } ) } } // Get default configuration export async function GET(request: NextRequest) { if (!(await isAuthenticatedNextjs())) { const redirectUrl = new URL("/auth", request.url); const referer = request.headers.get("referer"); const nextPath = referer ? new URL(referer).pathname + new URL(referer).search : "/"; redirectUrl.searchParams.set("next", nextPath); return NextResponse.redirect(redirectUrl); } const defaultPlatforms = getDefaultPlatforms() return NextResponse.json({ platforms: Object.entries(defaultPlatforms).map(([id, config]) => ({ id, ...config })), strategies: [ { id: 'direct-keywords', name: 'Direct Keywords', description: 'Search for people looking for your product category' }, { id: 'problem-pain', name: 'Problem/Pain', description: 'Find people experiencing problems you solve' }, { id: 'competitor-alternative', name: 'Competitor Alternatives', description: 'People looking to switch from competitors' }, { id: 'how-to', name: 'How-To/Tutorials', description: 'People learning about solutions' }, { id: 'emotional-frustrated', name: 'Frustration Posts', description: 'Emotional posts about pain points' }, { id: 'comparison', name: 'Comparisons', description: '"X vs Y" comparison posts' }, { id: 'recommendation', name: 'Recommendations', description: '"What do you use" recommendation requests' } ] }) }