I have a project that is composed on Posts
and each post can have a collection of Tags
. I'm trying to enable an edit feature that allows Post tags to be updates / added / removed. Currently, my query upserts these tags, but I'm experiencing some trouble caused by the deletion aspect. Any time new tags are created, since they have no ID at the time, they are deleted immediately.
await Prisma.post.update({
where: {
id: postId,
},
data: {
title,
content,
updatedAt: new Date(),
tags: {
// Upsert tags, remove tags that are not in the request
upsert: tags.map((tag) => ({
where: {
id: tag.id ? tag.id : "0",
},
create: {
name: tag.name,
color: tag.color,
creator: {
connect: {
id: session.user.id,
},
},
},
update: {
name: tag.name,
color: tag.color,
},
})),
deleteMany: {
id: {
notIn: tags.map((tag) => tag.id ?? "0"),
},
},
},
},
include: {
tags: true,
},
});
I've attempted various solutions such as keying on the name or other properties (e.g. name AND color). None of these seem to fix the issue, as they end up duplicating tags and causing similar bugs.
Here are the schema I'm using my my Post and Tag models.
Post
model Post {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
title String
content String
authorId String
author User @relation(fields: [authorId], references: [id])
comments Comment[]
tags Tag[]
}
Tag
model Tag {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
name String
color String
creatorId String
creator User @relation(fields: [creatorId], references: [id])
posts Post[]
}
If someone could point me in the right direction as to how one handles a situation like this, I would greatly appreciate it.
You should cut out your steps in a transaction to delete the tags not in the query before inserting the new ones. Then you can create and update your data and return your post
In your schema.prisma
you should enable interactiveTransactions
, for this:
generator client {
provider = "prisma-client-js"
previewFeatures = ["interactiveTransactions"]
}
Then cut out your steps in a transaction
// Remove <Post> if you aren't in typescript
await prisma.$transaction<Post>(async (trx) => {
const existingTags = tags.filter(({ id }) => id);
const newTags = tags.filter(({ id }) => !id);
await trx.tag.deleteMany({
where: {
id: {
notIn: existingTags.map(({ id }) => id),
},
},
});
// Update the existing tags
await Promise.all(
tags.map((tag) =>
trx.tag.update({
where: {
id: tag.id,
},
data: {
color: tag.color,
name: tag.name,
},
})
)
);
await trx.tag.createMany({
data: newTags.map((tag) => ({
...tag,
creator: {
connect: {
id: session.user.id,
},
},
})),
});
return trx.post.update({
where: {
id: postId,
},
data: {
title,
content,
updatedAt: new Date(),
},
include: {
tags: true,
},
});
});