# 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 <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`](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:

```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.
