All files / src/app/api/admin/monitoring/performance/aggregate route.ts

88.04% Statements 81/92
87.5% Branches 14/16
100% Functions 1/1
88.04% Lines 81/92

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 931x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 2x 2x 2x 2x 2x 2x 10x 10x 10x 12x 1x 1x 1x 1x 1x 1x 9x 9x 9x 12x             9x 9x 12x 12x 1x 1x 1x 1x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 12x           12x 1x 1x  
/**
 * Performance Aggregation API
 *
 * GET /api/admin/monitoring/performance/aggregate
 *
 * Returns aggregated performance metrics grouped by various dimensions.
 */
 
export const dynamic = 'force-dynamic';
 
import { NextRequest, NextResponse } from 'next/server';
import { getAggregatedMetrics, type AggregationResult } from '@/lib/monitoring/performanceQueries';
import { logger } from '@/lib/logging';
import {
  withAdmin,
  withErrorHandling,
  successResponse,
  errorResponse,
  type ApiSuccessResponse,
  type ApiErrorResponse,
} from '@/lib/api';
 
/**
 * GET handler for aggregated metrics
 */
async function handleGet(
  request: NextRequest
): Promise<NextResponse<ApiSuccessResponse<AggregationResult> | ApiErrorResponse>> {
  const { searchParams } = new URL(request.url);
 
  // Parse query parameters
  const groupBy = searchParams.get('groupBy');
  const metric = searchParams.get('metric') || 'count';
  const timeRange = searchParams.get('timeRange') || '24h';
  const limit = searchParams.get('limit');
 
  // Validate groupBy (required)
  const validGroupBy = ['path', 'method', 'statusCode', 'hour', 'day'];
  if (!groupBy || !validGroupBy.includes(groupBy)) {
    return errorResponse(
      'INVALID_PARAM',
      'groupBy is required and must be one of: path, method, statusCode, hour, day',
      { status: 400 }
    );
  }
 
  // Validate metric
  const validMetrics = ['count', 'avgDuration', 'p95Duration', 'errorRate'];
  if (!validMetrics.includes(metric)) {
    return errorResponse(
      'INVALID_PARAM',
      'Invalid metric. Must be one of: count, avgDuration, p95Duration, errorRate',
      { status: 400 }
    );
  }
 
  // Validate time range
  const validRanges = ['1h', '6h', '24h', '7d', '30d'];
  if (!validRanges.includes(timeRange)) {
    return errorResponse(
      'INVALID_PARAM',
      'Invalid timeRange. Must be one of: 1h, 6h, 24h, 7d, 30d',
      { status: 400 }
    );
  }
 
  // Validate limit
  const limitNum = limit ? parseInt(limit, 10) : 20;
  if (isNaN(limitNum) || limitNum < 1 || limitNum > 100) {
    return errorResponse('INVALID_PARAM', 'limit must be between 1 and 100', {
      status: 400,
    });
  }
 
  try {
    const result = await getAggregatedMetrics({
      groupBy: groupBy as 'path' | 'method' | 'statusCode' | 'hour' | 'day',
      metric: metric as 'count' | 'avgDuration' | 'p95Duration' | 'errorRate',
      timeRange,
      limit: limitNum,
    });
 
    return successResponse(result);
  } catch (error) {
    logger.error('Error fetching aggregated metrics', error instanceof Error ? error : new Error(String(error)), { category: 'API' });
    return errorResponse('INTERNAL_ERROR', 'Failed to fetch aggregated metrics', {
      status: 500,
    });
  }
}
 
export const GET = withErrorHandling(withAdmin(handleGet));