tripoli.cloud/tripoli-api
Adam Belkacemi 3172b3e256
All checks were successful
CI / build-and-test (push) Successful in 2m16s
CI / deploy (push) Successful in 3m16s
Vitrine publique (accueil, architecture, statut) + recherche plein texte
- Nouvelles pages publiques sans routeur externe (History API minimale,
  useRoute/RouteLink) : accueil, /architecture (état réel du projet,
  honnête sur les limites), /statut (télémétrie live de tripoli-edge
  lue directement sur /healthz et /metrics, aucune donnée simulée).
- Recherche façon Spotlight dans TopBar : nouvel endpoint
  GET /api/search côté tripoli-api, recherche plein texte Postgres
  (tsvector généré + index GIN) sur le nom ET le contenu des fichiers
  texte extrait à l'upload (formats texte/code, borné à 5 Mio) ;
  extraits de résultat via ts_headline.
- AuthLayout : logo cliquable vers l'accueil.

Vérifié : tsc --noEmit propre, npm run build propre, cargo build -p
tripoli-api propre.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 13:03:52 +02:00
..
deploy Phase 3 : tripoli-api (auth, fichiers, PostgreSQL) 2026-09-16 22:43:02 +02:00
migrations Vitrine publique (accueil, architecture, statut) + recherche plein texte 2026-09-24 13:03:52 +02:00
src Vitrine publique (accueil, architecture, statut) + recherche plein texte 2026-09-24 13:03:52 +02:00
Cargo.toml Frontend et backend entièrement réels : plus aucune donnée simulée 2026-09-17 18:19:51 +02:00
README.md Frontend et backend entièrement réels : plus aucune donnée simulée 2026-09-17 18:19:51 +02:00

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