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

100% Statements 101/101
100% Branches 9/9
100% Functions 2/2
100% Lines 101/101

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 1021x 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 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 5x 5x 5x 4x 4x 4x 4x 4x 4x 5x 5x 5x 5x 5x 5x 5x 1x 1x 1x 1x 1x 9x 9x 9x 9x 9x 9x 5x 5x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 1x 1x 1x  
export const dynamic = "force-dynamic";
 
import { NextRequest, NextResponse } from 'next/server';
import { } from "next-auth";
import { prisma } from "@/lib/prisma";
import { logger } from "@/lib/logging";
import { z } from "zod";
import {
  withAdmin,
  withErrorHandling,
  successResponse,
  createdResponse,
  ApiError,
  ApiSuccessResponse,
  ApiErrorResponse } from "@/lib/api";
import { } from "@/lib/api/middleware";
 
const LOG_CATEGORY = "ADMIN_TESTIMONIALS_API";
 
// Validation schema for creating/updating testimonials
const testimonialSchema = z.object({
  review: z.string().min(10, "Review must be at least 10 characters"),
  authorName: z.string().min(2, "Author name must be at least 2 characters"),
  authorRole: z.string().min(2, "Author role must be at least 2 characters"),
  authorImage: z
    .string()
    .url("Author image must be a valid URL")
    .or(z.string().startsWith("/")),
  isActive: z.boolean().optional().default(true) });
 
/**
 * GET /api/admin/testimonials
 * List all testimonials (including inactive)
 */
async function handleGet(request: NextRequest): Promise<NextResponse<ApiSuccessResponse<unknown> | ApiErrorResponse>> {
  const { searchParams } = new URL(request.url);
  const page = parseInt(searchParams.get("page") || "1");
  const limit = Math.min(parseInt(searchParams.get("limit") || "20"), 100);
  const skip = (page - 1) * limit;
 
  const [testimonials, total] = await Promise.all([
    prisma.testimonial.findMany({
      skip,
      take: limit,
      orderBy: { createdAt: "desc" } }),
    prisma.testimonial.count(),
  ]);
 
  return successResponse({
    testimonials: testimonials.map((t) => ({
      id: t.id,
      review: t.review,
      authorName: t.authorName,
      authorRole: t.authorRole,
      authorImage: t.authorImage,
      isActive: t.isActive,
      createdAt: t.createdAt })),
    pagination: {
      page,
      limit,
      total,
      pages: Math.ceil(total / limit) } });
}
 
/**
 * POST /api/admin/testimonials
 * Create a new testimonial
 */
async function handlePost(request: NextRequest): Promise<NextResponse<ApiSuccessResponse<unknown> | ApiErrorResponse>> {
  const body = await request.json();
 
  // Validate input
  const validationResult = testimonialSchema.safeParse(body);
  if (!validationResult.success) {
    throw ApiError.validation("Validation failed", validationResult.error.issues);
  }
 
  const validatedData = validationResult.data;
 
  const testimonial = await prisma.testimonial.create({
    data: {
      review: validatedData.review,
      authorName: validatedData.authorName,
      authorRole: validatedData.authorRole,
      authorImage: validatedData.authorImage,
      isActive: validatedData.isActive } });
 
  logger.info(`Created testimonial ${testimonial.id}`, { category: LOG_CATEGORY });
 
  return createdResponse({
    id: testimonial.id,
    review: testimonial.review,
    authorName: testimonial.authorName,
    authorRole: testimonial.authorRole,
    authorImage: testimonial.authorImage,
    isActive: testimonial.isActive,
    createdAt: testimonial.createdAt });
}
 
export const GET = withErrorHandling(withAdmin(handleGet));
export const POST = withErrorHandling(withAdmin(handlePost));