Scaffold Fastify + Drizzle API with pick-by-light
Layered: routes -> services -> repositories -> db, over PostgreSQL 18 with split roles (findr_migrator for DDL, findr_app for DML). Keycloak JWT auth with per-endpoint scope guards. Drizzle migrations run with the DDL role at container start. Pick-by-light: one ESP32 controller drives one WS2812 chain through several boxes; each box owns a contiguous LED slice. MQTT contract in src/lib/mqtt-topics.ts / docs/mqtt.md. /v1/pick controls light/idle/off and /v1/boxes + /v1/controllers manage the strip mapping. Reference resource /v1/parts wired end to end. Vitest, Biome, multi-stage Dockerfile, local compose.yml with Postgres. Signed-off-by: Peter Siegmund <mars3142@noreply.mars3142.dev>
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
boxNumberFromTopic,
|
||||
idleCommand,
|
||||
offCommand,
|
||||
pickCommand,
|
||||
topics,
|
||||
} from "../src/lib/mqtt-topics.js";
|
||||
|
||||
describe("mqtt topic builders", () => {
|
||||
const t = topics("findr");
|
||||
|
||||
it("builds the command and state topics for a box", () => {
|
||||
expect(t.boxConfig(2)).toBe("findr/box/2/config");
|
||||
expect(t.cmdLight(2)).toBe("findr/box/2/cmd/light");
|
||||
expect(t.stateLight(2)).toBe("findr/box/2/state/light");
|
||||
expect(t.ctrlOnline("abc")).toBe("findr/controller/abc/state/online");
|
||||
expect(t.allBoxState()).toBe("findr/box/+/state/+");
|
||||
expect(t.allControllerState()).toBe("findr/controller/+/state/+");
|
||||
});
|
||||
|
||||
it("extracts the box number from a topic", () => {
|
||||
expect(boxNumberFromTopic("findr/box/7/state/light")).toBe(7);
|
||||
expect(boxNumberFromTopic("findr/box/x/state/light")).toBeNull();
|
||||
expect(boxNumberFromTopic("other/box/7/state/light")).toBeNull();
|
||||
});
|
||||
|
||||
it("respects a custom prefix", () => {
|
||||
const p = topics("lab");
|
||||
expect(p.cmdLight(3)).toBe("lab/box/3/cmd/light");
|
||||
expect(boxNumberFromTopic("lab/box/3/evt/button", "lab")).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("light command builders", () => {
|
||||
it("pick lights exactly the given bins", () => {
|
||||
const cmd = pickCommand([{ cell: "D4", ledIndex: 27, mode: "take" }], {
|
||||
seconds: 30,
|
||||
requestId: "r1",
|
||||
});
|
||||
expect(cmd).toEqual({
|
||||
effect: "pick",
|
||||
bins: [{ cell: "D4", ledIndex: 27, mode: "take" }],
|
||||
seconds: 30,
|
||||
requestId: "r1",
|
||||
});
|
||||
});
|
||||
|
||||
it("idle and off carry just the effect", () => {
|
||||
expect(idleCommand().effect).toBe("idle");
|
||||
expect(offCommand().effect).toBe("off");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user