All files / src/app/api/admin/metrics/database route.ts

99.37% Statements 160/161
91.3% Branches 21/23
100% Functions 2/2
99.37% Lines 160/161

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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 1621x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 13x 13x 13x 13x 13x 13x 13x 13x 10x 10x 13x 13x 13x 13x 13x 13x 13x 13x 13x 1x 1x 13x 13x 13x 13x 13x 13x 13x 13x 13x 1x 1x 1x 1x 1x 1x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 10x 12x 1x 1x 1x 1x 12x 12x 12x 10x 12x 1x 1x 1x 1x 12x 12x 12x 11x 12x   1x 1x 1x 12x 12x 12x 12x 12x 12x 12x 12x 12x 1x 1x 12x 1x 1x 11x 10x 10x 10x 12x 12x 12x  
export const dynamic = "force-dynamic";
 
import { NextResponse } from "next/server";
import { } from "next-auth";
import { getQueryMetrics, getMetricsSummary } from "@/lib/database/metrics";
import { prisma } from "@/lib/prisma";
import { addSecurityHeaders } from "@/lib/security";
import {
  withAdmin,
  withErrorHandling,
  ApiSuccessResponse,
  ApiErrorResponse } from "@/lib/api";
import { } from "@/lib/api/middleware";
 
/**
 * GET /api/admin/metrics/database
 *
 * Returns database query metrics and statistics for admin monitoring.
 * Requires admin authentication.
 */
async function handleGet(): Promise<NextResponse<ApiSuccessResponse<unknown> | ApiErrorResponse>> {
  // Get query metrics from in-memory store
  const queryMetrics = getQueryMetrics();
  const summary = getMetricsSummary();
 
  // Get additional database statistics
  const [
    totalProducts,
    totalOrders,
    totalUsers,
    totalReviews,
    recentErrors,
    recentPerformanceMetrics,
  ] = await Promise.all([
    prisma.product.count(),
    prisma.order.count(),
    prisma.user.count(),
    prisma.review.count(),
    prisma.errorLog.findMany({
      where: { category: "DATABASE" },
      orderBy: { createdAt: "desc" },
      take: 10,
      select: {
        id: true,
        message: true,
        createdAt: true,
        statusCode: true } }),
    prisma.performanceMetric.findMany({
      where: { type: "database" },
      orderBy: { createdAt: "desc" },
      take: 20,
      select: {
        id: true,
        name: true,
        duration: true,
        createdAt: true } }),
  ]);
 
  // Calculate database health status
  const healthStatus = getHealthStatus(summary, queryMetrics);
 
  const responseData = {
    health: healthStatus,
    summary: {
      ...summary,
      totalQueries: queryMetrics.total,
      slowQueryPercentage: queryMetrics.total > 0
        ? ((queryMetrics.slowQueries / queryMetrics.total) * 100).toFixed(2)
        : "0" },
    queryMetrics: {
      total: queryMetrics.total,
      slow: queryMetrics.slowQueries,
      warning: queryMetrics.warningQueries,
      critical: queryMetrics.criticalQueries,
      averageDuration: queryMetrics.averageDuration },
    topSlowestQueries: queryMetrics.topSlowest.map(q => ({
      duration: q.duration,
      timestamp: q.timestamp,
      query: q.query.substring(0, 200) + (q.query.length > 200 ? "..." : "") })),
    queryPatterns: queryMetrics.queryPatterns,
    tableStats: {
      products: totalProducts,
      orders: totalOrders,
      users: totalUsers,
      reviews: totalReviews },
    recentDatabaseErrors: recentErrors,
    recentPerformanceMetrics: recentPerformanceMetrics.map(m => ({
      name: m.name,
      duration: m.duration,
      timestamp: m.createdAt })),
    timestamp: new Date().toISOString() };
 
  return addSecurityHeaders(
    NextResponse.json({
      success: true,
      data: responseData })
  ) as NextResponse<ApiSuccessResponse<unknown> | ApiErrorResponse>;
}
 
export const GET = withErrorHandling(withAdmin(handleGet));
 
/**
 * Determine health status based on metrics
 */
function getHealthStatus(
  summary: { healthy: boolean; avgQueryTime: number; slowQueryPercentage: number },
  metrics: { criticalQueries: number; warningQueries: number }
): {
  status: 'healthy' | 'degraded' | 'unhealthy';
  message: string;
  checks: { name: string; status: 'pass' | 'warn' | 'fail'; value: string }[];
} {
  const checks: { name: string; status: 'pass' | 'warn' | 'fail'; value: string }[] = [];
 
  // Check average query time
  if (summary.avgQueryTime < 50) {
    checks.push({ name: 'Average Query Time', status: 'pass', value: `${summary.avgQueryTime}ms` });
  } else if (summary.avgQueryTime < 100) {
    checks.push({ name: 'Average Query Time', status: 'warn', value: `${summary.avgQueryTime}ms` });
  } else {
    checks.push({ name: 'Average Query Time', status: 'fail', value: `${summary.avgQueryTime}ms` });
  }
 
  // Check slow query percentage
  if (summary.slowQueryPercentage < 5) {
    checks.push({ name: 'Slow Query Rate', status: 'pass', value: `${summary.slowQueryPercentage}%` });
  } else if (summary.slowQueryPercentage < 10) {
    checks.push({ name: 'Slow Query Rate', status: 'warn', value: `${summary.slowQueryPercentage}%` });
  } else {
    checks.push({ name: 'Slow Query Rate', status: 'fail', value: `${summary.slowQueryPercentage}%` });
  }
 
  // Check critical queries
  if (metrics.criticalQueries === 0) {
    checks.push({ name: 'Critical Queries', status: 'pass', value: `${metrics.criticalQueries}` });
  } else if (metrics.criticalQueries < 5) {
    checks.push({ name: 'Critical Queries', status: 'warn', value: `${metrics.criticalQueries}` });
  } else {
    checks.push({ name: 'Critical Queries', status: 'fail', value: `${metrics.criticalQueries}` });
  }
 
  // Determine overall status
  const hasFailure = checks.some(c => c.status === 'fail');
  const hasWarning = checks.some(c => c.status === 'warn');
 
  let status: 'healthy' | 'degraded' | 'unhealthy';
  let message: string;
 
  if (hasFailure) {
    status = 'unhealthy';
    message = 'Database performance issues detected. Review slow queries.';
  } else if (hasWarning) {
    status = 'degraded';
    message = 'Some performance warnings. Monitor closely.';
  } else {
    status = 'healthy';
    message = 'Database performance is optimal.';
  }
 
  return { status, message, checks };
}