"use server"; import { revalidatePath } from "next/cache"; import { prisma } from "@/lib/db"; import { requireUserId } from "@/lib/auth-helpers"; import { CategoryNameSchema } from "@/lib/validation/category"; import type { CategoryDTO } from "@/types/board"; export async function createCategory(name: string): Promise { const userId = await requireUserId(); const parsedName = CategoryNameSchema.parse(name); const count = await prisma.category.count({ where: { userId } }); const category = await prisma.category.create({ data: { name: parsedName, order: count, userId }, }); revalidatePath("/"); return { id: category.id, name: category.name, order: category.order, groups: [] }; } export async function renameCategory(categoryId: string, name: string): Promise { const userId = await requireUserId(); const parsedName = CategoryNameSchema.parse(name); const { count } = await prisma.category.updateMany({ where: { id: categoryId, userId }, data: { name: parsedName }, }); if (count === 0) throw new Error("Category not found"); revalidatePath("/"); } export type DeleteCategoryResult = { error?: string }; /** * Deleting a non-empty category would cascade-delete every group (and * their to-dos) inside it -- so this is an expected, user-facing * validation rather than an exceptional failure, and is modeled as a * return value rather than a thrown error (Next.js redacts thrown Server * Action error messages in production, so a specific message needs to * come back this way to actually reach the client). */ export async function deleteCategory(categoryId: string): Promise { const userId = await requireUserId(); const category = await prisma.category.findFirst({ where: { id: categoryId, userId }, select: { _count: { select: { groups: true } } }, }); if (!category) return { error: "Category not found." }; if (category._count.groups > 0) { return { error: "This category still has groups in it. Delete or move them out first." }; } await prisma.category.delete({ where: { id: categoryId } }); revalidatePath("/"); return {}; } /** * Persists a new lane order for all of the current user's categories. * `orderedIds` must contain every category id the user owns. */ export async function reorderCategories(orderedIds: string[]): Promise { const userId = await requireUserId(); const owned = await prisma.category.findMany({ where: { userId }, select: { id: true }, }); const ownedIds = new Set(owned.map((c) => c.id)); if (orderedIds.length !== ownedIds.size || !orderedIds.every((id) => ownedIds.has(id))) { throw new Error("Category list does not match the current user's categories"); } await prisma.$transaction( orderedIds.map((id, index) => prisma.category.update({ where: { id }, data: { order: index } }) ) ); revalidatePath("/"); }