API: don't null-out empty notes/title (NOT NULL columns); constraint violations return 400 so offline clients drop bad ops
All checks were successful
Build & Release APK / build (push) Successful in 2m1s

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 23:40:31 -07:00
parent 8091c7d94d
commit 64967c19a0

View File

@@ -31,9 +31,12 @@ function err(message: string, status: number) {
const TASK_FIELDS = ["title", "notes", "status", "project_id", "context", "waiting_for", "due_date", "defer_date", "priority", "estimate_min", "sort_order"]; const TASK_FIELDS = ["title", "notes", "status", "project_id", "context", "waiting_for", "due_date", "defer_date", "priority", "estimate_min", "sort_order"];
const PROJECT_FIELDS = ["name", "status", "notes", "sort_order"]; const PROJECT_FIELDS = ["name", "status", "notes", "sort_order"];
// Only these may be cleared to NULL; notes/title are NOT NULL and keep "".
const NULLABLE = new Set(["project_id", "context", "waiting_for", "due_date", "defer_date", "estimate_min"]);
function pick(body: Record<string, unknown>, fields: string[]) { function pick(body: Record<string, unknown>, fields: string[]) {
const out: Record<string, unknown> = {}; const out: Record<string, unknown> = {};
for (const f of fields) if (f in body) out[f] = body[f] === "" ? null : body[f]; for (const f of fields) if (f in body) out[f] = body[f] === "" && NULLABLE.has(f) ? null : body[f];
return out; return out;
} }
@@ -166,6 +169,10 @@ Bun.serve({
fetch: (req) => fetch: (req) =>
handle(req).catch((e) => { handle(req).catch((e) => {
console.error(e); console.error(e);
// Constraint violations are bad input, not server faults — 400 so
// offline clients drop the op instead of retrying it forever.
const errno = (e as { errno?: string }).errno ?? "";
if (errno.startsWith("23")) return err(`constraint violation: ${(e as Error).message}`, 400);
return err("internal error", 500); return err("internal error", 500);
}), }),
}); });