Enhance tags

This commit is contained in:
2025-12-21 01:06:27 +01:00
parent e90578c98a
commit 6fc641306a
20 changed files with 687 additions and 111 deletions

View File

@ -1,23 +1,23 @@
"use server";
// "use server";
import { prisma } from "@/lib/prisma";
// import { prisma } from "@/lib/prisma";
export async function deleteItems(itemId: string, type: string) {
// export async function deleteItems(itemId: string, type: string) {
switch (type) {
case "categories":
await prisma.artCategory.delete({ where: { id: itemId } });
break;
case "tags":
await prisma.artTag.delete({ where: { id: itemId } });
break;
// case "types":
// await prisma.portfolioType.delete({ where: { id: itemId } });
// break;
// case "albums":
// await prisma.portfolioAlbum.delete({ where: { id: itemId } });
// break;
}
// switch (type) {
// case "categories":
// await prisma.artCategory.delete({ where: { id: itemId } });
// break;
// case "tags":
// await prisma.artTag.delete({ where: { id: itemId } });
// break;
// // case "types":
// // await prisma.portfolioType.delete({ where: { id: itemId } });
// // break;
// // case "albums":
// // await prisma.portfolioAlbum.delete({ where: { id: itemId } });
// // break;
// }
return { success: true };
}
// return { success: true };
// }

View File

@ -1,9 +1,9 @@
"use server"
import { prisma } from "@/lib/prisma"
import { tagSchema } from "@/schemas/artworks/tagSchema"
import { TagFormInput, tagSchema } from "@/schemas/artworks/tagSchema"
export async function createTag(formData: tagSchema) {
export async function createTag(formData: TagFormInput) {
const parsed = tagSchema.safeParse(formData)
if (!parsed.success) {
@ -12,25 +12,43 @@ export async function createTag(formData: tagSchema) {
}
const data = parsed.data
const parentId = data.parentId ?? null;
const tagSlug = data.name.toLowerCase().replace(/\s+/g, "-");
const created = await prisma.artTag.create({
data: {
name: data.name,
slug: data.slug,
description: data.description
},
})
if (data.categoryIds) {
await prisma.artTag.update({
where: { id: created.id },
const created = await prisma.$transaction(async (tx) => {
const tag = await tx.artTag.create({
data: {
categories: {
set: data.categoryIds.map(id => ({ id }))
}
}
name: data.name,
slug: tagSlug,
description: data.description,
parentId
},
});
}
if (data.categoryIds) {
await tx.artTag.update({
where: { id: tag.id },
data: {
categories: {
set: data.categoryIds.map(id => ({ id }))
}
}
});
}
if (data.aliases && data.aliases.length > 0) {
await tx.artTagAlias.createMany({
data: data.aliases.map((alias) => ({
tagId: tag.id,
alias,
})),
skipDuplicates: true,
});
}
return tag;
});
return created
}

View File

@ -0,0 +1,40 @@
"use server";
import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";
export async function deleteTag(tagId: string) {
const tag = await prisma.artTag.findUnique({
where: { id: tagId },
select: {
id: true,
_count: {
select: {
artworks: true,
children: true,
},
},
},
});
if (!tag) {
throw new Error("Tag not found.");
}
if (tag._count.artworks > 0) {
throw new Error("Cannot delete tag: it is used by artworks.");
}
if (tag._count.children > 0) {
throw new Error("Cannot delete tag: it has child tags.");
}
await prisma.$transaction(async (tx) => {
await tx.artTagAlias.deleteMany({ where: { tagId } });
await tx.artTag.delete({ where: { id: tagId } });
});
revalidatePath("/tags");
return { success: true };
}

View File

@ -0,0 +1,17 @@
"use server"
import { prisma } from "@/lib/prisma";
export async function isDescendant(tagId: string, possibleAncestorId: string) {
// Walk upwards from possibleAncestorId; if we hit tagId, it's a cycle.
let current: string | null = possibleAncestorId;
while (current) {
if (current === tagId) return true;
const t = await prisma.artTag.findUnique({
where: { id: current },
select: { parentId: true },
});
current = t?.parentId ?? null;
}
return false;
}

View File

@ -1,10 +1,10 @@
"use server"
import { prisma } from '@/lib/prisma';
import { tagSchema } from '@/schemas/artworks/tagSchema';
import { z } from 'zod/v4';
import { TagFormInput, tagSchema } from '@/schemas/artworks/tagSchema';
import { isDescendant } from './isDescendant';
export async function updateTag(id: string, rawData: z.infer<typeof tagSchema>) {
export async function updateTag(id: string, rawData: TagFormInput) {
const parsed = tagSchema.safeParse(rawData)
if (!parsed.success) {
@ -14,25 +14,60 @@ export async function updateTag(id: string, rawData: z.infer<typeof tagSchema>)
const data = parsed.data
const updated = await prisma.artTag.update({
where: { id },
data: {
name: data.name,
slug: data.slug,
description: data.description
},
})
const parentId = data.parentId ?? null;
const tagSlug = data.name.toLowerCase().replace(/\s+/g, "-");
if (data.categoryIds) {
await prisma.artTag.update({
where: { id: id },
data: {
categories: {
set: data.categoryIds.map(id => ({ id }))
}
}
});
if (parentId === id) {
throw new Error("A tag cannot be its own parent.");
}
if (parentId) {
const cycle = await isDescendant(id, parentId);
if (cycle) throw new Error("Invalid parent tag (would create a cycle).");
}
const updated = await prisma.$transaction(async (tx) => {
const tag = await tx.artTag.update({
where: { id },
data: {
name: data.name,
slug: tagSlug,
description: data.description,
parentId,
categories: data.categoryIds
? { set: data.categoryIds.map((cid) => ({ id: cid })) }
: undefined,
},
});
const existing = await tx.artTagAlias.findMany({
where: { tagId: id },
select: { id: true, alias: true },
});
const desired = new Set((data.aliases ?? []).map((a) => a));
const existingSet = new Set(existing.map((a) => a.alias));
const toCreate = Array.from(desired).filter((a) => !existingSet.has(a));
const toDeleteIds = existing
.filter((a) => !desired.has(a.alias))
.map((a) => a.id);
if (toDeleteIds.length > 0) {
await tx.artTagAlias.deleteMany({
where: { id: { in: toDeleteIds } },
});
}
if (toCreate.length > 0) {
await tx.artTagAlias.createMany({
data: toCreate.map((alias) => ({ tagId: id, alias })),
skipDuplicates: true,
});
}
return tag;
});
return updated
}