Adds the full Next.js app (app/, components/, lib/, scripts/, assets/, docs/, Dockerfile, compose, configs) to version control. Security: - CIPC BizPortal customer-code logins (Login 1 + Login 3) now read CIPC_LOGIN1_CODE / CIPC_LOGIN1_PASSWORD / CIPC_LOGIN3_CODE / CIPC_LOGIN3_PASSWORD from .env.local instead of source. - BizPortal personal login in apply-bizportal route reads BIZPORTAL_ID_NO / BIZPORTAL_PASSWORD. - Added .env.example as the committed template. .gitignore excludes .env*, node_modules, .next, logs, sqlite, Playwright storage_state.json (live CIPC cookies), scratch dirs, client PDFs and debug screenshots.
94 lines
3.1 KiB
TypeScript
94 lines
3.1 KiB
TypeScript
// app/api/tasks/[id]/route.ts
|
|
// GET /api/tasks/[id] — single task
|
|
// PATCH /api/tasks/[id] — partial update (used by UI for edits + column moves)
|
|
// DELETE /api/tasks/[id] — soft delete (archive)
|
|
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
import { fetchRecord, updateRecord, deleteRecord, PBRecord } from "@/lib/pb";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
const COLLECTION = "tasks";
|
|
|
|
const EDITABLE = new Set([
|
|
"title",
|
|
"description",
|
|
"priority",
|
|
"payload",
|
|
"assignee",
|
|
]);
|
|
|
|
const STATUS_TRANSITIONS: Record<string, string[]> = {
|
|
backlog: ["ready", "blocked", "in_progress"],
|
|
ready: ["backlog", "in_progress", "blocked"],
|
|
in_progress: ["blocked", "done", "failed"],
|
|
blocked: ["backlog", "ready", "in_progress"],
|
|
done: ["backlog"], // can re-queue if needed
|
|
failed: ["backlog", "ready"], // can re-queue
|
|
};
|
|
|
|
export async function GET(_req: NextRequest, { params }: { params: { id: string } }) {
|
|
try {
|
|
const rec = await fetchRecord<PBRecord>(COLLECTION, params.id);
|
|
return NextResponse.json(rec);
|
|
} catch (err: any) {
|
|
return NextResponse.json({ error: err.message }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
export async function PATCH(req: NextRequest, { params }: { params: { id: string } }) {
|
|
let body: any;
|
|
try {
|
|
body = await req.json();
|
|
} catch {
|
|
return NextResponse.json({ error: "invalid JSON body" }, { status: 400 });
|
|
}
|
|
|
|
// Whitelist fields the UI is allowed to set directly. The agent
|
|
// uses /claim, /complete, /fail endpoints for state transitions.
|
|
const fields: Record<string, unknown> = {};
|
|
for (const [k, v] of Object.entries(body ?? {})) {
|
|
if (EDITABLE.has(k)) fields[k] = v;
|
|
}
|
|
|
|
// Status changes from the UI are allowed but only along valid
|
|
// edges — drag-and-drop an "in_progress" card to "backlog" silently
|
|
// no-ops (the UI should use the explicit /cancel endpoint).
|
|
if (typeof body.status === "string") {
|
|
if (!Object.prototype.hasOwnProperty.call(STATUS_TRANSITIONS, body.status)) {
|
|
return NextResponse.json(
|
|
{ error: `invalid status: ${body.status}` },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
const current = await fetchRecord<PBRecord>(COLLECTION, params.id);
|
|
const allowed = STATUS_TRANSITIONS[current.status as string] || [];
|
|
if (!allowed.includes(body.status)) {
|
|
return NextResponse.json(
|
|
{ error: `cannot transition from '${current.status}' to '${body.status}' via PATCH` },
|
|
{ status: 409 },
|
|
);
|
|
}
|
|
fields.status = body.status;
|
|
}
|
|
|
|
if (Object.keys(fields).length === 0) {
|
|
return NextResponse.json({ error: "no editable fields supplied" }, { status: 400 });
|
|
}
|
|
|
|
try {
|
|
const updated = await updateRecord<PBRecord>(COLLECTION, params.id, fields);
|
|
return NextResponse.json(updated);
|
|
} catch (err: any) {
|
|
return NextResponse.json({ error: err.message }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
export async function DELETE(_req: NextRequest, { params }: { params: { id: string } }) {
|
|
try {
|
|
await deleteRecord(COLLECTION, params.id);
|
|
return NextResponse.json({ ok: true });
|
|
} catch (err: any) {
|
|
return NextResponse.json({ error: err.message }, { status: 500 });
|
|
}
|
|
}
|