Error in delete request with nextjs 14, got an error on execution

126 Views Asked by At

i made an ecommerce app but in the admin dashboard which connect to mySql trough prisma but when i try to delete one product which is fetched in the store in a app apart, i got an error. Here i post the frontend component where the button and the delete request are: cell-action.tsx

"use client";

import axios from "axios";
import { Copy, Edit, MoreHorizontal, Trash } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
import { toast } from "react-hot-toast";

import { AlertModal } from "@/components/modals/alert-modal";
import { Button } from "@/components/ui/button";
import { 
  DropdownMenu, 
  DropdownMenuContent, 
  DropdownMenuItem, 
  DropdownMenuLabel, 
  DropdownMenuTrigger
} from "@/components/ui/dropdown-menu";

import { ProductColumn } from "./columns";

interface CellActionProps {
  data: ProductColumn;
}

export const CellAction: React.FC<CellActionProps> = ({
  data,
}) => {
  const [loading, setLoading] = useState(false);
  const [open, setOpen] = useState(false);
  const router = useRouter();
  const params = useParams();

  const onConfirm = async () => {
    try {
      setLoading(true);
      await axios.delete(`/api/${params.storeId}/products/${data.id}`);
      toast.success('Product deleted.');
      router.refresh();
    } catch (error) {
      toast.error('Something went wrong');
    } finally {
      setLoading(false);
      setOpen(false);
    }
  };

  const onCopy = (id: string) => {
    navigator.clipboard.writeText(id);
    toast.success('Product ID copied to clipboard.');
  }

  return (
    <>
      <AlertModal 
        isOpen={open} 
        onClose={() => setOpen(false)}
        onConfirm={onConfirm}
        loading={loading}
      />
      <DropdownMenu>
        <DropdownMenuTrigger asChild>
          <Button variant="ghost" className="h-8 w-8 p-0">
            <span className="sr-only">Open menu</span>
            <MoreHorizontal className="h-4 w-4" />
          </Button>
        </DropdownMenuTrigger>
        <DropdownMenuContent align="end">
          <DropdownMenuLabel>Actions</DropdownMenuLabel>
          <DropdownMenuItem
            onClick={() => onCopy(data.id)}
          >
            <Copy className="mr-2 h-4 w-4" /> Copy Id
          </DropdownMenuItem>
          <DropdownMenuItem
            onClick={() => router.push(`/${params.storeId}/products/${data.id}`)}
          >
            <Edit className="mr-2 h-4 w-4" /> Update
          </DropdownMenuItem>
          <DropdownMenuItem
            onClick={() => setOpen(true)}
          >
            <Trash className="mr-2 h-4 w-4" /> Delete
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </>
  );
};

and here the backend route code:

/api/[storeId]/products/[productId]/route.ts

import { NextResponse } from "next/server";
import { auth } from "@clerk/nextjs";

import prismadb from "@/lib/prismadb";

export async function GET(
  req: Request,
  { params }: { params: { productId: string } }
) {
  try {
    if (!params.productId) {
      return new NextResponse("Product id is required", { status: 400 });
    }

    const product = await prismadb.product.findUnique({
      where: {
        id: params.productId
      },
      include: {
        images: true,
        category: true,
        size: true,
        color: true,
      }
    });
  
    return NextResponse.json(product);
  } catch (error) {
    console.log('[PRODUCT_GET]', error);
    return new NextResponse("Internal error", { status: 500 });
  }
};

export async function DELETE(
  req: Request,
  { params }: { params: { productId: string, storeId: string } }
) {
  try {
    const { userId } = auth();

    if (!userId) {
      return new NextResponse("Unauthenticated", { status: 403 });
    }

    if (!params.productId) {
      return new NextResponse("Product id is required", { status: 400 });
    }

    const storeByUserId = await prismadb.store.findFirst({
      where: {
        id: params.storeId,
        userId
      }
    });

    if (!storeByUserId) {
      return new NextResponse("Unauthorized", { status: 405 });
    }

    const product = await prismadb.product.delete({
      where: {
        id: params.productId
      },
    });
  
    return NextResponse.json(product);
  } catch (error) {
    console.log('[PRODUCT_DELETE]', error);
    return new NextResponse("Internal error", { status: 500 });
  }
};


