kanban-dev-webapp/src/app/actions/subtasks.ts

97 lines
No EOL
2.7 KiB
TypeScript

"use server";
import { revalidatePath } from "next/cache";
import { getServerPb } from "@/lib/pb/server";
import { requireSession } from "@/lib/auth/session";
import { logActivity } from "@/app/actions/activity";
import { positionBetween } from "@/lib/ordering/fractional";
import type { CardRecord, SubtaskRecord } from "@/lib/pb/types";
/** See the security note at the top of src/app/actions/cards.ts. */
async function requireOwner() {
const session = await requireSession();
if (session.role !== "owner") throw new Error("Not found");
return session;
}
function cardPath(clientSlug: string, projectSlug: string, cardId: string) {
return `/p/${clientSlug}/${projectSlug}/c/${cardId}`;
}
export async function addSubtask(input: {
cardId: string;
title: string;
clientSlug: string;
projectSlug: string;
}): Promise<void> {
await requireOwner();
const pb = await getServerPb();
const title = input.title.trim().slice(0, 200);
if (!title) return;
const last = await pb
.collection("subtasks")
.getFirstListItem<SubtaskRecord>(`card = "${input.cardId}"`, {
sort: "-position",
})
.catch(() => null);
await pb.collection("subtasks").create({
card: input.cardId,
title,
done: false,
position: positionBetween(last ? last.position : null, null),
});
revalidatePath(cardPath(input.clientSlug, input.projectSlug, input.cardId));
}
export async function toggleSubtask(input: {
subtaskId: string;
done: boolean;
clientSlug: string;
projectSlug: string;
}): Promise<void> {
const session = await requireOwner();
const pb = await getServerPb();
const subtask = await pb
.collection("subtasks")
.getOne<SubtaskRecord>(input.subtaskId);
await pb.collection("subtasks").update(input.subtaskId, { done: input.done });
const card = await pb.collection("cards").getOne<CardRecord>(subtask.card);
await logActivity({
project: card.project,
card: card.id,
actor: session.userId,
action: "subtask_toggled",
// `field` carries the subtask's title so the timeline can name it without
// a second lookup, and still reads correctly after the subtask is deleted.
field: subtask.title,
newValue: String(input.done),
});
revalidatePath(cardPath(input.clientSlug, input.projectSlug, subtask.card));
}
export async function deleteSubtask(input: {
subtaskId: string;
clientSlug: string;
projectSlug: string;
}): Promise<void> {
await requireOwner();
const pb = await getServerPb();
const subtask = await pb
.collection("subtasks")
.getOne<SubtaskRecord>(input.subtaskId);
await pb.collection("subtasks").delete(input.subtaskId);
revalidatePath(cardPath(input.clientSlug, input.projectSlug, subtask.card));
}