Event log: resolve input_received lane from the firing device
Replace the hardcoded lane: 0 on input_received events with a real device->lane lookup. A new LaneMap caches lane_devices.id -> lane, built at startup and refreshed by the setup routes on assign/unassign. An unmapped device logs lane: -1 + a warning (0 is a real lane) and is still recorded faithfully (append-only chain). source stays null for raw inputs by design: it's an IdentitySource (how a vehicle was identified), not a device field; device provenance remains in identity. Documented both in the wiki.
This commit is contained in:
@@ -5,6 +5,7 @@ import { createDb, type Db } from "@parking/db";
|
||||
import { TOKEN_COOKIE, requireJwtSecret } from "./auth.js";
|
||||
import { deviceEvents } from "./device-events.js";
|
||||
import { EventLog } from "./event-log.js";
|
||||
import { LaneMap } from "./lane-map.js";
|
||||
import { PrinterMonitor } from "./printer-monitor.js";
|
||||
import { buildSigner } from "./signer.js";
|
||||
import { authRoutes } from "./routes/auth.js";
|
||||
@@ -46,9 +47,15 @@ export async function buildServer(opts: BuildOptions = {}): Promise<FastifyInsta
|
||||
// Local username/password login → JWT in an HttpOnly cookie + CSRF cookie.
|
||||
await authRoutes(app, db);
|
||||
|
||||
// device id -> lane resolver. Built from lane_devices at startup and refreshed
|
||||
// by setupRoutes on assign/unassign, so device events can be stamped with the
|
||||
// lane the device belongs to (events carry the device id, not a lane).
|
||||
const laneMap = new LaneMap(db);
|
||||
laneMap.refresh();
|
||||
|
||||
// Device-agnostic setup: the admin selects devices per lane from the driver
|
||||
// catalog at first-run. See wiki/concepts/first-run-setup.md.
|
||||
await setupRoutes(app, db);
|
||||
await setupRoutes(app, db, () => laneMap.refresh());
|
||||
|
||||
// Inbound device pushes (e.g. Dingtian Input Link URL → button events),
|
||||
// guarded by source-IP allowlist + a shared-secret path token, both read from
|
||||
@@ -72,10 +79,22 @@ export async function buildServer(opts: BuildOptions = {}): Promise<FastifyInsta
|
||||
const eventLog = new EventLog(db, buildSigner(app.log));
|
||||
await eventRoutes(app, db, eventLog);
|
||||
const unsubscribeInput = deviceEvents.onInput((e) => {
|
||||
// Resolve which lane the device belongs to. -1 marks "device fired but isn't
|
||||
// mapped to a lane" (assigned without a lane, or a stale id) — still recorded
|
||||
// faithfully (the chain is append-only) rather than silently dropped or
|
||||
// mis-stamped as lane 0, which is a real lane.
|
||||
const lane = laneMap.laneFor(e.deviceId) ?? -1;
|
||||
if (lane === -1) {
|
||||
app.log.warn(`input from unmapped device ${e.driverId}:${e.deviceId} — logged as lane -1`);
|
||||
}
|
||||
eventLog
|
||||
.append({
|
||||
type: "input_received",
|
||||
lane: 0, // lane mapping is a TODO — device->lane lookup arrives with setup/lane wiring
|
||||
lane,
|
||||
// `source` is an IdentitySource (wiegand/lpr/qr/ticket/manual) — how a
|
||||
// VEHICLE was identified. A raw input has none, so it stays null. The
|
||||
// device provenance lives in `identity` instead.
|
||||
source: null,
|
||||
identity: `${e.driverId}:${e.deviceId} input:${e.input}/${e.edge}`,
|
||||
occurredAt: e.at,
|
||||
})
|
||||
@@ -83,7 +102,7 @@ export async function buildServer(opts: BuildOptions = {}): Promise<FastifyInsta
|
||||
});
|
||||
app.addHook("onClose", async () => unsubscribeInput());
|
||||
|
||||
// TODO: entry flow (input event → signed event → print → relay); map device→lane.
|
||||
// TODO: entry flow (input event → signed event → print → relay).
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user