The server code
The handler that changes a count, from _worker.js.
// _worker.js
export default {
async fetch(request, env) {
const url = new URL(request.url);
const userId = request.headers.get('x-brod-user-id');
const email = request.headers.get('x-brod-user-email');
const role = request.headers.get('x-brod-role');
// Change a count: editors and the owner only
if (request.method === 'POST' && url.pathname === '/api/move') {
if (role !== 'editor' && role !== 'owner') return new Response('Read-only', { status: 403 });
const { sku, location, delta } = await request.json();
if (!Number.isInteger(delta) || delta === 0) return new Response('Bad amount', { status: 400 });
// The count and the history row change together, or not at all
await env.DB.batch([
{ sql: 'UPDATE items SET qty = qty + ? WHERE sku = ? AND location = ?', params: [delta, sku, location] },
{ sql: 'INSERT INTO moves (sku, location, delta, by, at) VALUES (?, ?, ?, ?, ?)',
params: [sku, location, delta, email, Date.now()] },
]);
await env.KV.put('last-location:' + userId, location);
return Response.json({ ok: true });
}
// Where this person was working last time
if (url.pathname === '/api/me') {
return Response.json({ role, lastLocation: await env.KV.get('last-location:' + userId) });
}
// History of one item
if (url.pathname === '/api/moves') {
const rows = await env.DB.query(
'SELECT * FROM moves WHERE sku = ? ORDER BY at DESC LIMIT 50', [url.searchParams.get('sku')]);
return Response.json({ moves: rows });
}
return new Response('Not found', { status: 404 });
},
};
- Role on every request. Brod tells the app the role on every request, and strips any
x-brod-* header a visitor tries to send.
- Checked on the server. Enforcing "viewers cannot change counts" is the app's job, which is why the check sits on the server rather than only hiding the buttons.