The /api/pb/records/[id] proxy had an allowlist of editable fields that was written before the 2026-08-18 onboarding field additions. The new fields were silently dropped before reaching PocketBase, so the modal appeared to save successfully but the data never landed in PB. Added to COMPANY_EDIT_FIELDS: physical_address_line_1/2, physical_town_city, physical_province, physical_postal_code, postal_address_line_1/2, postal_town_city, postal_province, postal_postal_code, company_email, company_cell_phone, public_officer_name, company_secretary_name, financial_year_end_month, authorised_shares Added to DIRECTOR_EDIT_FIELDS: residential_address_line_1/2, residential_town_city, residential_province, residential_postal_code, postal_address_line_1/2, postal_town_city, postal_province, postal_postal_code, date_of_birth End-to-end tested via proxy PATCH — fields now land in PB.
156 lines
5.2 KiB
TypeScript
156 lines
5.2 KiB
TypeScript
// app/api/pb/records/[id]/route.ts
|
|
// GET /api/pb/records/:id?collection=... — fetch a single record
|
|
// PATCH /api/pb/records/:id?collection=... — update a record (edit)
|
|
// DELETE /api/pb/records/:id?collection=... — delete a record
|
|
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
import { fetchRecord, updateRecord, deleteRecord } from "@/lib/pb";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
const ALLOWED_COLLECTIONS = new Set([
|
|
"ysterbaard_directors",
|
|
"ysterbaard_companies",
|
|
"groups",
|
|
]);
|
|
|
|
// Fields editable for directors via the in-app edit form.
|
|
// (Address fields added 2026-08-18 alongside the onboarding form fields.)
|
|
const DIRECTOR_EDIT_FIELDS = new Set([
|
|
"surname", "first_names", "id_no", "id_no_masked",
|
|
"telephone", "email", "notes", "source",
|
|
"residential_address",
|
|
"residential_address_line_1", "residential_address_line_2",
|
|
"residential_town_city", "residential_province", "residential_postal_code",
|
|
"postal_address",
|
|
"postal_address_line_1", "postal_address_line_2",
|
|
"postal_town_city", "postal_province", "postal_postal_code",
|
|
"director_type",
|
|
"date_of_birth",
|
|
]);
|
|
|
|
// Fields editable for companies via the in-app edit form.
|
|
// (Structured address fields + company-level fields added 2026-08-18.)
|
|
const COMPANY_EDIT_FIELDS = new Set([
|
|
"enterprise_no", "enterprise_name", "enterprise_type",
|
|
"registration_date",
|
|
"physical_address",
|
|
"physical_address_line_1", "physical_address_line_2",
|
|
"physical_town_city", "physical_province", "physical_postal_code",
|
|
"postal_address",
|
|
"postal_address_line_1", "postal_address_line_2",
|
|
"postal_town_city", "postal_province", "postal_postal_code",
|
|
"company_email", "company_cell_phone",
|
|
"public_officer_name", "company_secretary_name",
|
|
"financial_year_end_month", "authorised_shares",
|
|
"director", "notes", "source",
|
|
"sars_login_username", "sars_login_password",
|
|
"sars_paye_no", "sars_vat_no", "sars_tax_number",
|
|
"sars_vat_returns_period", "sars_paye_return_period",
|
|
"groups",
|
|
]);
|
|
|
|
function getIdParam(params: { id: string }) {
|
|
return params.id;
|
|
}
|
|
|
|
export async function GET(
|
|
req: NextRequest,
|
|
{ params }: { params: { id: string } }
|
|
) {
|
|
const url = new URL(req.url);
|
|
const collection = url.searchParams.get("collection");
|
|
const expand = url.searchParams.get("expand") ?? undefined;
|
|
|
|
if (!collection || !ALLOWED_COLLECTIONS.has(collection)) {
|
|
return NextResponse.json(
|
|
{ error: `collection '${collection}' is not allowed` },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
try {
|
|
const result = await fetchRecord(collection, getIdParam(params), expand);
|
|
return NextResponse.json(result);
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : "Unknown PB error";
|
|
console.error("[pb get]", collection, getIdParam(params), message);
|
|
return NextResponse.json({ error: message }, { status: 502 });
|
|
}
|
|
}
|
|
|
|
export async function PATCH(
|
|
req: NextRequest,
|
|
{ params }: { params: { id: string } }
|
|
) {
|
|
const url = new URL(req.url);
|
|
const collection = url.searchParams.get("collection");
|
|
|
|
if (!collection || !ALLOWED_COLLECTIONS.has(collection)) {
|
|
return NextResponse.json(
|
|
{ error: `collection '${collection}' is not allowed` },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
let body: Record<string, unknown>;
|
|
try {
|
|
body = await req.json();
|
|
} catch {
|
|
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
|
}
|
|
|
|
// Sanitise: only pass through allowlisted fields.
|
|
const allowlist = collection === "ysterbaard_directors"
|
|
? DIRECTOR_EDIT_FIELDS
|
|
: COMPANY_EDIT_FIELDS;
|
|
|
|
const sanitised: Record<string, unknown> = {};
|
|
for (const key of allowlist) {
|
|
if (key in body) {
|
|
sanitised[key] = body[key];
|
|
}
|
|
}
|
|
|
|
// Recompute id_no_masked if id_no is being changed.
|
|
if ("id_no" in sanitised && typeof sanitised.id_no === "string" && sanitised.id_no.length >= 12) {
|
|
const id = sanitised.id_no as string;
|
|
sanitised.id_no_masked = id.slice(0, 6) + "XXXX" + id.slice(10, 12) + "X";
|
|
}
|
|
|
|
try {
|
|
const record = await updateRecord(collection, getIdParam(params), sanitised);
|
|
console.log(`[pb patch] ${new Date().toISOString()} ${collection}/${record.id}`);
|
|
return NextResponse.json(record);
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : "Unknown PB error";
|
|
console.error("[pb patch]", collection, getIdParam(params), message);
|
|
return NextResponse.json({ error: message }, { status: 502 });
|
|
}
|
|
}
|
|
|
|
export async function DELETE(
|
|
req: NextRequest,
|
|
{ params }: { params: { id: string } }
|
|
) {
|
|
const url = new URL(req.url);
|
|
const collection = url.searchParams.get("collection");
|
|
|
|
if (!collection || !ALLOWED_COLLECTIONS.has(collection)) {
|
|
return NextResponse.json(
|
|
{ error: `collection '${collection}' is not allowed` },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const id = getIdParam(params);
|
|
try {
|
|
await deleteRecord(collection, id);
|
|
console.log(`[pb delete] ${new Date().toISOString()} ${collection}/${id}`);
|
|
return new NextResponse(null, { status: 204 });
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : "Unknown PB error";
|
|
console.error("[pb delete]", collection, id, message);
|
|
return NextResponse.json({ error: message }, { status: 502 });
|
|
}
|
|
}
|