Files
GameTime/server
Blomios 917777e18b chore(wip): consolidation intermédiaire multi-tickets (sprints Statistiques, UI, Bug resolution, Serveur-client)
Regroupe l'état de travail en cours réalisé dans un même worktree sur
plusieurs tickets/sprints (#85, #136, #145, #155-160, #162-164),
mélangeant des tickets QA et inProgress. Ne constitue pas une feature
terminée : commit de sauvegarde avant triage/split par ticket en
branches feature/* dédiées. Exclut les dossiers d'environnement de
build locaux et le heap dump parasite (.gitignore mis à jour).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 16:48:54 +02:00
..

GameTime server

Headless Dart server for future GameTime account, sync and sharing features. This package is separate from the Flutter app at the repository root.

Local run

Install dependencies from this directory:

dart pub get

Run the server:

dart run bin/server.dart

The HTTP server binds to 0.0.0.0 and reads PORT from the environment. When PORT is not set, it listens on 8080.

PORT=9090 dart run bin/server.dart

PostgreSQL

Ticket #49 adds the initial sync-ready PostgreSQL schema and a minimal connection/migration utility.

Database environment variables:

  • DATABASE_HOST: PostgreSQL host.
  • DATABASE_PORT: PostgreSQL port, defaults to 5432 when omitted.
  • DATABASE_NAME: database name.
  • DATABASE_USER: database user.
  • DATABASE_PASSWORD: database password.

Apply migrations from server/:

DATABASE_HOST=localhost \
DATABASE_PORT=5432 \
DATABASE_NAME=gametime \
DATABASE_USER=gametime \
DATABASE_PASSWORD=gametime \
dart run bin/migrate.dart

Migrations are read from server/migrations/ in alphabetical order. The v1 runner is intentionally simple; SQL files use idempotent DDL where practical.

The PostgreSQL integration test is skipped unless TEST_DATABASE_URL is set:

TEST_DATABASE_URL=postgres://gametime:gametime@localhost:5432/gametime dart test

Healthcheck:

curl http://localhost:8080/health

Expected response:

{"status":"ok"}

Authentication

Ticket #48 adds account registration, login, logout and bearer-token request authentication.

Endpoints:

  • POST /auth/register with { "email": "...", "password": "...", "displayName": "..." }.
  • POST /auth/login with { "email": "...", "password": "...", "deviceLabel": "..." }.
  • POST /auth/logout with Authorization: Bearer <token>.

Passwords are stored with PBKDF2-HMAC-SHA256 via package:cryptography, using a per-password random salt. API tokens are opaque random values; only a SHA-256 hash of the token is stored in PostgreSQL.

Sync

Ticket #50 adds authenticated incremental sync endpoints using simple last-write-wins conflict resolution based on clientUpdatedAt.

Endpoints:

  • POST /sync/push with Authorization: Bearer <token>.
  • GET /sync/pull?since=<serverCursor> with Authorization: Bearer <token>.
  • POST /sync/exchange with Authorization: Bearer <token>.

serverCursor is an ISO8601 UTC timestamp. For push, it is the greatest server_updated_at currently known for the authenticated user after applying the batch. For pull, it is the greatest serverUpdatedAt returned, or the server clock if no resource is returned.

POST /sync/exchange applies push first, then returns the pull payload with pushResults included so per-item validation errors remain visible to the client.

Sharing

Ticket #51 adds authenticated targeted sharing for programs and workout templates.

Endpoints:

  • POST /shares with Authorization: Bearer <token>.
  • GET /shares/inbox with Authorization: Bearer <token>.
  • POST /shares/{id}/accept with Authorization: Bearer <token>.
  • POST /shares/{id}/decline with Authorization: Bearer <token>.
  • POST /shares/{id}/revoke with Authorization: Bearer <token>.

Creating a share stores a snapshot payload and creates pending recipient rows for known recipient emails. Unknown emails are reported as unresolvedEmails without failing the whole request. Accepting a share creates a new synced_resources copy owned by the recipient with a server-generated clientId; the sender's original resource is never modified. Accept returns 409 for revoked or already answered shares, while missing shares or recipients return 404.

API Contract

The current HTTP contract is documented in openapi.yaml. It covers health, authentication, sync and targeted sharing endpoints implemented by this server package.

To inspect it, paste the file into https://editor.swagger.io/ or open it with a local OpenAPI-compatible viewer.

Docker Deployment

Ticket #52 adds Docker packaging for a headless Linux deployment. TLS is not handled by the API container: terminate HTTPS in an external reverse proxy and forward traffic to the Docker host on API_PORT.

Create a local environment file:

cp .env.example .env

Edit .env before deployment:

  • API_BIND_ADDRESS: Docker host interface to publish. Use 0.0.0.0 for LAN access by the external reverse proxy, or a specific host IP to restrict the bind address.
  • API_PORT: host port exposed for the reverse proxy.
  • POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD: PostgreSQL bootstrap values.
  • DATABASE_HOST, DATABASE_PORT, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD: database connection used by the API. With the provided Compose file, DATABASE_HOST=postgres.
  • MIGRATE_ON_STARTUP: when true, the API container applies SQL migrations before starting the server.

Start the stack from server/:

docker compose up -d --build

The api service waits for the PostgreSQL healthcheck, runs /app/bin/migrate, then launches /app/bin/server. Migrations are copied into the image under /app/migrations.

To inspect the fully interpolated Compose configuration:

docker compose config

Gitea Registry

Build and push the server image to a Gitea Container Registry with no registry or credential value stored in the repository:

GITEA_REGISTRY=gitea.example.com \
GITEA_OWNER=my-org \
GITEA_IMAGE_NAME=gametime-server \
GITEA_IMAGE_TAG=latest \
GITEA_USERNAME=my-user \
GITEA_TOKEN='replace-with-token' \
./scripts/push-gitea-image.sh

GITEA_IMAGE_TAG defaults to latest when omitted. The script uses docker login --password-stdin so the token is not printed by the command line.

Scope

Ticket #47 only scaffolds the Dart server, the hexagonal directory layout and the /health endpoint. Ticket #49 adds the first PostgreSQL schema. Ticket #48 adds authentication. Ticket #50 adds sync. Ticket #51 adds targeted sharing. Ticket #52 adds Docker Compose packaging and Gitea registry publishing.

Upcoming tickets will fill the empty adapters and use cases:

  • #53: API, contract and integration tests.