Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | /** * Analytics Aggregation Cron Endpoint * * Should be triggered by a cron job daily at midnight. * Aggregates the previous day's analytics data. * * POST /api/cron/analytics-aggregate * - Requires CRON_SECRET in Authorization header * - Optionally accepts date range for backfilling */ import { NextRequest, NextResponse } from "next/server"; import { runDailyAggregation, runRangeAggregation, cleanupOldData } from "@/lib/analytics/aggregation"; import { logger } from "@/lib/logging"; export const dynamic = "force-dynamic"; /** * POST - Run analytics aggregation * * Headers: * Authorization: Bearer <CRON_SECRET> * * Body (optional): * { * "action": "aggregate" | "cleanup" | "backfill", * "startDate": "2024-01-01", // for backfill * "endDate": "2024-01-31", // for backfill * "retentionDays": 90 // for cleanup * } */ export async function POST(request: NextRequest) { try { // Verify cron secret const authHeader = request.headers.get("authorization"); const cronSecret = process.env.CRON_SECRET; if (!cronSecret) { logger.warn("CRON_SECRET not configured", { category: "CRON" }); return NextResponse.json( { error: "Cron endpoint not configured" }, { status: 503 } ); } if (authHeader !== `Bearer ${cronSecret}`) { return NextResponse.json( { error: "Unauthorized" }, { status: 401 } ); } // Parse body for options let body: { action?: string; startDate?: string; endDate?: string; retentionDays?: number; } = {}; try { body = await request.json(); } catch { // Body is optional, default to daily aggregation } const action = body.action || "aggregate"; switch (action) { case "aggregate": { // Run aggregation for yesterday const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); await runDailyAggregation(yesterday); return NextResponse.json({ success: true, action: "aggregate", date: yesterday.toISOString().split("T")[0] }); } case "backfill": { // Backfill a date range if (!body.startDate || !body.endDate) { return NextResponse.json( { error: "startDate and endDate required for backfill" }, { status: 400 } ); } const startDate = new Date(body.startDate); const endDate = new Date(body.endDate); if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) { return NextResponse.json( { error: "Invalid date format" }, { status: 400 } ); } await runRangeAggregation(startDate, endDate); return NextResponse.json({ success: true, action: "backfill", startDate: body.startDate, endDate: body.endDate }); } case "cleanup": { // Clean up old raw data const retentionDays = body.retentionDays || 90; await cleanupOldData(retentionDays); return NextResponse.json({ success: true, action: "cleanup", retentionDays }); } default: return NextResponse.json( { error: `Unknown action: ${action}` }, { status: 400 } ); } } catch (error) { logger.error("Analytics aggregation error", error instanceof Error ? error : new Error(String(error)), { category: "CRON" }); return NextResponse.json( { error: error instanceof Error ? error.message : "Aggregation failed" }, { status: 500 } ); } } |