import { NextResponse } from "next/server"; import { scanScheduler } from "@/services/scanScheduler"; import { lighthouseScanner } from "@/services/lighthouseScanner"; import { logError } from "@/utils/errorUtils"; import { verifyCronSecret } from "@/lib/apiAuth"; export async function GET(request: Request) { const authError = verifyCronSecret(request); if (authError) return authError; try { const url = new URL(request.url); const mode = url.searchParams.get("mode") || "all"; // "scheduled", "change_detection", "all" const organizationId = url.searchParams.get("organizationId"); // Optional: limit to specific org console.info(JSON.stringify({ level: 'info', event: 'scan_process_start', mode, timestamp: new Date().toISOString() })); const results = { scheduledScans: 0, changeDetectionScans: 0, errors: [] as string[], startTime: new Date().toISOString(), }; // Process scheduled scans if (mode === "scheduled" || mode === "all") { try { console.info(JSON.stringify({ level: 'info', event: 'processing_scheduled_scans', timestamp: new Date().toISOString() })); await scanScheduler.processScheduledScans(); // Get count of processed scans const scheduledScans = await scanScheduler.getScheduledScans(); results.scheduledScans = scheduledScans.length; console.info(JSON.stringify({ level: 'info', event: 'scheduled_scans_processed', count: results.scheduledScans, timestamp: new Date().toISOString() })); } catch (error) { const errorMsg = `Error processing scheduled scans: ${error instanceof Error ? error.message : 'Unknown error'}`; logError(errorMsg, error); results.errors.push(errorMsg); } } // Process change detection if (mode === "change_detection" || mode === "all") { try { console.info(JSON.stringify({ level: 'info', event: 'processing_change_detection', timestamp: new Date().toISOString() })); await scanScheduler.processChangeDetection(); // Note: Change detection count is harder to track since it's based on actual changes // We'll just indicate it was processed results.changeDetectionScans = -1; // -1 indicates processed but count unknown console.info(JSON.stringify({ level: 'info', event: 'change_detection_processed', timestamp: new Date().toISOString() })); } catch (error) { const errorMsg = `Error processing change detection: ${error instanceof Error ? error.message : 'Unknown error'}`; logError(errorMsg, error); results.errors.push(errorMsg); } } // Get overall statistics const stats = await getScanStatistics(organizationId ?? undefined); const response = { success: results.errors.length === 0, message: `Automatic scan process completed - ${results.scheduledScans} scheduled scans, change detection processed`, results, statistics: stats, timestamp: new Date().toISOString(), }; console.info(JSON.stringify({ level: 'info', event: 'scan_process_completed', success: response.success, timestamp: new Date().toISOString() })); return NextResponse.json(response); } catch (error) { const errorMsg = `Critical error in automatic scan process: ${error instanceof Error ? error.message : 'Unknown error'}`; logError(errorMsg, error); return NextResponse.json( { success: false, error: errorMsg, timestamp: new Date().toISOString(), }, { status: 500 } ); } } /** * Get scan statistics for monitoring */ async function getScanStatistics(organizationId?: string) { try { const { getSupabaseAdmin } = await import("@/lib/admin"); const supabase = getSupabaseAdmin(); const now = new Date(); const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); // Build query let query = supabase .from('scans') .select('id, status, created_at, triggered_by'); if (organizationId) { const { data: websitesForOrg, error: orgErr } = await supabase .from('websites') .select('id') .eq('organization_id', organizationId); if (orgErr) { throw orgErr; } const websiteIds = (websitesForOrg || []).map((w: any) => w.id); if (websiteIds.length === 0) { return { today: { total: 0, byStatus: {}, byTrigger: {} }, thisMonth: { total: 0 }, last24Hours: { total: 0 }, }; } query = query.in('website_id', websiteIds as any[]); } // Get today's scans const { data: todayScans } = await query .gte('created_at', startOfDay.toISOString()); // Get this month's scans const { data: monthScans } = await query .gte('created_at', startOfMonth.toISOString()); // Get scans by status const { data: statusCounts } = await query .select('status') as unknown as { data: Array<{ status: string }> }; const statusBreakdown = (statusCounts?.reduce((acc: Record, scan: { status: string }) => { const key = String(scan.status || 'unknown'); acc[key] = (acc[key] || 0) + 1; return acc; }, {} as Record)) || {}; // Get scans by trigger type const { data: triggerCounts } = await query .select('triggered_by') as unknown as { data: Array<{ triggered_by: string | null }> }; const triggerBreakdown = (triggerCounts?.reduce((acc: Record, scan: { triggered_by: string | null }) => { const trigger = String(scan.triggered_by || 'unknown'); acc[trigger] = (acc[trigger] || 0) + 1; return acc; }, {} as Record)) || {}; return { today: { total: todayScans?.length || 0, byStatus: statusBreakdown, byTrigger: triggerBreakdown, }, thisMonth: { total: monthScans?.length || 0, }, last24Hours: { total: todayScans?.length || 0, }, }; } catch (error) { logError('Error getting scan statistics', error); return { today: { total: 0, byStatus: {}, byTrigger: {} }, thisMonth: { total: 0 }, last24Hours: { total: 0 }, }; } } /** * Manual scan trigger endpoint */ export async function POST(request: Request) { try { const body = await request.json(); const { websiteId, pageId, deviceType = 'desktop', categories, priority = 'medium' } = body; if (!websiteId || !pageId) { return NextResponse.json( { error: "Website ID and Page ID are required" }, { status: 400 } ); } console.info(JSON.stringify({ level: 'info', event: 'manual_scan_triggered', websiteId, pageId, timestamp: new Date().toISOString() })); // Check subscription limits const { data: website } = await (await import("@/lib/admin")).getSupabaseAdmin() .from('websites') .select('organization_id') .eq('id', websiteId) .single(); if (!website) { return NextResponse.json( { error: "Website not found" }, { status: 404 } ); } const { canScan, limits, currentUsage } = await lighthouseScanner.checkSubscriptionLimits( String(website.organization_id) ); if (!canScan) { return NextResponse.json( { error: "Subscription limit exceeded", limits, currentUsage, }, { status: 429 } ); } // Perform the scan const scanConfig = { websiteId, pageId, deviceType: deviceType as 'desktop' | 'mobile', categories: categories || ['performance', 'accessibility', 'seo', 'best_practices'], priority: priority as 'low' | 'medium' | 'high', triggeredBy: 'manual' as const, }; const result = await lighthouseScanner.performScan(scanConfig); if (result.success) { return NextResponse.json({ success: true, scanId: result.scanId, message: "Scan completed successfully", metrics: result.metrics, }); } else { return NextResponse.json( { success: false, error: result.error, }, { status: 500 } ); } } catch (error) { const errorMsg = `Error in manual scan: ${error instanceof Error ? error.message : 'Unknown error'}`; logError(errorMsg, error); return NextResponse.json( { success: false, error: errorMsg, }, { status: 500 } ); } }