All files / src/app/api/admin/images/audit route.ts

100% Statements 94/94
100% Branches 13/13
100% Functions 1/1
100% Lines 94/94

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 951x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 8x 8x 8x 8x 8x 6x 6x 6x 6x 6x 6x 6x 5x 8x 1x 1x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 8x 12x 12x 12x 4x 4x 4x 4x 4x 4x 4x 12x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 12x 6x 6x 6x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 1x 1x  
export const dynamic = "force-dynamic";
 
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import fs from "fs/promises";
import path from "path";
import {
  withAdmin,
  withErrorHandling,
  successResponse,
  ApiSuccessResponse,
  ApiErrorResponse } from "@/lib/api";
 
/**
 * GET /api/admin/images/audit
 * Generate missing images report
 */
async function handleGet(): Promise<NextResponse<ApiSuccessResponse<unknown> | ApiErrorResponse>> {
  // Get all products with their images
  const products = await prisma.product.findMany({
    include: { images: true },
    orderBy: { id: "asc" } });
 
  // Scan directory
  const productsDir = path.join(process.cwd(), "public", "images", "products");
  let actualFiles: string[] = [];
 
  try {
    actualFiles = await fs.readdir(productsDir);
    actualFiles = actualFiles.filter((f) => f.endsWith(".webp"));
  } catch {
    actualFiles = [];
  }
 
  // Generate audit report
  interface AuditItem {
    productId: number;
    title: string;
    category?: number;
    missingCount?: number;
    actual?: number;
    imageCount?: number;
    status: string;
  }
 
  const missingImages: AuditItem[] = [];
  const completeProducts: AuditItem[] = [];
  const partialProducts: AuditItem[] = [];
 
  for (const product of products) {
    const imageCount = product.images.length;
 
    if (imageCount === 0) {
      missingImages.push({
        productId: product.id,
        title: product.title,
        category: product.categoryId,
        missingCount: 2, // At least 1 thumbnail + 1 preview expected
        actual: 0,
        status: "MISSING" });
    } else if (imageCount === 1) {
      partialProducts.push({
        productId: product.id,
        title: product.title,
        imageCount,
        status: "PARTIAL" });
    } else {
      completeProducts.push({
        productId: product.id,
        title: product.title,
        imageCount,
        status: "COMPLETE" });
    }
  }
 
  const totalProducts = products.length;
  const completionRate = totalProducts > 0
    ? Number(((completeProducts.length / totalProducts) * 100).toFixed(1))
    : 0;
 
  return successResponse({
    summary: {
      totalProducts,
      completeProducts: completeProducts.length,
      partialProducts: partialProducts.length,
      missingProducts: missingImages.length,
      completionRate,
      filesInDirectory: actualFiles.length },
    complete: completeProducts,
    partial: partialProducts,
    missing: missingImages });
}
 
export const GET = withErrorHandling(withAdmin(handleGet));