Moving the arttags table to tags table part 1
This commit is contained in:
@ -13,6 +13,7 @@ export async function deleteArtwork(artworkId: string) {
|
||||
colors: true,
|
||||
metadata: true,
|
||||
tags: true,
|
||||
tagsV2: true,
|
||||
categories: true,
|
||||
},
|
||||
});
|
||||
@ -74,6 +75,7 @@ export async function deleteArtwork(artworkId: string) {
|
||||
where: { id: artworkId },
|
||||
data: {
|
||||
tags: { set: [] },
|
||||
tagsV2: { set: [] },
|
||||
categories: { set: [] },
|
||||
},
|
||||
});
|
||||
@ -82,4 +84,4 @@ export async function deleteArtwork(artworkId: string) {
|
||||
await prisma.artwork.delete({ where: { id: artworkId } });
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,9 +15,9 @@ export async function getSingleArtwork(id: string) {
|
||||
categories: true,
|
||||
colors: { include: { color: true } },
|
||||
// sortContexts: true,
|
||||
tags: true,
|
||||
tagsV2: true,
|
||||
variants: true,
|
||||
timelapse: true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ function mapSortingToOrderBy(sorting: ArtworkTableInput["sorting"]) {
|
||||
// relation counts: Prisma supports ordering by _count
|
||||
albumsCount: (desc) => ({ albums: { _count: desc ? "desc" : "asc" } }),
|
||||
categoriesCount: (desc) => ({ categories: { _count: desc ? "desc" : "asc" } }),
|
||||
tagsCount: (desc) => ({ tags: { _count: desc ? "desc" : "asc" } }),
|
||||
tagsCount: (desc) => ({ tagsV2: { _count: desc ? "desc" : "asc" } }),
|
||||
};
|
||||
|
||||
const orderBy = sorting
|
||||
@ -89,7 +89,7 @@ export async function getArtworksTablePage(input: unknown) {
|
||||
gallery: { select: { id: true, name: true } },
|
||||
albums: { select: { id: true, name: true } },
|
||||
categories: { select: { id: true, name: true } },
|
||||
_count: { select: { albums: true, categories: true, tags: true } },
|
||||
_count: { select: { albums: true, categories: true, tagsV2: true } },
|
||||
},
|
||||
}),
|
||||
]);
|
||||
@ -109,7 +109,7 @@ export async function getArtworksTablePage(input: unknown) {
|
||||
categories: a.categories,
|
||||
albumsCount: a._count.albums,
|
||||
categoriesCount: a._count.categories,
|
||||
tagsCount: a._count.tags,
|
||||
tagsCount: a._count.tagsV2,
|
||||
}));
|
||||
|
||||
const out = { rows, total, pageIndex, pageSize };
|
||||
|
||||
@ -47,7 +47,7 @@ export async function updateArtwork(
|
||||
const tagsRelation =
|
||||
tagIds || tagsToCreate.length
|
||||
? {
|
||||
tags: {
|
||||
tagsV2: {
|
||||
set: [], // replace entire relation
|
||||
connect: (tagIds ?? []).map((tagId) => ({ id: tagId })),
|
||||
connectOrCreate: tagsToCreate.map((tName) => ({
|
||||
@ -94,4 +94,4 @@ export async function updateArtwork(
|
||||
});
|
||||
|
||||
return updatedArtwork;
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ export async function deleteCategory(catId: string) {
|
||||
id: true,
|
||||
_count: {
|
||||
select: {
|
||||
tags: true,
|
||||
tagLinks: true,
|
||||
artworks: true
|
||||
},
|
||||
},
|
||||
@ -25,7 +25,7 @@ export async function deleteCategory(catId: string) {
|
||||
throw new Error("Cannot delete category: it is used by artworks.");
|
||||
}
|
||||
|
||||
if (cat._count.tags > 0) {
|
||||
if (cat._count.tagLinks > 0) {
|
||||
throw new Error("Cannot delete category: it is used by tags.");
|
||||
}
|
||||
|
||||
@ -34,4 +34,4 @@ export async function deleteCategory(catId: string) {
|
||||
revalidatePath("/categories");
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,14 +3,17 @@
|
||||
import { prisma } from "@/lib/prisma"
|
||||
|
||||
export async function getCategoriesWithTags() {
|
||||
return await prisma.artCategory.findMany({ include: { tags: true }, orderBy: { sortIndex: "asc" } })
|
||||
return await prisma.artCategory.findMany({
|
||||
include: { tagLinks: { include: { tag: true } } },
|
||||
orderBy: { sortIndex: "asc" },
|
||||
})
|
||||
}
|
||||
|
||||
export async function getCategoriesWithCount() {
|
||||
return await prisma.artCategory.findMany({
|
||||
include: {
|
||||
_count: { select: { artworks: true, tags: true } },
|
||||
_count: { select: { artworks: true, tagLinks: true } },
|
||||
},
|
||||
orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,30 +17,30 @@ export async function createTag(formData: TagFormInput) {
|
||||
const tagSlug = data.name.toLowerCase().replace(/\s+/g, "-");
|
||||
|
||||
const created = await prisma.$transaction(async (tx) => {
|
||||
const tag = await tx.artTag.create({
|
||||
const tag = await tx.tag.create({
|
||||
data: {
|
||||
name: data.name,
|
||||
slug: tagSlug,
|
||||
description: data.description,
|
||||
isParent: data.isParent,
|
||||
showOnAnimalPage: data.showOnAnimalPage,
|
||||
parentId
|
||||
isVisible: data.isVisible ?? true,
|
||||
},
|
||||
});
|
||||
|
||||
if (data.categoryIds) {
|
||||
await tx.artTag.update({
|
||||
where: { id: tag.id },
|
||||
data: {
|
||||
categories: {
|
||||
set: data.categoryIds.map(id => ({ id }))
|
||||
}
|
||||
}
|
||||
await tx.tagCategory.createMany({
|
||||
data: data.categoryIds.map((categoryId) => ({
|
||||
tagId: tag.id,
|
||||
categoryId,
|
||||
isParent: data.isParent,
|
||||
showOnAnimalPage: data.showOnAnimalPage,
|
||||
parentTagId: parentId,
|
||||
})),
|
||||
skipDuplicates: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (data.aliases && data.aliases.length > 0) {
|
||||
await tx.artTagAlias.createMany({
|
||||
await tx.tagAlias.createMany({
|
||||
data: data.aliases.map((alias) => ({
|
||||
tagId: tag.id,
|
||||
alias,
|
||||
@ -53,4 +53,4 @@ export async function createTag(formData: TagFormInput) {
|
||||
});
|
||||
|
||||
return created
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,14 +4,13 @@ import { prisma } from "@/lib/prisma";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
export async function deleteTag(tagId: string) {
|
||||
const tag = await prisma.artTag.findUnique({
|
||||
const tag = await prisma.tag.findUnique({
|
||||
where: { id: tagId },
|
||||
select: {
|
||||
id: true,
|
||||
_count: {
|
||||
select: {
|
||||
artworks: true,
|
||||
children: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -25,16 +24,21 @@ export async function deleteTag(tagId: string) {
|
||||
throw new Error("Cannot delete tag: it is used by artworks.");
|
||||
}
|
||||
|
||||
if (tag._count.children > 0) {
|
||||
const parentUsage = await prisma.tagCategory.count({
|
||||
where: { parentTagId: tagId },
|
||||
});
|
||||
|
||||
if (parentUsage > 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 } });
|
||||
await tx.tagAlias.deleteMany({ where: { tagId } });
|
||||
await tx.tagCategory.deleteMany({ where: { tagId } });
|
||||
await tx.tag.delete({ where: { id: tagId } });
|
||||
});
|
||||
|
||||
revalidatePath("/tags");
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,5 +3,5 @@
|
||||
import { prisma } from "@/lib/prisma"
|
||||
|
||||
export async function getTags() {
|
||||
return await prisma.artTag.findMany({ orderBy: { sortIndex: "asc" } })
|
||||
}
|
||||
return await prisma.tag.findMany({ orderBy: { sortIndex: "asc" } })
|
||||
}
|
||||
|
||||
@ -3,20 +3,26 @@
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export async function isDescendant(tagId: string, possibleAncestorId: string): Promise<boolean> {
|
||||
// Walk upwards from possibleAncestorId; if we hit tagId, it's a cycle.
|
||||
let current: string | null = possibleAncestorId;
|
||||
|
||||
while (current) {
|
||||
// Walk upwards across any category hierarchy; if we hit tagId, it's a cycle.
|
||||
const visited = new Set<string>();
|
||||
const queue: string[] = [possibleAncestorId];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift();
|
||||
if (!current) continue;
|
||||
if (current === tagId) return true;
|
||||
if (visited.has(current)) continue;
|
||||
visited.add(current);
|
||||
|
||||
const t: { parentId: string | null } | null =
|
||||
await prisma.artTag.findUnique({
|
||||
where: { id: current },
|
||||
select: { parentId: true },
|
||||
});
|
||||
const parents = await prisma.tagCategory.findMany({
|
||||
where: { tagId: current, parentTagId: { not: null } },
|
||||
select: { parentTagId: true },
|
||||
});
|
||||
|
||||
current = t?.parentId ?? null;
|
||||
for (const p of parents) {
|
||||
if (p.parentTagId) queue.push(p.parentTagId);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
102
src/actions/tags/migrateArtTags.ts
Normal file
102
src/actions/tags/migrateArtTags.ts
Normal file
@ -0,0 +1,102 @@
|
||||
"use server";
|
||||
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
export async function migrateArtTags() {
|
||||
const artTags = await prisma.artTag.findMany({
|
||||
include: {
|
||||
aliases: true,
|
||||
categories: true,
|
||||
artworks: { select: { id: true } },
|
||||
},
|
||||
orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
|
||||
});
|
||||
|
||||
const idMap = new Map<string, string>();
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
for (const artTag of artTags) {
|
||||
const tag = await tx.tag.upsert({
|
||||
where: { slug: artTag.slug },
|
||||
update: {
|
||||
name: artTag.name,
|
||||
description: artTag.description,
|
||||
isVisible: true,
|
||||
},
|
||||
create: {
|
||||
name: artTag.name,
|
||||
slug: artTag.slug,
|
||||
description: artTag.description,
|
||||
isVisible: true,
|
||||
},
|
||||
});
|
||||
|
||||
idMap.set(artTag.id, tag.id);
|
||||
}
|
||||
|
||||
const aliasRows = artTags.flatMap((artTag) => {
|
||||
const tagId = idMap.get(artTag.id);
|
||||
if (!tagId) return [];
|
||||
return artTag.aliases.map((a) => ({
|
||||
tagId,
|
||||
alias: a.alias,
|
||||
}));
|
||||
});
|
||||
|
||||
if (aliasRows.length > 0) {
|
||||
await tx.tagAlias.createMany({
|
||||
data: aliasRows,
|
||||
skipDuplicates: true,
|
||||
});
|
||||
}
|
||||
|
||||
const categoryRows = artTags.flatMap((artTag) => {
|
||||
const tagId = idMap.get(artTag.id);
|
||||
if (!tagId) return [];
|
||||
const parentTagId = artTag.parentId
|
||||
? idMap.get(artTag.parentId) ?? null
|
||||
: null;
|
||||
|
||||
return artTag.categories.map((category) => ({
|
||||
tagId,
|
||||
categoryId: category.id,
|
||||
isParent: artTag.isParent,
|
||||
showOnAnimalPage: artTag.showOnAnimalPage,
|
||||
parentTagId,
|
||||
}));
|
||||
});
|
||||
|
||||
if (categoryRows.length > 0) {
|
||||
await tx.tagCategory.createMany({
|
||||
data: categoryRows,
|
||||
skipDuplicates: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Connect artwork relations outside the transaction to avoid timeouts.
|
||||
for (const artTag of artTags) {
|
||||
const tagId = idMap.get(artTag.id);
|
||||
if (!tagId) continue;
|
||||
|
||||
for (const artwork of artTag.artworks) {
|
||||
await prisma.artwork.update({
|
||||
where: { id: artwork.id },
|
||||
data: {
|
||||
tagsV2: {
|
||||
connect: { id: tagId },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const summary = {
|
||||
tags: artTags.length,
|
||||
aliases: artTags.reduce((sum, t) => sum + t.aliases.length, 0),
|
||||
categoryLinks: artTags.reduce((sum, t) => sum + t.categories.length, 0),
|
||||
};
|
||||
revalidatePath("/tags");
|
||||
return summary;
|
||||
}
|
||||
@ -27,22 +27,62 @@ export async function updateTag(id: string, rawData: TagFormInput) {
|
||||
}
|
||||
|
||||
const updated = await prisma.$transaction(async (tx) => {
|
||||
const tag = await tx.artTag.update({
|
||||
const tag = await tx.tag.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: data.name,
|
||||
slug: tagSlug,
|
||||
description: data.description,
|
||||
isParent: data.isParent,
|
||||
showOnAnimalPage: data.showOnAnimalPage,
|
||||
parentId,
|
||||
categories: data.categoryIds
|
||||
? { set: data.categoryIds.map((cid) => ({ id: cid })) }
|
||||
: undefined,
|
||||
isVisible: data.isVisible ?? true,
|
||||
},
|
||||
});
|
||||
|
||||
const existing = await tx.artTagAlias.findMany({
|
||||
if (data.categoryIds) {
|
||||
const existingLinks = await tx.tagCategory.findMany({
|
||||
where: { tagId: id },
|
||||
select: { id: true, categoryId: true },
|
||||
});
|
||||
|
||||
const desired = new Set(data.categoryIds);
|
||||
const existingSet = new Set(existingLinks.map((l) => l.categoryId));
|
||||
|
||||
const toCreate = data.categoryIds.filter((cid) => !existingSet.has(cid));
|
||||
const toDeleteIds = existingLinks
|
||||
.filter((l) => !desired.has(l.categoryId))
|
||||
.map((l) => l.id);
|
||||
|
||||
if (toDeleteIds.length > 0) {
|
||||
await tx.tagCategory.deleteMany({
|
||||
where: { id: { in: toDeleteIds } },
|
||||
});
|
||||
}
|
||||
|
||||
if (toCreate.length > 0) {
|
||||
await tx.tagCategory.createMany({
|
||||
data: toCreate.map((categoryId) => ({
|
||||
tagId: id,
|
||||
categoryId,
|
||||
isParent: data.isParent,
|
||||
showOnAnimalPage: data.showOnAnimalPage,
|
||||
parentTagId: parentId,
|
||||
})),
|
||||
skipDuplicates: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (existingLinks.length > 0) {
|
||||
await tx.tagCategory.updateMany({
|
||||
where: { tagId: id, categoryId: { in: data.categoryIds } },
|
||||
data: {
|
||||
isParent: data.isParent,
|
||||
showOnAnimalPage: data.showOnAnimalPage,
|
||||
parentTagId: parentId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const existing = await tx.tagAlias.findMany({
|
||||
where: { tagId: id },
|
||||
select: { id: true, alias: true },
|
||||
});
|
||||
@ -56,13 +96,13 @@ export async function updateTag(id: string, rawData: TagFormInput) {
|
||||
.map((a) => a.id);
|
||||
|
||||
if (toDeleteIds.length > 0) {
|
||||
await tx.artTagAlias.deleteMany({
|
||||
await tx.tagAlias.deleteMany({
|
||||
where: { id: { in: toDeleteIds } },
|
||||
});
|
||||
}
|
||||
|
||||
if (toCreate.length > 0) {
|
||||
await tx.artTagAlias.createMany({
|
||||
await tx.tagAlias.createMany({
|
||||
data: toCreate.map((alias) => ({ tagId: id, alias })),
|
||||
skipDuplicates: true,
|
||||
});
|
||||
@ -72,4 +112,4 @@ export async function updateTag(id: string, rawData: TagFormInput) {
|
||||
});
|
||||
|
||||
return updated
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user