162 lines
4.7 KiB
TypeScript
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("/");
|
|
}
|