From 9b6c031e600b5e592dd632f247282ffc09fbe6c8 Mon Sep 17 00:00:00 2001 From: Dave Date: Fri, 25 Sep 2026 22:35:22 +0200 Subject: [PATCH] feat(agent-api): GET /api/agent/projects/:projectId/cards Co-Authored-By: Claude Haiku 4.5 --- .../agent/projects/[projectId]/cards/route.ts | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/app/api/agent/projects/[projectId]/cards/route.ts diff --git a/src/app/api/agent/projects/[projectId]/cards/route.ts b/src/app/api/agent/projects/[projectId]/cards/route.ts new file mode 100644 index 0000000..0753094 --- /dev/null +++ b/src/app/api/agent/projects/[projectId]/cards/route.ts @@ -0,0 +1,72 @@ +import { getAdminPb } from "@/lib/pb/admin"; +import { requireAgentToken } from "@/lib/agent/auth"; +import { jsonError, jsonOk } from "@/lib/http/json"; +import type { CardRecord, ColumnRecord } from "@/lib/pb/types"; + +/** + * GET /api/agent/projects/:projectId/cards — every non-archived card in the + * project, any column, in the same order the board itself renders in. + * + * `?column=inbox` filters to the Inbox column only, matching `is_inbox` — + * never a literal column name, since column names are editable per project. + */ +export async function GET( + request: Request, + context: { params: Promise<{ projectId: string }> } +): Promise { + const denied = requireAgentToken(request); + if (denied) return denied; + + const { projectId } = await context.params; + const pb = await getAdminPb(); + + const project = await pb.collection("projects").getOne(projectId).catch(() => null); + if (!project) return jsonError("Project not found", 404); + + const columns = await pb.collection("columns").getFullList({ + filter: `project = "${projectId}"`, + sort: "position", + }); + const columnsById = new Map(columns.map((c) => [c.id, c])); + + const url = new URL(request.url); + const onlyInbox = url.searchParams.get("column") === "inbox"; + + let cards = await pb.collection("cards").getFullList({ + filter: `project = "${projectId}" && archived = false`, + }); + + if (onlyInbox) { + cards = cards.filter((card) => columnsById.get(card.column)?.is_inbox === true); + } + + cards.sort((a, b) => { + const colA = columnsById.get(a.column)?.position ?? 0; + const colB = columnsById.get(b.column)?.position ?? 0; + return colA - colB || a.position - b.position; + }); + + const out = cards + .map((card) => { + const column = columnsById.get(card.column); + if (!column) return null; + return { + 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: { + id: column.id, + name: column.name, + is_inbox: column.is_inbox, + is_done: column.is_done, + }, + }; + }) + .filter((c): c is NonNullable => c !== null); + + return jsonOk(out); +}