Skip to content

Backend

The backend is a Go module under backend/.

Package map

PathResponsibility
cmd/serverComposition root, environment configuration, HTTP server lifecycle
cmd/migrateForward migration, schema status, and version operations
internal/domainRoom aggregate, participant and round types, planning-poker rules
internal/portsRepository and room-event interfaces
internal/applicationUse-case orchestration, persistence, versioning, event publication
internal/adapters/httpapiHTTP/JSON, SSE, and WebSocket adapters
internal/adapters/webappStatic assets, cache policy, and SvelteKit dynamic-route fallback
internal/adapters/sqliteDefault SQLite repository
internal/adapters/postgresOptional PostgreSQL repository
internal/adapters/memoryProcess-local publish/subscribe event hub
internal/databaseConnection 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, while hasVote remains 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

MethodPathIdentityDescription
GET/api/healthNoneLiveness response
POST/api/roomsNoneCreate a room and facilitator
POST/api/rooms/{code}/joinNoneJoin with a display name
GET/api/rooms/{code}NoneGet the public room snapshot
POST/api/rooms/{code}/voteX-Participant-IDSubmit or replace a vote
POST/api/rooms/{code}/revealX-Participant-IDReveal the current round
POST/api/rooms/{code}/endX-Participant-IDEnd and summarize the session
POST/api/rooms/{code}/resetX-Participant-IDStart another round
POST/api/rooms/{code}/topicX-Participant-IDChange the topic and reset
POST/api/rooms/{code}/demo-teamX-Participant-IDAdd demonstration teammates
GET/events/rooms/{code}Room codeOpen the SSE stream
GET/ws/rooms/{code}Query parameterOpen a WebSocket session

Create and join return:

json
{
  "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:

json
{ "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

VariableDefaultDescription
ADDRESS:8080Backend listen address
DATABASE_DRIVERsqlitesqlite or postgres
DATABASE_PATHdata/data.dbSQLite file path; use :memory: for ephemeral mode
DATABASE_URLLocal PostgreSQL DSNPostgreSQL connection string
DATABASE_AUTO_MIGRATEtrueApply pending migrations before the server starts; set to false when migrations run as a deployment step
STATIC_DIRUnsetCompiled SvelteKit directory; the image uses /app/public

The server handles SIGINT and SIGTERM and closes the HTTP server and repository during shutdown.

Backlog Hold'em project documentation