export async function PATCH(
  req: Request,
  { params }: { params: { productId: string, storeId: string } }
) {
  try {
    const { userId } = auth();

    const body = await req.json();

    const { name, price, categoryId, images, colorId, sizeId, isFeatured, isArchived } = body;

    if (!userId) {
      return new NextResponse("Unauthenticated", { status: 403 });
    }

    if (!params.productId) {
      return new NextResponse("Product id is required", { status: 400 });
    }

    if (!name) {
      return new NextResponse("Name is required", { status: 400 });
    }

    if (!images || !images.length) {
      return new NextResponse("Images are required", { status: 400 });
    }

    if (!price) {
      return new NextResponse("Price is required", { status: 400 });
    }

    if (!categoryId) {
      return new NextResponse("Category id is required", { status: 400 });
    }

    if (!colorId) {
      return new NextResponse("Color id is required", { status: 400 });
    }

    if (!sizeId) {
      return new NextResponse("Size id is required", { status: 400 });
    }

    const storeByUserId = await prismadb.store.findFirst({
      where: {
        id: params.storeId,
        userId
      }
    });

    if (!storeByUserId) {
      return new NextResponse("Unauthorized", { status: 405 });
    }

    await prismadb.product.update({
      where: {
        id: params.productId
      },
      data: {
        name,
        price,
        categoryId,
        colorId,
        sizeId,
        images: {
          deleteMany: {},
        },
        isFeatured,
        isArchived,
      },
    });

    const product = await prismadb.product.update({
      where: {
        id: params.productId
      },
      data: {
        images: {
          createMany: {
            data: [
              ...images.map((image: { url: string }) => image),
            ],
          },
        },
      },
    })
  
    return NextResponse.json(product);
  } catch (error) {
    console.log('[PRODUCT_PATCH]', error);
    return new NextResponse("Internal error", { status: 500 });
  }
};

and here are the prisma model: /prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url = env("DATABASE_URL")
  relationMode = "prisma"
}

model Store {
  id              String    @id @default(uuid())
  name            String
  userId          String
  billboards      Billboard[] @relation("StoreToBillboard")
  categories      Category[] @relation("StoreToCategory")
  products        Product[]  @relation("StoreToProduct")
  sizes           Size[]     @relation("StoreToSize")
  colors          Color[]     @relation("StoreToColor")
  orders          Order[]     @relation("StoreToOrder")
  createdAt       DateTime   @default(now())
  updatedAt       DateTime   @updatedAt
}

model Billboard {
  id          String @id @default(uuid())
  storeId     String
  store       Store @relation("StoreToBillboard", fields: [storeId], references: [id])
  label       String
  imageUrl    String
  categories  Category[]
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  @@index([storeId])
}

model Category {
  id          String      @id @default(uuid())
  storeId     String      // Foreign Key to Store
  store       Store       @relation("StoreToCategory", fields: [storeId], references: [id])
  billboardId String      // Foreign Key to Billboard
  billboard   Billboard   @relation(fields: [billboardId], references: [id])
  name        String
  products    Product[]   @relation("CategoryToProduct")
  createdAt   DateTime    @default(now())
  updatedAt   DateTime    @updatedAt

  @@index([storeId])
  @@index([billboardId])
}

model Product {
  id          String    @id @default(uuid())
  storeId     String    // Foreign Key to Store
  store       Store     @relation("StoreToProduct", fields: [storeId], references: [id])
  categoryId  String    // Foreign Key to Category
  category    Category  @relation("CategoryToProduct", fields: [categoryId], references: [id])
  name        String
  price       Decimal
  isFeatured  Boolean   @default(false)
  isArchived  Boolean   @default(false)
  sizeId      String    // Foreign Key to Size
  size        Size      @relation(fields: [sizeId], references: [id])
  colorId     String    // Foreign Key to Color
  color       Color     @relation(fields: [colorId], references: [id])
  images      Image[]   // Relation to Image model
  orderItems  OrderItem[]   // Relation to Order model
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt

  @@index([storeId])
  @@index([categoryId])
  @@index([sizeId])
  @@index([colorId])
}

model Order {
  id        String    @id @default(uuid())
  storeId     String    // Foreign Key to Store
  store       Store     @relation("StoreToOrder", fields: [storeId], references: [id])
  orderItems OrderItem[] // Relation to OrderItem model
  isPaid     Boolean   @default(false)
  phone      String    @default("")
  address    String    @default("")
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt

  @@index([storeId])
}

// Intermediary for a many-to-many relationship
model OrderItem {
  id        String  @id @default(uuid())
  orderId   String  // Foreign Key to Order
  order     Order   @relation(fields: [orderId], references: [id])
  productId String  // Foreign Key to Product
  product   Product @relation(fields: [productId], references: [id])

  @@index([orderId])
  @@index([productId])
}

model Size {
  id          String    @id @default(uuid())
  storeId     String    // Foreign Key to Store
  store       Store     @relation("StoreToSize", fields: [storeId], references: [id])
  name        String
  value       String
  products    Product[] // Relation to Product model
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt

  @@index([storeId])
}

model Color {
  id          String   @id @default(uuid())
  storeId     String   // Foreign Key to Store
  store       Store    @relation("StoreToColor", fields: [storeId], references: [id])
  name        String
  value       String
  products    Product[] // Relation to Product model
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt

  @@index([storeId])
}

model Image {
  id          String   @id @default(uuid())
  productId   String   // Foreign Key to Product
  product     Product  @relation(fields: [productId], references: [id], onDelete: Cascade)
  url         String   // URL of the image
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  @@index([productId])
}

And this is the error: **[PRODUCT_DELETE] PrismaClientKnownRequestError: Invalid prisma.product.deleteMany() invocation:

The change you are trying to make would violate the required relation 'OrderItemToProduct' between the OrderItem and Product models.**

Hope you have a hint on how to possibly fix it.

0

There are 0 best solutions below