# 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: ```bash dart pub get ``` Run the server: ```bash 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`. ```bash 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/`: ```bash 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: ```bash TEST_DATABASE_URL=postgres://gametime:gametime@localhost:5432/gametime dart test ``` Healthcheck: ```bash curl http://localhost:8080/health ``` Expected response: ```json {"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 `. 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 `. - `GET /sync/pull?since=` with `Authorization: Bearer `. - `POST /sync/exchange` with `Authorization: Bearer `. `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 `. - `GET /shares/inbox` with `Authorization: Bearer `. - `POST /shares/{id}/accept` with `Authorization: Bearer `. - `POST /shares/{id}/decline` with `Authorization: Bearer `. - `POST /shares/{id}/revoke` with `Authorization: Bearer `. 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`](openapi.yaml). It covers health, authentication, sync and targeted sharing endpoints implemented by this server package. To inspect it, paste the file into 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: ```bash 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/`: ```bash 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: ```bash 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: ```bash 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.