tripoli.cloud/tripoli-api/README.md
Adam Belkacemi a639b705a6
All checks were successful
CI / build-and-test (push) Successful in 2m14s
CI / deploy (push) Successful in 3m17s
Frontend et backend entièrement réels : plus aucune donnée simulée
À la demande explicite de l'utilisateur après avoir constaté que
tripoli.cloud affichait un 404 vide (le frontend n'avait jamais été
déployé) : déploiement du frontend, puis remplacement complet des
mocks par de vraies données, des deux côtés.

Backend (tripoli-api) :
- users.name, files.category (file/photo/backup), table notifications
  (migration 0002).
- POST /api/auth/guest : compte invité réel et réutilisé
  (invite@tripoli.local), pas une identité simulée côté frontend.
- GET /api/files?category=... , GET /api/notifications (+ read-all/
  {id}/read), GET /api/storage (used_bytes réel, available_bytes via
  statvfs - jamais un quota inventé).
- Notifications émises réellement sur upload/suppression.

Frontend (app/) :
- src/api/*.ts appellent réellement tripoli-api (plus de mockFetch).
- Upload réel (XHR, vraie progression), suppression avec bouton dédié,
  vraies miniatures photo (<img src="/api/files/:id">).
- Session restaurée au chargement (GET /api/auth/me), logout réel.
- Retiré plutôt que simulé : 2FA, clé de récupération, accès partagés,
  albums, dossiers, "4 appareils sauvegardés", chiffres marketing et
  claim AES-256 côté client sur la page de connexion (jamais vrai -
  remplacé par le vrai : HTTPS + Argon2).

Déploiement :
- Frontend construit et déployé sur le serveur (le build local échouait
  spécifiquement sur cette machine - rolldown/esbuild natif ne répondait
  plus, cause non résolue ; contourné en buildant sur le serveur, comme
  déjà fait pour les crates Rust).
- Caddy : /var/www/tripoli-cloud servi en statique pour tripoli.cloud,
  /api/*, /healthz, /metrics toujours proxyés vers tripoli-edge.
- Pipeline CI étendu : le job deploy construit et déploie maintenant
  aussi le frontend, via un script sudo dédié supplémentaire
  (tripoli-deploy-frontend.sh) sur le compte deploy sans privilège.
- Vérifié en navigateur réel (Chrome) : inscription, connexion, invité,
  upload avec progression, photos, sauvegardes, notifications,
  suppression, déconnexion - tous testés en conditions réelles.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 18:19:51 +02:00

98 lines
4.8 KiB
Markdown

# tripoli-api
Auth, fichiers, métadonnées PostgreSQL. Axum + sqlx (requêtes vérifiées
à l'exécution, pas les macros `query!` à vérification compile-time :
elles auraient exigé une base joignable à chaque `cargo build`, y
compris depuis un poste qui n'a pas le tunnel WireGuard monté - plus de
complexité que ce que ça apporte pour un projet de cette taille).
## Auth
Sessions opaques côté serveur (table `sessions`, cookie `tripoli_session`
`HttpOnly` + `Secure` + `SameSite=Lax`), pas de JWT : révoquer une session
est une suppression de ligne, pas une histoire de clé de signature à
faire tourner. Mots de passe hachés avec Argon2 (`argon2` + `password-hash`,
implémentations RustCrypto).
## Endpoints
| Route | Auth | Rôle |
|---|---|---|
| `GET /healthz` | non | ping PostgreSQL |
| `POST /api/auth/register` | non | `{name, email, password}` -> crée le compte + session |
| `POST /api/auth/login` | non | `{email, password}` -> session |
| `POST /api/auth/guest` | non | connecte (crée si besoin) le compte invité réel `invite@tripoli.local` |
| `POST /api/auth/logout` | oui | invalide la session |
| `GET /api/auth/me` | oui | utilisateur courant |
| `GET /api/files` | oui | fichiers de l'utilisateur courant, `?category=file\|photo\|backup` |
| `POST /api/files` | oui | upload multipart, champs `file` et `category` (optionnel) |
| `GET /api/files/{id}` | oui | téléchargement (vérifie la propriété) |
| `DELETE /api/files/{id}` | oui | suppression (vérifie la propriété) |
| `GET /api/notifications` | oui | notifications de l'utilisateur (émises par upload/suppression) |
| `POST /api/notifications/read-all` | oui | marque tout comme lu |
| `POST /api/notifications/{id}/read` | oui | marque une notification comme lue |
| `GET /api/storage` | oui | `used_bytes` (somme réelle des fichiers), `available_bytes` (`statvfs`) |
## Stockage
Chaque fichier est un blob sur disque nommé par son UUID
(`TRIPOLI_BLOB_DIR`, `/var/lib/tripoli-api/blobs` par défaut) - jamais
par le nom d'origine, qui ne vit qu'en métadonnée PostgreSQL. Ça évite
toute question de traversée de chemin ou de caractères spéciaux dans un
nom de fichier envoyé par un client.
Stockage disque, pas Hetzner Object Storage pour cette v1 : le brief
laissait le choix, et Object Storage aurait ajouté un client S3 et des
identifiants supplémentaires sans bénéfice pour un unique VPS avec 55 Go
disponibles. À revisiter si l'espace disque devient une contrainte
réelle.
L'upload multipart est écrit sur disque chunk par chunk
(`field.chunk().await` -> `AsyncWriteExt::write_all`), jamais accumulé
en mémoire : c'est ce qui rend la progression d'upload réelle côté
client - le corps de la requête part effectivement au fil de l'eau.
## Configuration
| Variable | Défaut | Rôle |
|---|---|---|
| `TRIPOLI_API_LISTEN` | `127.0.0.1:8081` | Adresse d'écoute (jamais publique - tripoli-edge est le seul appelant) |
| `DATABASE_URL` | — (requis) | `postgres://...` |
| `TRIPOLI_BLOB_DIR` | `/var/lib/tripoli-api/blobs` | Stockage des blobs |
| `TRIPOLI_SESSION_TTL_HOURS` | `720` (30 jours) | Durée de vie d'une session |
## Déploiement (fait manuellement pour cette phase, même remarque que tripoli-edge)
- PostgreSQL dans un conteneur Docker dédié (`/opt/tripoli-api/docker-compose.yml`),
**séparé de celui d'iot-alarm-system** : pas de couplage entre projets
qui n'ont rien à voir. Exposé uniquement sur `127.0.0.1:5433` - tripoli-api
tourne nativement sur l'hôte, pas besoin de l'exposer sur un réseau Docker.
Mot de passe généré côté serveur (`/opt/tripoli-api/.env`, jamais commité,
jamais affiché dans un terminal).
- tripoli-api tourne en systemd (`deploy/tripoli-api.service`) sous le même
utilisateur système sans privilège `tripoli` que tripoli-edge.
- Les migrations (`migrations/*.sql`) s'appliquent automatiquement au
démarrage (`sqlx::migrate!`) - `0002_full_features.sql` ajoute `name`
aux utilisateurs, `category` aux fichiers, et la table
`notifications`.
## Compte invité
"Visiter en tant qu'invité" se connecte à un compte réel
(`invite@tripoli.local`), créé une seule fois puis réutilisé - jamais
une identité simulée côté frontend. Son mot de passe est un UUID
aléatoire jamais communiqué : personne ne peut s'y connecter autrement
qu'en passant par `POST /api/auth/guest`.
## Vérifié en production
```
curl https://tripoli.cloud/healthz -> {"status":"ok"}
curl -X POST https://tripoli.cloud/api/auth/register -> compte + session
curl https://tripoli.cloud/api/auth/me (avec cookie) -> utilisateur courant
curl -F file=@... https://tripoli.cloud/api/files -> upload
curl https://tripoli.cloud/api/files/{id} -> download
```
Testé aussi : accès sans cookie (401), email déjà pris (409), mauvais
mot de passe (401), double suppression (404 la seconde fois).