import { NextResponse } from "next/server"; import { lighthouseScanner } from "@/services/lighthouseScanner"; import { logError } from "@/utils/errorUtils"; export async function POST(request: Request) { try { const body = await request.json(); const { websiteId, url, changeType, contentHash, metadata } = body; if (!websiteId || !url) { return NextResponse.json( { error: "Website ID and URL are required" }, { status: 400 } ); } // Structured log for production visibility console.info(JSON.stringify({ level: 'info', event: 'webhook_website_change_received', websiteId, changeType: changeType || 'unknown', timestamp: new Date().toISOString(), })); // Verify the webhook signature if needed const signature = request.headers.get('x-webhook-signature'); if (process.env.WEBHOOK_SECRET && signature) { // Add webhook signature verification here if needed // const isValid = verifyWebhookSignature(body, signature, process.env.WEBHOOK_SECRET); // if (!isValid) { // return NextResponse.json({ error: "Invalid signature" }, { status: 401 }); // } } // Get website details const { getSupabaseAdmin } = await import("@/lib/admin"); const supabase = getSupabaseAdmin(); const { data: website, error: websiteError } = await supabase .from('websites') .select(` id, organization_id, organizations!inner ( subscription_tier ) `) .eq('id', websiteId) .single(); if (websiteError || !website) { return NextResponse.json( { error: "Website not found" }, { status: 404 } ); } // Check subscription limits const { canScan, limits } = await lighthouseScanner.checkSubscriptionLimits( String(website.organization_id) ); if (!canScan) { console.warn(JSON.stringify({ level: 'warn', event: 'subscription_limit_exceeded', websiteId, timestamp: new Date().toISOString(), })); return NextResponse.json( { success: false, message: "Subscription limit exceeded - scan skipped", limits, }, { status: 429 } ); } // Check if change detection is enabled for this subscription const subscriptionLimits = getSubscriptionLimits(String((website as any).organizations?.subscription_tier || 'free')); if (!subscriptionLimits.changeDetectionEnabled) { console.warn(JSON.stringify({ level: 'warn', event: 'change_detection_disabled', tier: String((website as any).organizations?.subscription_tier || 'unknown'), websiteId, timestamp: new Date().toISOString(), })); return NextResponse.json( { success: false, message: "Change detection not available for this subscription tier", }, { status: 403 } ); } // Get or create the page record let pageId: string; const { data: existingPage } = await supabase .from('pages') .select('id, content_hash') .eq('website_id', websiteId) .eq('url', url) .single(); if (existingPage) { pageId = String((existingPage as any).id); // Update the page with new content hash await supabase .from('pages') .update({ content_hash: contentHash, last_seen_at: new Date().toISOString(), metadata: metadata || {}, }) .eq('id', pageId); } else { // Create new page record const { data: newPage, error: createError } = await supabase .from('pages') .insert({ website_id: websiteId, url, path: new URL(url).pathname, content_hash: contentHash, title: metadata?.title || 'Unknown Page', is_active: true, metadata: metadata || {}, }) .select('id') .single(); if (createError || !newPage) { throw new Error(`Failed to create page record: ${createError?.message}`); } pageId = String((newPage as any).id); } // Trigger a high-priority scan due to changes const scanConfig = { websiteId, pageId, deviceType: 'desktop' as const, categories: ['performance', 'accessibility', 'seo', 'best_practices'] as ( 'performance' | 'accessibility' | 'seo' | 'best_practices' )[], priority: 'high' as const, triggeredBy: 'change_detection' as const, }; const result = await lighthouseScanner.performScan(scanConfig); // Log the change detection await supabase .from('audit_logs') .insert({ website_id: websiteId, action: 'change_detected', entity_type: 'page', entity_id: pageId, changes: { change_type: changeType || 'content_update', url, content_hash: contentHash, metadata, }, }); if (result.success) { console.info(JSON.stringify({ level: 'info', event: 'change_detection_scan_completed', websiteId, timestamp: new Date().toISOString(), })); return NextResponse.json({ success: true, scanId: result.scanId, message: "Change detection scan triggered successfully", metrics: result.metrics, }); } else { console.error(JSON.stringify({ level: 'error', event: 'change_detection_scan_failed', websiteId, timestamp: new Date().toISOString(), })); return NextResponse.json( { success: false, error: result.error, }, { status: 500 } ); } } catch (error) { const errorMsg = `Error processing website change webhook: ${error instanceof Error ? error.message : 'Unknown error'}`; logError(errorMsg, error); return NextResponse.json( { success: false, error: errorMsg, }, { status: 500 } ); } } /** * Get subscription limits based on tier */ function getSubscriptionLimits(tier: string) { switch (tier) { case 'free': return { changeDetectionEnabled: false, }; case 'starter': return { changeDetectionEnabled: true, }; case 'professional': return { changeDetectionEnabled: true, }; case 'enterprise': return { changeDetectionEnabled: true, }; default: return { changeDetectionEnabled: false, }; } }