diff --git a/src/app/api/agent/cards/[cardId]/move/route.ts b/src/app/api/agent/cards/[cardId]/move/route.ts index d315d43..b4c162d 100644 --- a/src/app/api/agent/cards/[cardId]/move/route.ts +++ b/src/app/api/agent/cards/[cardId]/move/route.ts @@ -37,67 +37,72 @@ export async function PATCH( return jsonError("A destination column name is required", 400); } - const pb = await getAdminPb(); - const card = await pb.collection("cards").getOne(cardId).catch(() => null); - if (!card || card.archived) return jsonError("Card not found", 404); + try { + 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 columns = await pb.collection("columns").getFullList({ - filter: `project = "${card.project}"`, - sort: "position", - }); + const columns = await pb.collection("columns").getFullList({ + filter: `project = "${card.project}"`, + sort: "position", + }); - const destination = columns.find( - (c) => c.name.toLowerCase() === columnName.trim().toLowerCase() - ); - if (!destination) { - return jsonError( - `Unknown column "${columnName}". This project's columns are: ${columns - .map((c) => c.name) - .join(", ")}`, - 400 + const destination = columns.find( + (c) => c.name.toLowerCase() === columnName.trim().toLowerCase() ); - } + if (!destination) { + return jsonError( + `Unknown column "${columnName}". This project's columns are: ${columns + .map((c) => c.name) + .join(", ")}`, + 400 + ); + } - const lastInDestination = await pb - .collection("cards") - .getFirstListItem(`column = "${destination.id}" && archived = false`, { - sort: "-position", - }) - .catch(() => null); - const position = positionBetween(lastInDestination ? lastInDestination.position : null, null); + const lastInDestination = await pb + .collection("cards") + .getFirstListItem(`column = "${destination.id}" && archived = false`, { + sort: "-position", + }) + .catch(() => null); + const position = positionBetween(lastInDestination ? lastInDestination.position : null, null); - // Same accept-transition rule as the owner's own drag-and-drop move: a - // card leaving the Inbox for a real working column, for the first time, - // is being accepted — not just relocated. - const accepting = !card.accepted && card.column !== destination.id && !destination.is_inbox; + // Same accept-transition rule as the owner's own drag-and-drop move: a + // card leaving the Inbox for a real working column, for the first time, + // is being accepted — not just relocated. + const accepting = !card.accepted && card.column !== destination.id && !destination.is_inbox; - const updated = await pb.collection("cards").update(cardId, { - column: destination.id, - position, - ...(accepting - ? { accepted: true, start_date: card.start_date || new Date().toISOString().slice(0, 10) } - : {}), - }); - - if (accepting) { - const owner = await getOwnerUser(); - await logActivity({ - project: card.project, - card: card.id, - actor: owner.id, - action: "card_accepted", - newValue: destination.name, - }).catch((error) => { - console.error("[agent-api] accepted card but activity failed", error); + const updated = await pb.collection("cards").update(cardId, { + column: destination.id, + position, + ...(accepting + ? { accepted: true, start_date: card.start_date || new Date().toISOString().slice(0, 10) } + : {}), }); - await relinkProject(card.project).catch((error) => { - console.error("[agent-api] accepted card but relinking failed", error); - }); - } - return jsonOk({ - id: updated.id, - column: destination.name, - accepted: updated.accepted, - }); + if (accepting) { + const owner = await getOwnerUser(); + await logActivity({ + project: card.project, + card: card.id, + actor: owner.id, + action: "card_accepted", + newValue: destination.name, + }).catch((error) => { + console.error("[agent-api] accepted card but activity failed", error); + }); + await relinkProject(card.project).catch((error) => { + console.error("[agent-api] accepted card but relinking failed", error); + }); + } + + return jsonOk({ + id: updated.id, + column: destination.name, + accepted: updated.accepted, + }); + } catch (error) { + console.error("[agent-api] move card failed", error); + return jsonError("Something went wrong", 500); + } } diff --git a/src/app/api/agent/cards/[cardId]/route.ts b/src/app/api/agent/cards/[cardId]/route.ts index ee6c7b1..851958c 100644 --- a/src/app/api/agent/cards/[cardId]/route.ts +++ b/src/app/api/agent/cards/[cardId]/route.ts @@ -25,64 +25,70 @@ export async function GET( 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); + try { + const pb = await getAdminPb(); - const column = await pb - .collection("columns") - .getOne(card.column) - .catch(() => null); + const card = await pb.collection("cards").getOne(cardId).catch(() => null); + if (!card || card.archived) return jsonError("Card not found", 404); - const subtasks = await pb.collection("subtasks").getFullList({ - filter: `card = "${cardId}"`, - sort: "position", - }); + const column = await pb + .collection("columns") + .getOne(card.column) + .catch(() => null); - const comments = await pb.collection("comments").getFullList({ - filter: `card = "${cardId}"`, - sort: "created", - }); + const subtasks = await pb.collection("subtasks").getFullList({ + filter: `card = "${cardId}"`, + sort: "position", + }); - 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 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, + })), + }); + } catch (error) { + console.error("[agent-api] get card failed", error); + return jsonError("Something went wrong", 500); } - - 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, - })), - }); } diff --git a/src/app/api/agent/cards/[cardId]/subtasks/route.ts b/src/app/api/agent/cards/[cardId]/subtasks/route.ts index a9d9ae2..9c70e6f 100644 --- a/src/app/api/agent/cards/[cardId]/subtasks/route.ts +++ b/src/app/api/agent/cards/[cardId]/subtasks/route.ts @@ -41,28 +41,33 @@ export async function POST( return jsonError("At least one non-empty title is required", 400); } - const pb = await getAdminPb(); - const card = await pb.collection("cards").getOne(cardId).catch(() => null); - if (!card || card.archived) return jsonError("Card not found", 404); + try { + 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 last = await pb - .collection("subtasks") - .getFirstListItem(`card = "${cardId}"`, { sort: "-position" }) - .catch(() => null); + const last = await pb + .collection("subtasks") + .getFirstListItem(`card = "${cardId}"`, { sort: "-position" }) + .catch(() => null); - let position: number | null = last ? last.position : null; - const created: string[] = []; + 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); + 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); + } catch (error) { + console.error("[agent-api] create subtask failed", error); + return jsonError("Something went wrong", 500); } - - return jsonOk({ created }, 201); } diff --git a/src/app/api/agent/projects/[projectId]/cards/route.ts b/src/app/api/agent/projects/[projectId]/cards/route.ts index 0753094..c2fd80a 100644 --- a/src/app/api/agent/projects/[projectId]/cards/route.ts +++ b/src/app/api/agent/projects/[projectId]/cards/route.ts @@ -18,55 +18,61 @@ export async function GET( 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); + try { + const pb = await getAdminPb(); - const columns = await pb.collection("columns").getFullList({ - filter: `project = "${projectId}"`, - sort: "position", - }); - const columnsById = new Map(columns.map((c) => [c.id, c])); + const project = await pb.collection("projects").getOne(projectId).catch(() => null); + if (!project) return jsonError("Project not found", 404); - const url = new URL(request.url); - const onlyInbox = url.searchParams.get("column") === "inbox"; + const columns = await pb.collection("columns").getFullList({ + filter: `project = "${projectId}"`, + sort: "position", + }); + const columnsById = new Map(columns.map((c) => [c.id, c])); - let cards = await pb.collection("cards").getFullList({ - filter: `project = "${projectId}" && archived = false`, - }); + const url = new URL(request.url); + const onlyInbox = url.searchParams.get("column") === "inbox"; - if (onlyInbox) { - cards = cards.filter((card) => columnsById.get(card.column)?.is_inbox === true); + 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); + } catch (error) { + console.error("[agent-api] list project cards failed", error); + return jsonError("Something went wrong", 500); } - - 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); } diff --git a/src/app/api/agent/projects/route.ts b/src/app/api/agent/projects/route.ts index 9046920..9ee2a27 100644 --- a/src/app/api/agent/projects/route.ts +++ b/src/app/api/agent/projects/route.ts @@ -1,6 +1,6 @@ import { getAdminPb } from "@/lib/pb/admin"; import { requireAgentToken } from "@/lib/agent/auth"; -import { jsonOk } from "@/lib/http/json"; +import { jsonError, jsonOk } from "@/lib/http/json"; import type { ClientRecord, ProjectRecord } from "@/lib/pb/types"; /** @@ -14,29 +14,34 @@ export async function GET(request: Request): Promise { const denied = requireAgentToken(request); if (denied) return denied; - const pb = await getAdminPb(); + try { + const pb = await getAdminPb(); - const [projects, clients] = await Promise.all([ - pb.collection("projects").getFullList({ sort: "name" }), - pb.collection("clients").getFullList(), - ]); + const [projects, clients] = await Promise.all([ + pb.collection("projects").getFullList({ sort: "name" }), + pb.collection("clients").getFullList(), + ]); - const clientsById = new Map(clients.map((c) => [c.id, c])); + const clientsById = new Map(clients.map((c) => [c.id, c])); - const out = projects - .map((project) => { - const client = clientsById.get(project.client); - if (!client) return null; - return { - id: project.id, - name: project.name, - slug: project.slug, - client: { id: client.id, name: client.name, slug: client.slug }, - kind: project.kind, - status: project.status, - }; - }) - .filter((p): p is NonNullable => p !== null); + const out = projects + .map((project) => { + const client = clientsById.get(project.client); + if (!client) return null; + return { + id: project.id, + name: project.name, + slug: project.slug, + client: { id: client.id, name: client.name, slug: client.slug }, + kind: project.kind, + status: project.status, + }; + }) + .filter((p): p is NonNullable => p !== null); - return jsonOk(out); + return jsonOk(out); + } catch (error) { + console.error("[agent-api] list projects failed", error); + return jsonError("Something went wrong", 500); + } }