v0.2.0 Development - achedon12/guestroll GitHub Wiki
Note: Documentation of Guestroll v0.2.0. The latest release is v0.5.0: this page in v0.5.0 · what changed · other versions
Run locally
git clone https://github.com/achedon12/guestroll && cd guestroll
nvm use # Node 22, see .nvmrc
npm ci
echo "ADMIN_PASSWORD=dev-password-123" > .env.local
npm run dev # http://localhost:3000
Fill it with demo events and photos (through the public API, in another terminal):
BASE_URL=http://localhost:3000 ADMIN_PASSWORD=dev-password-123 npm run seed:demo
To try the camera on a real phone, the page must be HTTPS. The quickest way is
a tunnel (cloudflared tunnel --url http://localhost:3000) or
next dev --experimental-https on your LAN.
Checks
npm run lint
npm run typecheck # generates Next's route types first
npm test # unit + integration (Vitest)
npm run test:coverage # the same, with a coverage report in coverage/
npm run build
npm run test:e2e # functional tests on the production build (Playwright)
npm run test:e2e starts the production build on port 3210 with a temporary
data folder, so it never touches your data. Locally it uses your installed
Chrome; in CI, the Chromium installed by npx playwright install.
Three layers of tests
| Layer | Where | What it runs against |
|---|---|---|
| Unit | tests/unit/ |
Pure modules: access policy, validation, rate limiter, QR rendering… |
| Integration | tests/integration/ |
Real SQLite in a temp folder, real images through sharp, real ZIPs, QR codes decoded by ZXing |
| Functional | tests/e2e/api/ |
The HTTP API of a running production build: guest and admin scenarios, security, quotas |
| Browser | tests/e2e/browser/ |
Real journeys: a guest on a phone, an admin on a desktop |
Unit-level files:
| File | What it locks |
|---|---|
policy.test.ts |
Every access rule: who sees which photo, in which size |
routeGuards.test.ts |
Every admin page and route checks the session; every guest mutation checks the origin |
qrScan.test.ts |
Every QR preset and shape is rendered and decoded with ZXing |
qr.test.ts |
Style parsing, escaping, logo excavation |
request.test.ts |
Client IP and origin rules, the streaming body cap |
rateLimit.test.ts, validation.test.ts, ids.test.ts, negotiate.test.ts, translate.test.ts |
The rest of the pure modules |
Branches and CI
develop is the default branch, where pull requests land; main is
production. Every change on either branch runs:
| Workflow | What it does |
|---|---|
| Quality | Lint, types, production build |
| Tests | Vitest with coverage, then Playwright on a production build |
| Security | CodeQL (code and workflows) and npm audit on production dependencies, plus a weekly run |
| Docker image | Builds the image, runs it on a custom port and user, uploads a photo end to end |
| Workflows | actionlint and zizmor on .github/ |
Publish to production (manual) fast-forwards main to develop once
Quality and Tests passed there; Align develop carries anything that lands on
main back to develop. Tagging main with vX.Y.Z runs Release: the
multi-arch image on GHCR (and Docker Hub when its secrets are set) and a GitHub
release with generated notes. Website deploys site/ to GitHub Pages.
Housekeeping: Dependabot (grouped, one-week cooldown), stale issues, a welcome message on first pull requests, a "merge conflict" label, and pull requests sent back to draft when changes are requested.
Database changes
Edit src/lib/db/schema.ts, then:
npm run db:generate -- --name short_description
Commit the generated folder under drizzle/. Migrations run automatically on
startup. Never edit a migration that has been released.
Conventions
- TypeScript strict, ESM, the
@/alias forsrc/. - Access rules go in
lib/policy.tswith a test. Routes stay thin. - UI text goes in
lib/i18n/messages.ts, in all four languages. - Colors only through the tokens in
globals.css(bg-surface,text-ink-muted…), so both themes keep working. - Comments explain why, not what.
- Commits follow Conventional Commits.
The visual direction is described in design-prompt.md: read it before changing the interface.