From a297fe5f15b0ea0ba1e21c09773de771e8e33ccb Mon Sep 17 00:00:00 2001 From: Dave Date: Sat, 26 Sep 2026 09:37:19 +0200 Subject: [PATCH] feat(agent-api): GET /api/agent/cards/:cardId Co-Authored-By: Claude Haiku 4.5 --- src/app/api/agent/cards/[cardId]/route.ts | 88 +++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/app/api/agent/cards/[cardId]/route.ts diff --git a/src/app/api/agent/cards/[cardId]/route.ts b/src/app/api/agent/cards/[cardId]/route.ts new file mode 100644 index 0000000..ee6c7b1 --- /dev/null +++ b/src/app/api/agent/cards/[cardId]/route.ts @@ -0,0 +1,88 @@ +import { getAdminPb } from "@/lib/pb/admin"; +import { requireAgentToken } from "@/lib/agent/auth"; +import { jsonError, jsonOk } from "@/lib/http/json"; +import type { + CardRecord, + ColumnRecord, + CommentRecord, + CommitRecord, + SubtaskRecord, + UserRecord, +} from "@/lib/pb/types"; + +/** + * GET /api/agent/cards/:cardId — one card's full detail: everything the list + * endpoint returns, plus its existing checklist, existing comments (with + * author names resolved), and its 10 most recent linked commits — enough for + * an agent to know what has already touched this card before drafting a plan + * for it, without pulling full commit history. + */ +export async function GET( + request: Request, + context: { params: Promise<{ cardId: string }> } +): Promise { + const denied = requireAgentToken(request); + if (denied) return denied; + + const { cardId } = await context.params; + const pb = await getAdminPb(); + + const card = await pb.collection("cards").getOne(cardId).catch(() => null); + if (!card || card.archived) return jsonError("Card not found", 404); + + const column = await pb + .collection("columns") + .getOne(card.column) + .catch(() => null); + + const subtasks = await pb.collection("subtasks").getFullList({ + filter: `card = "${cardId}"`, + sort: "position", + }); + + const comments = await pb.collection("comments").getFullList({ + filter: `card = "${cardId}"`, + sort: "created", + }); + + const userIds = new Set(comments.map((c) => c.author).filter(Boolean)); + const names: Record = {}; + for (const id of userIds) { + const user = await pb.collection("users").getOne(id).catch(() => null); + names[id] = user?.name || user?.email || "Someone"; + } + + const recentCommits = await pb.collection("commits").getList(1, 10, { + filter: `card = "${cardId}" && is_merge = false`, + sort: "-authored_at", + }); + + return jsonOk({ + id: card.id, + number: card.number, + title: card.title, + description: card.description, + priority: card.priority, + target_date: card.target_date, + size_band: card.size_band, + column: column + ? { + id: column.id, + name: column.name, + is_inbox: column.is_inbox, + is_done: column.is_done, + } + : null, + subtasks: subtasks.map((s) => ({ id: s.id, title: s.title, done: s.done })), + comments: comments.map((c) => ({ + id: c.id, + author: names[c.author] ?? "Someone", + body: c.body, + created: c.created, + })), + recentCommits: recentCommits.items.map((commit) => ({ + summary: commit.summary || commit.subject, + authored_at: commit.authored_at, + })), + }); +}