feat(agent-api): POST /api/agent/cards/:cardId/subtasks
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0e74dbdf14
commit
ded7ffbb1f
1 changed files with 68 additions and 0 deletions
68
src/app/api/agent/cards/[cardId]/subtasks/route.ts
Normal file
68
src/app/api/agent/cards/[cardId]/subtasks/route.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { getAdminPb } from "@/lib/pb/admin";
|
||||||
|
import { requireAgentToken } from "@/lib/agent/auth";
|
||||||
|
import { positionBetween } from "@/lib/ordering/fractional";
|
||||||
|
import { jsonError, jsonOk } from "@/lib/http/json";
|
||||||
|
import type { CardRecord, SubtaskRecord } from "@/lib/pb/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/agent/cards/:cardId/subtasks — turn a plan into a checklist.
|
||||||
|
*
|
||||||
|
* Appends after the card's existing subtasks, in the order given, using the
|
||||||
|
* same fractional-position spacing every other subtask-creation path in this
|
||||||
|
* app uses (see addSubtask in src/app/actions/subtasks.ts).
|
||||||
|
*/
|
||||||
|
export async function POST(
|
||||||
|
request: Request,
|
||||||
|
context: { params: Promise<{ cardId: string }> }
|
||||||
|
): Promise<Response> {
|
||||||
|
const denied = requireAgentToken(request);
|
||||||
|
if (denied) return denied;
|
||||||
|
|
||||||
|
const { cardId } = await context.params;
|
||||||
|
|
||||||
|
let json: unknown;
|
||||||
|
try {
|
||||||
|
json = await request.json();
|
||||||
|
} catch {
|
||||||
|
return jsonError("Invalid request body", 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const titles = (json as { titles?: unknown } | null)?.titles;
|
||||||
|
if (!Array.isArray(titles)) {
|
||||||
|
return jsonError("titles must be an array of strings", 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleaned = titles
|
||||||
|
.filter((t): t is string => typeof t === "string")
|
||||||
|
.map((t) => t.trim().slice(0, 200))
|
||||||
|
.filter((t) => t.length > 0);
|
||||||
|
|
||||||
|
if (cleaned.length === 0) {
|
||||||
|
return jsonError("At least one non-empty title is required", 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pb = await getAdminPb();
|
||||||
|
const card = await pb.collection("cards").getOne<CardRecord>(cardId).catch(() => null);
|
||||||
|
if (!card || card.archived) return jsonError("Card not found", 404);
|
||||||
|
|
||||||
|
const last = await pb
|
||||||
|
.collection("subtasks")
|
||||||
|
.getFirstListItem<SubtaskRecord>(`card = "${cardId}"`, { sort: "-position" })
|
||||||
|
.catch(() => null);
|
||||||
|
|
||||||
|
let position: number | null = last ? last.position : null;
|
||||||
|
const created: string[] = [];
|
||||||
|
|
||||||
|
for (const title of cleaned) {
|
||||||
|
position = positionBetween(position, null);
|
||||||
|
const subtask = await pb.collection("subtasks").create({
|
||||||
|
card: cardId,
|
||||||
|
title,
|
||||||
|
done: false,
|
||||||
|
position,
|
||||||
|
});
|
||||||
|
created.push(subtask.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonOk({ created }, 201);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue