La Médiathèque est née d'une envie simple en tant qu'Engineering Manager : construire quelque chose de suffisamment complexe pour être réellement formateur, mais suffisamment personnel pour rester motivant sur la durée.
Tracker des animés, jeux, films et séries, ça semble anodin. Mais dès qu'on creuse, les problèmes intéressants s'accumulent : comment agréger des données de trois APIs aux structures différentes ? Comment modéliser un suivi d'épisodes qui fonctionne aussi bien pour 12 épisodes que pour 1 000 ? Comment produire des statistiques personnelles qui révèlent quelque chose de vrai sur vos préférences, plutôt que juste compter des entrées ? Et quel rôle donner à l'IA dans l'enrichissement de chaque fiche ?
Ce sont des problèmes d'ingénierie réels — pas des exercices de formation, mais des questions auxquelles il faut répondre si on veut que l'outil soit utile.
Le projet repose sur une séparation nette entre le stockage des données et leur présentation.
chetaku-rs est une API REST écrite en Rust avec Axum, déployée sur Google Cloud Run (serverless, région Europe-West1). Elle gère l'intégralité des données persistantes :
x-api-key) pour les opérations d'écriturechetana.dev et localhost:3000Les routes principales :
GET /media → liste paginée et filtrée
GET /media/{type}/{externalId} → entrée unique par type + ID externe
PATCH /media/{id} → mise à jour (status, score, notes, épisodes)
DELETE /media/{id} → suppression (clé API requise)
GET /stats → statistiques globales pondérées
POST /sync/anime → synchronisation depuis MyAnimeList
POST /sync/game → synchronisation depuis RAWG
POST /sync/movie → synchronisation depuis TMDB
POST /sync/series → synchronisation depuis TMDB
Le frontend est intégré dans le portfolio chetana.dev (Nuxt 3 / Nitro). Il joue le rôle de couche d'orchestration : il récupère les données stockées depuis chetaku-rs, puis les enrichit à la volée en interrogeant les APIs tierces selon le type de média.
Chaque entrée dans media_entries stocke les données de suivi personnel — pas les métadonnées publiques, qui restent côté APIs :
| Champ | Type | Description |
|---|---|---|
media_type | TEXT | anime / game / movie / series |
external_id | TEXT | ID dans l'API source (MAL ID, RAWG slug, TMDB ID) |
status | TEXT | watching / completed / plan_to_watch / etc. |
score | SMALLINT | Note personnelle (1–10), nullable |
episodes_watched | INTEGER | Épisodes vus (anime et séries) |
playtime_hours | INTEGER | Heures jouées (jeux) |
genres | TEXT[] | Genres (dénormalisés pour les requêtes stats) |
creator | TEXT | Studio, développeur, réalisateur ou showrunner |
notes | TEXT | Notes personnelles libres |
Chaque type de média a sa propre source de données, avec des structures différentes et des contraintes différentes :
filler et recap, et trailer YouTube. La pagination des épisodes (100 par page) impose de gérer le cas has_next_page.
Les arcs narratifs sont hardcodés côté serveur dans server/utils/anime-arcs.ts — un objet indexé par MAL ID. Alternative fragile : scraper un wiki. Alternative choisie : données stables, contrôlées, maintenables.
Promise.allSettled sur un maximum de 15 saisons, avec dégradation gracieuse si un appel échoue.
Pour les films, TMDB fournit aussi le cast (top 10 avec photos), le réalisateur, le tagline et la durée — des données qui rendent chaque fiche nettement plus riche qu'une simple jaquette + score.
Suivre des épisodes de manière significative est plus complexe qu'un simple compteur. L'interface affiche :
Pour les saisons, l'algorithme calcule un offset cumulatif : la somme des épisodes de toutes les saisons précédentes. Si j'ai vu 45 épisodes et que les saisons font respectivement 10, 13 et 26 épisodes, je suis en saison 3, épisode 22. Ce calcul est fait côté client sur les données TMDB, sans aucun appel supplémentaire.
L'endpoint /stats calcule des métriques avancées directement en SQL, en parallèle avec tokio::join! :
love_score = COUNT(*) × AVG(score) — un genre vu 12 fois avec une note moyenne de 9.2 est mieux classé qu'un genre vu 30 fois avec une note de 5.8Le love_score est la métrique centrale. Un simple compteur ne dit pas grand chose — il reflète l'exposition, pas l'appréciation. Le love_score force une balance entre fréquence et qualité perçue, ce qui donne un profil de préférences nettement plus honnête.
Rust n'était pas le choix pragmatique ici — Node.js aurait suffi. C'était un choix délibéré de montée en compétence.
En tant qu'Engineering Manager, je suis régulièrement amené à évaluer des choix d'architecture impliquant Rust : performance critique, sécurité mémoire, workloads embarqués. Mais recommander ou challenger un choix Rust sans l'avoir pratiqué soi-même sur un vrai projet reste une position fragile. Ce projet était l'occasion de combler cet écart.
Le borrow checker, les lifetimes, le modèle d'ownership, les traits async — tout ça ne se comprend vraiment qu'en les rencontrant sur du code réel, pas en lisant de la documentation. Après ce projet, je peux discuter des frictions réelles de Rust avec mes équipes à partir d'une expérience concrète, pas d'une lecture.
Résultat opérationnel : chetaku-rs tourne sur Cloud Run Free Tier, démarre en sous-seconde, consomme ~15 Mo de RAM, et n'a eu aucun crash depuis son déploiement. Le compilateur Rust a éliminé à la conception les classes entières de bugs qui auraient pu apparaître en production.
La médiathèque est en lecture publique : la liste et les fiches sont accessibles à tous. Les opérations d'écriture (ajout, édition, suppression) sont réservées au propriétaire authentifié via Google OAuth. Les appels vers chetaku-rs transitent par une clé API interne jamais exposée côté client.