Organize/lib/actions/todos.ts

112 lines
3.2 KiB
TypeScript

"use server";
import { revalidatePath } from "next/cache";
import { prisma } from "@/lib/db";
import { requireUserId } from "@/lib/auth-helpers";
import { TodoDetailsSchema, TodoTitleSchema } from "@/lib/validation/todo";
import type { TodoDTO } from "@/types/board";
export async function createTodo(
groupId: string,
title: string,
details?: string
): Promise<TodoDTO> {
const userId = await requireUserId();
const parsedTitle = TodoTitleSchema.parse(title);
const parsedDetails = details ? TodoDetailsSchema.parse(details) : undefined;
const group = await prisma.group.findFirst({
where: { id: groupId, category: { userId } },
select: { id: true },
});
if (!group) throw new Error("Group not found");
const count = await prisma.todo.count({ where: { groupId } });
const todo = await prisma.todo.create({
data: { title: parsedTitle, details: parsedDetails ?? null, order: count, groupId },
});
revalidatePath("/");
return {
id: todo.id,
title: todo.title,
details: todo.details,
completed: todo.completed,
order: todo.order,
groupId: todo.groupId,
};
}
export async function updateTodo(
todoId: string,
data: { title?: string; details?: string | null }
): Promise<void> {
const userId = await requireUserId();
const update: { title?: string; details?: string | null } = {};
if (data.title !== undefined) update.title = TodoTitleSchema.parse(data.title);
if (data.details !== undefined) {
update.details = data.details ? TodoDetailsSchema.parse(data.details) : null;
}
const { count } = await prisma.todo.updateMany({
where: { id: todoId, group: { category: { userId } } },
data: update,
});
if (count === 0) throw new Error("To-do not found");
revalidatePath("/");
}
export async function toggleTodo(todoId: string, completed: boolean): Promise<void> {
const userId = await requireUserId();
const { count } = await prisma.todo.updateMany({
where: { id: todoId, group: { category: { userId } } },
data: { completed },
});
if (count === 0) throw new Error("To-do not found");
revalidatePath("/");
}
export async function deleteTodo(todoId: string): Promise<void> {
const userId = await requireUserId();
const { count } = await prisma.todo.deleteMany({
where: { id: todoId, group: { category: { userId } } },
});
if (count === 0) throw new Error("To-do not found");
revalidatePath("/");
}
/** Persists a new to-do order within a single group. */
export async function reorderTodos(groupId: string, orderedTodoIds: string[]): Promise<void> {
const userId = await requireUserId();
const group = await prisma.group.findFirst({
where: { id: groupId, category: { userId } },
include: { todos: { select: { id: true } } },
});
if (!group) throw new Error("Group not found");
const ownedIds = new Set(group.todos.map((t) => t.id));
if (
orderedTodoIds.length !== ownedIds.size ||
!orderedTodoIds.every((id) => ownedIds.has(id))
) {
throw new Error("To-do list does not match this group's to-dos");
}
await prisma.$transaction(
orderedTodoIds.map((id, index) =>
prisma.todo.update({ where: { id }, data: { order: index } })
)
);
revalidatePath("/");
}