All files / src/app/api/admin/hero/feature-cards route.ts

99.18% Statements 122/123
84.61% Branches 11/13
100% Functions 2/2
99.18% Lines 122/123

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 1241x 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 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 3x 3x 3x 1x 1x   3x 3x 3x 3x 1x 1x 1x 1x 1x 10x 10x 10x 10x 10x 10x 3x 3x 7x 7x 7x 7x 7x 7x 7x 10x 1x 1x 6x 6x 6x 6x 6x 6x 6x 10x 1x 1x 1x 1x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 4x 4x 4x 1x 1x 1x  
export const dynamic = "force-dynamic";
 
/**
 * Admin Hero Feature Cards API
 * CRUD operations for hero feature cards (side cards)
 */
 
import { NextRequest, NextResponse } from 'next/server';
import { } from "next-auth";
import { prisma } from "@/lib/prisma";
import { z } from "zod";
import {
  withAdmin,
  withErrorHandling,
  successResponse,
  createdResponse,
  ApiError,
  ApiSuccessResponse,
  ApiErrorResponse } from "@/lib/api";
import { } from "@/lib/api/middleware";
 
// Validation schema for creating feature card
const createFeatureCardSchema = z.object({
  productId: z.number().int().positive(),
  title: z.string().max(200).optional().nullable(),
  subtitle: z.string().max(100).optional().nullable(),
  position: z.enum(["top", "bottom"]),
  isActive: z.boolean().default(true) });
 
/**
 * GET /api/admin/hero/feature-cards
 * List all feature cards
 */
async function handleGet(): Promise<NextResponse<ApiSuccessResponse<unknown> | ApiErrorResponse>> {
  const cards = await prisma.heroFeatureCard.findMany({
    orderBy: { position: "asc" },
    include: {
      product: {
        select: {
          id: true,
          title: true,
          price: true,
          discountedPrice: true,
          images: {
            select: {
              id: true,
              url: true,
              thumbnailUrl: true },
            orderBy: { order: "asc" },
            take: 1 } } } } });
 
  // Sort: top first, then bottom
  const sortedCards = cards.sort((a, b) => {
    if (a.position === "top" && b.position === "bottom") return -1;
    if (a.position === "bottom" && b.position === "top") return 1;
    return 0;
  });
 
  return successResponse({ cards: sortedCards });
}
 
/**
 * POST /api/admin/hero/feature-cards
 * Create new feature card
 */
async function handlePost(request: NextRequest): Promise<NextResponse<ApiSuccessResponse<unknown> | ApiErrorResponse>> {
  const body = await request.json();
 
  // Validate input
  const validationResult = createFeatureCardSchema.safeParse(body);
  if (!validationResult.success) {
    throw ApiError.validation("Validation failed", validationResult.error.issues);
  }
 
  const validatedData = validationResult.data;
 
  // Check if product exists
  const product = await prisma.product.findUnique({
    where: { id: validatedData.productId } });
 
  if (!product) {
    throw ApiError.notFound("Product");
  }
 
  // Check if position is already taken by an active card
  const existingCard = await prisma.heroFeatureCard.findFirst({
    where: {
      position: validatedData.position,
      isActive: true } });
 
  if (existingCard) {
    throw ApiError.badRequest(
      `A feature card already exists in the ${validatedData.position} position. Delete or deactivate it first, or update the existing card.`
    );
  }
 
  const card = await prisma.heroFeatureCard.create({
    data: {
      productId: validatedData.productId,
      title: validatedData.title,
      subtitle: validatedData.subtitle,
      position: validatedData.position,
      isActive: validatedData.isActive },
    include: {
      product: {
        select: {
          id: true,
          title: true,
          price: true,
          discountedPrice: true,
          images: {
            select: {
              id: true,
              url: true,
              thumbnailUrl: true },
            orderBy: { order: "asc" },
            take: 1 } } } } });
 
  return createdResponse({ card });
}
 
export const GET = withErrorHandling(withAdmin(handleGet));
export const POST = withErrorHandling(withAdmin(handlePost));