All files / src/app/api/cart/merge route.ts

100% Statements 101/101
100% Branches 10/10
100% Functions 1/1
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 1x 1x 1x 1x 1x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 1x 1x 1x 1x 7x 7x 8x 1x 1x 1x 1x 1x 1x 1x 6x 6x 8x 1x 1x 5x 5x 5x 5x 5x 5x 5x 5x 5x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 3x 3x 3x 3x 3x 3x 3x 3x 3x 1x 1x  
export const dynamic = "force-dynamic";
 
import { NextRequest, NextResponse } from 'next/server';
import {
  withAuth,
  withErrorHandling,
  successResponse,
  validationErrorResponse,
  ApiSuccessResponse,
  ApiErrorResponse } from "@/lib/api";
import { RouteContext } from "@/lib/api/middleware";
import { prisma } from "@/lib/prisma";
import {
  mergeAnonymousCartToUser,
  transformDbCartToCartItems,
  validateCartItems,
  ConflictItem } from "@/lib/cart";
import { applyCartMerge } from "@/lib/cart/cart-merge";
import { Session } from "next-auth";
 
interface CartMergeResponse {
  success: boolean;
  merged: number;
  conflicts: number;
  conflictDetails: ConflictItem[];
  message: string;
}
 
/**
 * POST /api/cart/merge
 *
 * Merges anonymous cart (from cookie) into user's database cart
 * Called after user logs in
 *
 * Request body:
 * {
 *   anonymousItems: CartItem[] // Items from anonymous cart cookie
 * }
 */
async function handlePost(
  request: NextRequest,
  _context: RouteContext | undefined,
  session: Session
): Promise<NextResponse<ApiSuccessResponse<CartMergeResponse> | ApiErrorResponse>> {
  const { anonymousItems } = await request.json();
  const userId = session.user.id;
  const deviceId = request.headers.get("x-device-id") || "default";
 
  // Validate input
  if (!Array.isArray(anonymousItems)) {
    return validationErrorResponse(
      "Invalid request: anonymousItems must be an array"
    );
  }
 
  // If no anonymous items, just return success
  if (anonymousItems.length === 0) {
    return successResponse({
      success: true,
      merged: 0,
      conflicts: 0,
      conflictDetails: [],
      message: "No anonymous items to merge"});
  }
 
  // Validate cart items
  if (!validateCartItems(anonymousItems)) {
    return validationErrorResponse("Invalid cart items format");
  }
 
  // Fetch user's current cart from database
  const userCartDb = await prisma.cart.findMany({
    where: { userId },
    include: {
      product: {
        include: {
          images: {
            orderBy: { order: "asc" }}}}}});
 
  // Transform database cart to CartItem format
  const userCartItems = transformDbCartToCartItems(userCartDb);
 
  // Merge the carts
  const { merged, conflicts, message } = mergeAnonymousCartToUser(
    anonymousItems,
    userCartItems
  );
 
  // Apply merge to database
  await applyCartMerge(userId, merged, deviceId);
 
  // Prepare response
  return successResponse({
    success: true,
    merged: merged.length,
    conflicts: conflicts.length,
    conflictDetails: conflicts,
    message});
}
 
export const POST = withErrorHandling(withAuth(handlePost));