From bec4a0080c1ff4d1755387521e326878c02721f1 Mon Sep 17 00:00:00 2001 From: iris Date: Thu, 30 Jul 2026 01:16:00 +0200 Subject: [PATCH] public.ts: convert error responses to RFC 7807, matching admin.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up from #25's review feedback — that PR converted admin.ts + the global error handler to application/problem+json (sendProblem/ProblemDetails), left public.ts (the tablet-facing /api/* routes) on the old ad hoc { error } shape to stay scoped to what #25 was actually fixing. Same conversion here, no functional changes — every reply.code(N).send({ error }) becomes sendProblem(req, reply, N, title, detail). Client already degrades gracefully either way (errText() in Admin.tsx checks detail/title first, falls back to .error), so this was purely consistency cleanup, not urgent. typecheck+build clean. --- server/src/routes/public.ts | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/server/src/routes/public.ts b/server/src/routes/public.ts index d8c039a..85e0b46 100644 --- a/server/src/routes/public.ts +++ b/server/src/routes/public.ts @@ -7,6 +7,7 @@ import type { CreateTransactionResponse, Drink, } from '@wutzcalc/shared'; +import { sendProblem } from '../problem-details.js'; export function registerPublicRoutes(app: FastifyInstance, db: DB) { app.get('/api/bars', async () => { @@ -16,12 +17,12 @@ export function registerPublicRoutes(app: FastifyInstance, db: DB) { app.get<{ Querystring: { bar: string } }>('/api/config', async (req, reply) => { const barId = Number(req.query.bar); - if (!Number.isInteger(barId)) return reply.code(400).send({ error: 'bar required' }); + if (!Number.isInteger(barId)) return sendProblem(req, reply, 400, 'Bad Request', 'bar required'); const bar = db .prepare('SELECT id, name, pfand_cents FROM bars WHERE id = ?') .get(barId) as Bar | undefined; - if (!bar) return reply.code(404).send({ error: 'bar not found' }); + if (!bar) return sendProblem(req, reply, 404, 'Not Found', 'bar not found'); const drinks = db .prepare( @@ -49,29 +50,29 @@ export function registerPublicRoutes(app: FastifyInstance, db: DB) { const { client_uuid, bar_id, crew, items } = body; if (!client_uuid || typeof client_uuid !== 'string') { - return reply.code(400).send({ error: 'client_uuid required' }); + return sendProblem(req, reply, 400, 'Bad Request', 'client_uuid required'); } if (!Number.isInteger(bar_id)) { - return reply.code(400).send({ error: 'bar_id required' }); + return sendProblem(req, reply, 400, 'Bad Request', 'bar_id required'); } if (!Array.isArray(items)) { - return reply.code(400).send({ error: 'items required' }); + return sendProblem(req, reply, 400, 'Bad Request', 'items required'); } if (items.length > MAX_ITEMS_PER_TRANSACTION) { - return reply.code(400).send({ error: 'too many items' }); + return sendProblem(req, reply, 400, 'Bad Request', 'too many items'); } const rawPfandReturns = body.pfand_returns ?? 0; if (!Number.isInteger(rawPfandReturns) || rawPfandReturns < 0) { - return reply.code(400).send({ error: 'pfand_returns must be a non-negative integer' }); + return sendProblem(req, reply, 400, 'Bad Request', 'pfand_returns must be a non-negative integer'); } if (rawPfandReturns > MAX_PFAND_RETURNS) { - return reply.code(400).send({ error: 'pfand_returns too large' }); + return sendProblem(req, reply, 400, 'Bad Request', 'pfand_returns too large'); } const pfand_returns = rawPfandReturns; if (items.length === 0 && pfand_returns === 0) { - return reply.code(400).send({ error: 'empty transaction' }); + return sendProblem(req, reply, 400, 'Bad Request', 'empty transaction'); } const findByUuid = () => @@ -88,7 +89,7 @@ export function registerPublicRoutes(app: FastifyInstance, db: DB) { const bar = db .prepare('SELECT id, pfand_cents FROM bars WHERE id = ?') .get(bar_id) as { id: number; pfand_cents: number } | undefined; - if (!bar) return reply.code(404).send({ error: 'bar not found' }); + if (!bar) return sendProblem(req, reply, 404, 'Not Found', 'bar not found'); const drinkStmt = db.prepare( `SELECT d.id, d.price_cents @@ -112,12 +113,14 @@ export function registerPublicRoutes(app: FastifyInstance, db: DB) { item.qty <= 0 || item.qty > MAX_QTY_PER_LINE ) { - return reply.code(400).send({ error: 'invalid item' }); + return sendProblem(req, reply, 400, 'Bad Request', 'invalid item'); } const drink = drinkStmt.get(bar_id, item.drink_id) as | { id: number; price_cents: number } | undefined; - if (!drink) return reply.code(400).send({ error: `drink ${item.drink_id} not at bar` }); + if (!drink) { + return sendProblem(req, reply, 400, 'Bad Request', `drink ${item.drink_id} not at bar`); + } const lineUnit = drink.price_cents + bar.pfand_cents; total += lineUnit * item.qty;