# @circuy/api

Backend Hono. Sessions opaques en base, lecture publique cachable, administration authentifiée.

## Surface

**Public** (clé dans le chemin, CORS `*`) :

- `GET /v1/p/{publicKey}/manifest.json` — cache 60 s, `stale-while-revalidate` 600 s, ETag
- `GET /v1/p/{publicKey}/tours/{tourId}/{version}.json` — cache un an, immutable
- `POST /v1/events` — ingestion, origine déclarée, 204

**Administration** (cookie de session ou `Authorization: Bearer` pour l'extension, `Cache-Control: private, no-store`) :

- `GET /v1/auth/me`, `POST /v1/auth/logout`, `DELETE /v1/auth/me`
- `GET /v1/auth/{google|microsoft|github}` — OAuth (arctic)
- `GET /v1/auth/extension` — flux web de l'extension (`redirect_uri` `chromiumapp.org` ou `chrome-extension:`)
- `GET /v1/extensions`, `DELETE /v1/extensions/{id}` — liste et révocation des jetons d'extension
- `GET /v1/projects`, `GET /v1/projects/{id}`, `POST /v1/projects`, `PATCH /v1/projects/{id}`
- `GET /v1/projects/{id}/tours`, `POST /v1/tours`, `GET|PATCH /v1/tours/{id}`
- `POST /v1/tours/{id}/publish`, `POST /v1/tours/{id}/status`
- `GET /v1/tours/{id}/analytics`

Le HTML du corps des étapes est assaini à l'écriture (`sanitize-html`, liste blanche de la section 8.5). Seul le résultat assaini est stocké.

## Auth

Cookie `circuy_session` : `HttpOnly`, `SameSite=Lax`, 30 jours glissants, `Secure` en production. L'extension utilise un jeton opaque distinct, vérifié en base, envoyé en `Authorization: Bearer`. En local, `ALLOW_DEV_AUTH=1` autorise `POST /v1/auth/dev` avec `{ "email": "…" }` ; `{ "extension": true }` émet aussi un jeton d'extension. Ce raccourci est refusé si `NODE_ENV=production`.

## Base de données

Tables : `organizations`, `users`, `sessions`, `extension_tokens`, `projects`, `tours`, `tour_versions`, `tour_events`.

```bash
docker compose up -d          # depuis la racine
cp .env.example .env
pnpm db:migrate
pnpm --filter @circuy/api dev
```

Après une modification du schéma :

```bash
pnpm db:generate
pnpm db:migrate
```

Une migration ne mélange jamais un changement de structure et une reprise de données.

Purge mensuelle des événements de plus de treize mois : `pnpm --filter @circuy/api purge-events`. Restauration d'essai : `pnpm db:restore-drill` (jamais sur la base de travail).

Les tests d'intégration utilisent PostgreSQL réel (jamais simulé). Ils exigent une base joignable.
