Backend
The backend is a Go module under backend/.
Package map
| Path | Responsibility |
|---|---|
cmd/server | Composition root, environment configuration, HTTP server lifecycle |
cmd/migrate | Forward migration, schema status, and version operations |
internal/domain | Room aggregate, participant and round types, planning-poker rules |
internal/ports | Repository and room-event interfaces |
internal/application | Use-case orchestration, persistence, versioning, event publication |
internal/adapters/httpapi | HTTP/JSON, SSE, and WebSocket adapters |
internal/adapters/webapp | Static assets, cache policy, and SvelteKit dynamic-route fallback |
internal/adapters/sqlite | Default SQLite repository |
internal/adapters/postgres | Optional PostgreSQL repository |
internal/adapters/memory | Process-local publish/subscribe event hub |
internal/database | Connection configuration and embedded Goose migrations |
Domain aggregate
domain.Room is the consistency boundary. It contains:
- room metadata and invite code;
- current topic and card deck;
- participants and their votes;
- reveal state and monotonic version;
- every revealed result for the room lifetime;
- terminal session state and the final statistical summary.
Domain methods enforce these rules:
- selected cards must belong to the configured deck;
- votes cannot change after reveal;
- only the facilitator can reveal, reset, or change the topic;
- changing the topic resets the current round;
- unrevealed values are removed by
PublicCopy, whilehasVoteremains visible. - only the facilitator can end a session, and ending permanently locks room mutations.
The final summary excludes ? and missing votes from numeric calculations. It reports participant averages, median proximity, participation, uncertainty, and pre-reveal card revisions, plus room consensus. Awards include every tied participant.
Each demo-team command adds one named demonstration participant, up to the available pool of 16 names, and schedules that participant's vote. It is not a production presence simulation.
HTTP API
| Method | Path | Identity | Description |
|---|---|---|---|
GET | /api/health | None | Liveness response |
POST | /api/rooms | None | Create a room and facilitator |
POST | /api/rooms/{code}/join | None | Join with a display name |
GET | /api/rooms/{code} | None | Get the public room snapshot |
POST | /api/rooms/{code}/vote | X-Participant-ID | Submit or replace a vote |
POST | /api/rooms/{code}/reveal | X-Participant-ID | Reveal the current round |
POST | /api/rooms/{code}/end | X-Participant-ID | End and summarize the session |
POST | /api/rooms/{code}/reset | X-Participant-ID | Start another round |
POST | /api/rooms/{code}/topic | X-Participant-ID | Change the topic and reset |
POST | /api/rooms/{code}/demo-team | X-Participant-ID | Add demonstration teammates |
GET | /events/rooms/{code} | Room code | Open the SSE stream |
GET | /ws/rooms/{code} | Query parameter | Open a WebSocket session |
Create and join return:
{
"room": { "code": "A1B2C3", "version": 1 },
"participantId": "opaque-participant-token"
}State-changing HTTP requests send the participant token in X-Participant-ID.
Errors
Handlers return JSON in this shape:
{ "error": "only the facilitator can do that" }Missing rooms return 404, forbidden operations return 403, and invalid commands/state return 400. Ending requires a revealed round; ended rooms reject joins and all further mutations.
Runtime configuration
| Variable | Default | Description |
|---|---|---|
ADDRESS | :8080 | Backend listen address |
DATABASE_DRIVER | sqlite | sqlite or postgres |
DATABASE_PATH | data/data.db | SQLite file path; use :memory: for ephemeral mode |
DATABASE_URL | Local PostgreSQL DSN | PostgreSQL connection string |
DATABASE_AUTO_MIGRATE | true | Apply pending migrations before the server starts; set to false when migrations run as a deployment step |
STATIC_DIR | Unset | Compiled SvelteKit directory; the image uses /app/public |
The server handles SIGINT and SIGTERM and closes the HTTP server and repository during shutdown.