Organize/lib/actions/groups.ts

162 lines
4.7 KiB
TypeScript

"use server";
import { revalidatePath } from "next/cache";
import { prisma } from "@/lib/db";
import { requireUserId } from "@/lib/auth-helpers";
import { GroupColorSchema, GroupTitleSchema } from "@/lib/validation/group";
import type { GroupDTO } from "@/types/board";
export async function createGroup(
categoryId: string,
title: string,
color: string
): Promise<GroupDTO> {
const userId = await requireUserId();
const parsedTitle = GroupTitleSchema.parse(title);
const parsedColor = GroupColorSchema.parse(color);
const category = await prisma.category.findFirst({
where: { id: categoryId, userId },
select: { id: true },
});
if (!category) throw new Error("Category not found");
const count = await prisma.group.count({ where: { categoryId } });
const group = await prisma.group.create({
data: { title: parsedTitle, color: parsedColor, order: count, categoryId },
});
revalidatePath("/");
return {
id: group.id,
title: group.title,
color: group.color,
order: group.order,
noteContent: group.noteContent,
categoryId: group.categoryId,
todos: [],
};
}
export async function updateGroup(
groupId: string,
data: { title?: string; color?: string }
): Promise<void> {
const userId = await requireUserId();
const update: { title?: string; color?: string } = {};
if (data.title !== undefined) update.title = GroupTitleSchema.parse(data.title);
if (data.color !== undefined) update.color = GroupColorSchema.parse(data.color);
const { count } = await prisma.group.updateMany({
where: { id: groupId, category: { userId } },
data: update,
});
if (count === 0) throw new Error("Group not found");
revalidatePath("/");
}
export async function deleteGroup(groupId: string): Promise<void> {
const userId = await requireUserId();
const { count } = await prisma.group.deleteMany({
where: { id: groupId, category: { userId } },
});
if (count === 0) throw new Error("Group not found");
revalidatePath("/");
}
/**
* Archives a group instead of deleting it -- the row (and its to-dos) is
* kept, just excluded from the Home board query going forward.
*/
export async function archiveGroup(groupId: string): Promise<void> {
const userId = await requireUserId();
const { count } = await prisma.group.updateMany({
where: { id: groupId, category: { userId } },
data: { archivedAt: new Date() },
});
if (count === 0) throw new Error("Group not found");
revalidatePath("/");
}
/** Persists a new card order within a single lane. */
export async function reorderGroupsInCategory(
categoryId: string,
orderedGroupIds: string[]
): Promise<void> {
const userId = await requireUserId();
const category = await prisma.category.findFirst({
where: { id: categoryId, userId },
include: { groups: { select: { id: true } } },
});
if (!category) throw new Error("Category not found");
const ownedIds = new Set(category.groups.map((g) => g.id));
if (
orderedGroupIds.length !== ownedIds.size ||
!orderedGroupIds.every((id) => ownedIds.has(id))
) {
throw new Error("Group list does not match this category's groups");
}
await prisma.$transaction(
orderedGroupIds.map((id, index) =>
prisma.group.update({ where: { id }, data: { order: index } })
)
);
revalidatePath("/");
}
/**
* Moves a card to a different lane and persists the resulting order for
* both the source and destination lanes.
*/
export async function moveGroupToCategory(
groupId: string,
targetCategoryId: string,
orderedGroupIdsInTargetLane: string[],
orderedGroupIdsInSourceLane: string[]
): Promise<void> {
const userId = await requireUserId();
const [group, targetCategory] = await Promise.all([
prisma.group.findFirst({
where: { id: groupId, category: { userId } },
select: { id: true, categoryId: true },
}),
prisma.category.findFirst({ where: { id: targetCategoryId, userId }, select: { id: true } }),
]);
if (!group) throw new Error("Group not found");
if (!targetCategory) throw new Error("Target category not found");
const targetIndex = orderedGroupIdsInTargetLane.indexOf(groupId);
if (targetIndex === -1) {
throw new Error("Moved group must be present in the target lane's order list");
}
await prisma.$transaction([
prisma.group.update({
where: { id: groupId },
data: { categoryId: targetCategoryId, order: targetIndex },
}),
...orderedGroupIdsInTargetLane
.map((id, index) => ({ id, index }))
.filter(({ id }) => id !== groupId)
.map(({ id, index }) => prisma.group.update({ where: { id }, data: { order: index } })),
...orderedGroupIdsInSourceLane.map((id, index) =>
prisma.group.update({ where: { id }, data: { order: index } })
),
]);
revalidatePath("/");
}