Commit Graph
2 Commits
Author SHA1 Message Date
vliaudatandClaude Opus 5 f89e4690ba feat: package the application for Docker, with deployment docs
Multi-stage build on node:22-alpine, standalone output, non-root user,
healthcheck on /api/health, and migrations applied by the entrypoint
before the first request. A failed migration stops the container rather
than serving an inconsistent database.

Getting the Prisma CLI into the runtime image took three attempts and
the reasoning is recorded in the Dockerfile. Copying it out of the build
stage leaves its transitive dependencies behind; patching them in one at
a time is a losing game. It now gets its own stage and its own tree,
with the schema and prisma.config.ts beside it, and the entrypoint runs
from there so every import resolves locally. The version is read from
our own package.json so it cannot drift from the generated client.

Two things had to change to build without a database, which a build
container rightly does not have. prisma.config.ts no longer reads the
URL through prisma's env() helper, which throws on a missing variable
even for `generate`. And lib/db.ts creates the client on first use
rather than on import: Next imports every route module while collecting
page data, so a module that threw on import failed the build with an
error naming whichever route was analysed first, which says nothing
useful. The failure now lands on the first query, where it belongs.

Verified by running the image against a real database: migrations
applied, cron scheduled in Europe/Zurich, a device paired, and the panel
image served as a genuine 1-bit 800x480 BMP — so satori, resvg and the
vendored fonts all work on musl. The image hash came out identical to
the one produced on the glibc host, which is the reproducibility the
vendored fonts were for.

The production overlay publishes through an existing Traefik, drops the
host port, mounts the filesystem read-only, and adds a nightly dump kept
for a fortnight.

README and DEPLOY are in French and cover what actually bites: the panel
receives nothing and only updates when it wakes; the issuer must match
to the character; the captive portal URL takes no trailing slash; a
rollback across a migration needs the dump, because Prisma does not
undo one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012cSY9pVhZmJUKNN7wf1Myd
2026-09-20 22:48:36 +02:00
vliaudatandClaude Opus 5 42ce09100e feat: add Prisma schema, initial migration and idempotent seed
Model the whole domain: settings, the reference week, dated exceptions,
vacation periods, the public-holiday cache, display messages, the
translation cache, the audit log, and the device tables the BYOS server
needs (Device, DeviceLog, SyncState).

Two modelling decisions are load-bearing and documented in the schema:

- Opening times are wall-clock "HH:mm" strings in the shop timezone,
  never instants. Nothing is stored in UTC, which turns the March and
  October daylight-saving switches into non-events instead of edge cases.
- VacationPeriod is a source of truth, never expanded into
  ScheduleException rows. The resolver reads it directly at priority
  rank 2, so a holiday sync can never overwrite a manual exception and
  editing a period leaves no orphans behind.

Device access tokens are stored only as SHA-256 digests, and the image
filename column holds a content hash: the firmware skips the redraw when
the filename is unchanged, which is where the battery life comes from.

Prisma 7 no longer accepts the connection URL in the schema file, so it
moves to prisma.config.ts with the pg driver adapter.

The dev Postgres service lands here rather than with the rest of the
Docker work, because the migration needs a database to run against.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012cSY9pVhZmJUKNN7wf1Myd
2026-09-20 17:33:13 +02:00