All files / src/app/api/cron/cleanup-errors route.ts

86.79% Statements 92/106
57.14% Branches 4/7
100% Functions 2/2
86.79% Lines 92/106

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 1071x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 9x 9x 9x 9x 9x                         1x 1x 9x 9x     9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 9x 1x 1x 1x 1x 1x 1x 9x  
/**
 * Error Cleanup Cron Job
 *
 * Retention policy for error logs:
 * - Delete error logs older than 30 days
 * - Reset counts on resolved errors older than 30 days
 *
 * Should be called by Vercel Cron or similar scheduler.
 * Vercel cron config (vercel.json):
 * {
 *   "crons": [{
 *     "path": "/api/cron/cleanup-errors",
 *     "schedule": "0 3 * * *"
 *   }]
 * }
 */
 
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { logger } from '@/lib/logging';
 
const RETENTION_DAYS = 30;
 
/**
 * Verify cron authorization
 */
function verifyCronAuth(request: NextRequest): boolean {
  // In development or test, allow without auth
  if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
    return true;
  }

  const authHeader = request.headers.get('authorization');
  const cronSecret = process.env.CRON_SECRET;

  // If no cron secret configured, reject all requests
  if (!cronSecret) {
    logger.warn('CRON_SECRET not configured', { category: 'CRON' });
    return false;
  }

  return authHeader === `Bearer ${cronSecret}`;
}
 
export async function GET(request: NextRequest) {
  // Verify authorization
  if (!verifyCronAuth(request)) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
 
  const startTime = Date.now();
  const cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - RETENTION_DAYS);
 
  logger.info('Starting error cleanup', { category: 'CRON', cutoffDate: cutoffDate.toISOString() });
 
  try {
    // Delete old error logs
    const deletedLogs = await prisma.errorLog.deleteMany({
      where: {
        createdAt: { lt: cutoffDate },
      },
    });
 
    // Reset counts on old resolved errors
    const updatedStats = await prisma.errorStatistic.updateMany({
      where: {
        lastSeen: { lt: cutoffDate },
        status: 'resolved',
      },
      data: {
        count: 0,
      },
    });
 
    // Find and delete orphaned statistics (no recent logs)
    const orphanedStats = await prisma.errorStatistic.deleteMany({
      where: {
        lastSeen: { lt: cutoffDate },
        count: 0,
      },
    });
 
    const duration = Date.now() - startTime;
 
    const result = {
      success: true,
      retentionDays: RETENTION_DAYS,
      cutoffDate: cutoffDate.toISOString(),
      deletedLogs: deletedLogs.count,
      resetStatistics: updatedStats.count,
      deletedOrphanedStats: orphanedStats.count,
      durationMs: duration,
    };
 
    logger.info('Error cleanup completed', { category: 'CRON', ...result });
 
    return NextResponse.json(result);
  } catch (error) {
    logger.error('Error cleanup failed', error instanceof Error ? error : new Error(String(error)), { category: 'CRON' });
    return NextResponse.json(
      { success: false, error: 'Cleanup failed' },
      { status: 500 }
    );
  }
}