import { type NextRequest, NextResponse } from "next/server"; import { optionalAuth } from "@/lib/auth/middleware"; import { getAdAnalytics, getMarketplaceStats, trackEvent, trackEventsBatch, } from "@/lib/prisma/queries/telemetry"; import { checkRateLimit, createRateLimitResponse, getClientIP, } from "@/lib/rate-limiter"; import { secureLogger } from "@/lib/secure-logger"; import { telemetryBatchSchema, telemetryEventSchema, } from "@/lib/validation-schemas"; /** * POST /api/telemetry/event * Registra um único evento de telemetria */ export async function POST(request: NextRequest) { try { // Rate limiting const ip = getClientIP(request); const rateLimit = checkRateLimit(ip, "/api/telemetry/event"); if (!rateLimit.allowed) { return createRateLimitResponse(rateLimit.resetAt); } // Autenticação opcional (usuários anônimos podem gerar eventos) const authResult = await optionalAuth(request); const userId = authResult instanceof NextResponse ? undefined : authResult?.userId; const body = await request.json(); // Verificar se é batch ou evento único const isBatch = body.events && Array.isArray(body.events) && body.events.length > 0; if (isBatch) { // Batch de eventos // Garantir que todos os eventos tenham metadata const normalizedBody = { ...body, events: body.events.map((event: any) => ({ ...event, metadata: event.metadata || {}, })), }; const validationResult = telemetryBatchSchema.safeParse(normalizedBody); if (!validationResult.success) { return NextResponse.json( { success: false, error: "Dados de entrada inválidos", details: validationResult.error.issues, }, { status: 400 }, ); } const { events, session_id: batchSessionId } = validationResult.data; // Adicionar user_id e session_id aos eventos const eventsWithAuth = events.map((event) => ({ ...event, user_id: userId, session_id: batchSessionId || event.session_id || undefined, ad_id: event.ad_id ?? undefined, // Converter null para undefined metadata: event.metadata || {}, })); const trackedEvents = await trackEventsBatch(eventsWithAuth); secureLogger.debug("📊 Batch de eventos registrado", { count: trackedEvents.length, userId, ip, }); return NextResponse.json( { success: true, events: trackedEvents, }, { status: 201 }, ); } else { // Evento único // Garantir que o evento tenha metadata const normalizedBody = { ...body, metadata: body.metadata || {}, }; const validationResult = telemetryEventSchema.safeParse(normalizedBody); if (!validationResult.success) { return NextResponse.json( { success: false, error: "Dados de entrada inválidos", details: validationResult.error.issues, }, { status: 400 }, ); } const eventData = validationResult.data; // Adicionar user_id e session_id const trackedEvent = await trackEvent({ ...eventData, user_id: userId, session_id: body.session_id || undefined, ad_id: eventData.ad_id ?? undefined, // Converter null para undefined metadata: eventData.metadata || {}, }); secureLogger.debug("📊 Evento registrado", { eventType: eventData.event_type, adId: eventData.ad_id, userId, ip, }); return NextResponse.json( { success: true, event: trackedEvent, }, { status: 201 }, ); } } catch (error) { secureLogger.error("❌ Erro ao registrar evento de telemetria", { error: error instanceof Error ? error.message : "Erro desconhecido", stack: error instanceof Error ? error.stack : undefined, }); return NextResponse.json( { success: false, error: "Erro ao registrar evento", }, { status: 500 }, ); } } /** * GET /api/telemetry/analytics/:adId * Busca analytics de um anúncio específico */ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); const adIdParam = searchParams.get("adId"); const dashboard = searchParams.get("dashboard"); // Dashboard agregado (admin) if (dashboard === "true") { const startDateParam = searchParams.get("startDate"); const endDateParam = searchParams.get("endDate"); const startDate = startDateParam ? new Date(startDateParam) : undefined; const endDate = endDateParam ? new Date(endDateParam) : undefined; const stats = await getMarketplaceStats(startDate, endDate); return NextResponse.json( { success: true, stats, }, { status: 200 }, ); } // Analytics de um anúncio específico if (adIdParam) { const adId = parseInt(adIdParam, 10); if (Number.isNaN(adId)) { return NextResponse.json( { success: false, error: "ID do anúncio inválido", }, { status: 400 }, ); } const analytics = await getAdAnalytics(adId); if (!analytics) { return NextResponse.json( { success: false, error: "Analytics não encontrado para este anúncio", }, { status: 404 }, ); } return NextResponse.json( { success: true, analytics, }, { status: 200 }, ); } return NextResponse.json( { success: false, error: "Parâmetro 'adId' ou 'dashboard' é obrigatório", }, { status: 400 }, ); } catch (error) { secureLogger.error("❌ Erro ao buscar analytics", { error: error instanceof Error ? error.message : "Erro desconhecido", stack: error instanceof Error ? error.stack : undefined, }); return NextResponse.json( { success: false, error: "Erro ao buscar analytics", }, { status: 500 }, ); } }