All files / src/app/api/admin/categories route.ts

100% Statements 88/88
90.9% Branches 10/11
100% Functions 2/2
100% Lines 88/88

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 891x 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 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x 4x 4x 4x 4x 3x 3x 3x 1x 1x 1x 1x 1x 1x 7x 7x 7x 7x 7x 7x 7x 7x 2x 2x 5x 5x 5x 5x 5x 5x 5x 7x 7x 7x 7x 3x 3x 3x 3x 3x 3x 1x 1x 1x  
export const dynamic = "force-dynamic";
 
import { NextRequest, NextResponse } from 'next/server';
import {
  withAdmin,
  withErrorHandling,
  successResponse,
  createdResponse,
  ApiError,
  ApiSuccessResponse,
  ApiErrorResponse } from "@/lib/api";
import { prisma } from "@/lib/prisma";
import { z } from "zod";
import { invalidatePattern } from "@/lib/core";
 
// Validation schema for creating a category
const createCategorySchema = z.object({
  title: z.string().min(1, "Title is required"),
  slug: z.string().min(1, "Slug is required"),
  imageUrl: z.string().url().optional().nullable(),
  description: z.string().optional().nullable(),
  parentId: z
    .union([z.string(), z.number()])
    .transform((val) => (typeof val === "string" ? parseInt(val) : val))
    .optional()
    .nullable() });
 
type Category = {
  id: number;
  title: string;
  slug: string;
  imageUrl: string | null;
  description: string | null;
  parentId: number | null;
  children?: unknown[];
  _count?: { products: number };
};
 
/**
 * GET /api/admin/categories
 * Get all categories with hierarchy and product counts
 * Public endpoint - no authentication required
 */
async function handleGet(): Promise<NextResponse<ApiSuccessResponse<Category[]> | ApiErrorResponse>> {
  const categories = await prisma.category.findMany({
    include: {
      children: true,
      _count: { select: { products: true } } },
    orderBy: { title: "asc" } });
 
  return successResponse(categories);
}
 
/**
 * POST /api/admin/categories
 * Create new category
 * Admin only endpoint
 */
async function handlePost(
  request: NextRequest
): Promise<NextResponse<ApiSuccessResponse<Category> | ApiErrorResponse>> {
  const body = await request.json();
 
  // Validate request body
  const validation = createCategorySchema.safeParse(body);
  if (!validation.success) {
    throw ApiError.validation("Validation failed", validation.error.flatten().fieldErrors);
  }
 
  const { title, slug, imageUrl, description, parentId } = validation.data;
 
  const category = await prisma.category.create({
    data: {
      title,
      slug,
      imageUrl: imageUrl || null,
      description: description || null,
      parentId: parentId || null },
    include: { children: true } });
 
  // Invalidate all categories cache entries
  await invalidatePattern("categories:*");
 
  return createdResponse(category);
}
 
export const GET = withErrorHandling(handleGet);
export const POST = withErrorHandling(withAdmin(handlePost));