feat: Implement analysis job tracking with progress timeline and enhanced data source status management.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { convexAuthNextjsToken, isAuthenticatedNextjs } from "@convex-dev/auth/nextjs/server";
|
||||
import { fetchQuery } from "convex/nextjs";
|
||||
import { fetchMutation, fetchQuery } from "convex/nextjs";
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import { z } from 'zod'
|
||||
import { generateSearchQueries, getDefaultPlatforms } from '@/lib/query-generator'
|
||||
@@ -9,13 +9,14 @@ import type { EnhancedProductAnalysis, SearchConfig, PlatformConfig } from '@/li
|
||||
|
||||
const searchSchema = z.object({
|
||||
projectId: z.string(),
|
||||
jobId: z.optional(z.string()),
|
||||
config: z.object({
|
||||
platforms: z.array(z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
icon: z.string(),
|
||||
icon: z.string().optional(),
|
||||
enabled: z.boolean(),
|
||||
searchTemplate: z.string(),
|
||||
searchTemplate: z.string().optional(),
|
||||
rateLimit: z.number()
|
||||
})),
|
||||
strategies: z.array(z.string()),
|
||||
@@ -25,6 +26,7 @@ const searchSchema = z.object({
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
let jobId: string | undefined
|
||||
try {
|
||||
if (!(await isAuthenticatedNextjs())) {
|
||||
const redirectUrl = new URL("/auth", request.url);
|
||||
@@ -35,9 +37,18 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const { projectId, config } = searchSchema.parse(body)
|
||||
const parsed = searchSchema.parse(body)
|
||||
const { projectId, config } = parsed
|
||||
jobId = parsed.jobId
|
||||
|
||||
const token = await convexAuthNextjsToken();
|
||||
if (jobId) {
|
||||
await fetchMutation(
|
||||
api.searchJobs.update,
|
||||
{ jobId: jobId as any, status: "running", progress: 10 },
|
||||
{ token }
|
||||
);
|
||||
}
|
||||
const searchContext = await fetchQuery(
|
||||
api.projects.getSearchContext,
|
||||
{ projectId: projectId as any },
|
||||
@@ -45,6 +56,13 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
|
||||
if (!searchContext.context) {
|
||||
if (jobId) {
|
||||
await fetchMutation(
|
||||
api.searchJobs.update,
|
||||
{ jobId: jobId as any, status: "failed", error: "No analysis available." },
|
||||
{ token }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{ error: 'No analysis available for selected sources.' },
|
||||
{ status: 400 }
|
||||
@@ -60,18 +78,51 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Generate queries
|
||||
console.log(' Generating search queries...')
|
||||
const queries = generateSearchQueries(analysis as EnhancedProductAnalysis, config as SearchConfig)
|
||||
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`)
|
||||
if (jobId) {
|
||||
await fetchMutation(
|
||||
api.searchJobs.update,
|
||||
{ jobId: jobId as any, status: "running", progress: 40 },
|
||||
{ token }
|
||||
);
|
||||
}
|
||||
|
||||
// Execute searches
|
||||
console.log(' Executing searches...')
|
||||
const searchResults = await executeSearches(queries)
|
||||
console.log(` ✓ Found ${searchResults.length} raw results`)
|
||||
if (jobId) {
|
||||
await fetchMutation(
|
||||
api.searchJobs.update,
|
||||
{ jobId: jobId as any, status: "running", progress: 70 },
|
||||
{ token }
|
||||
);
|
||||
}
|
||||
|
||||
// Score and rank
|
||||
console.log(' Scoring opportunities...')
|
||||
const opportunities = scoreOpportunities(searchResults, analysis as EnhancedProductAnalysis)
|
||||
console.log(` ✓ Scored ${opportunities.length} opportunities`)
|
||||
if (jobId) {
|
||||
await fetchMutation(
|
||||
api.searchJobs.update,
|
||||
{ jobId: jobId as any, status: "running", progress: 90 },
|
||||
{ token }
|
||||
);
|
||||
}
|
||||
|
||||
if (jobId) {
|
||||
await fetchMutation(
|
||||
api.searchJobs.update,
|
||||
{ jobId: jobId as any, status: "completed", progress: 100 },
|
||||
{ token }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
@@ -97,17 +148,36 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Opportunity search error:', error)
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : typeof error === "string" ? error : "Search failed"
|
||||
console.error("❌ Opportunity search error:", errorMessage)
|
||||
|
||||
if (jobId) {
|
||||
try {
|
||||
const token = await convexAuthNextjsToken();
|
||||
await fetchMutation(
|
||||
api.searchJobs.update,
|
||||
{
|
||||
jobId: jobId as any,
|
||||
status: "failed",
|
||||
error: errorMessage
|
||||
},
|
||||
{ token }
|
||||
);
|
||||
} catch {
|
||||
// Best-effort job update only.
|
||||
}
|
||||
}
|
||||
|
||||
if (error.name === 'ZodError') {
|
||||
if (error?.name === 'ZodError') {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request format', details: error.errors },
|
||||
{ error: 'Invalid request format', details: error?.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to search for opportunities' },
|
||||
{ error: errorMessage || 'Failed to search for opportunities' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user