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.