Logiciel · Santé
NoBullFit
Un outil de suivi santé et fitness axé sur la confidentialité. Je construis l'ensemble du produit moi-même : le frontend, le backend, le modèle de données et toute l'infrastructure peu glamour entre les deux.
Ce que ça fait
NoBullFit est un journal de santé quotidien. Vous pouvez suivre votre alimentation par repas, créer et partager des recettes, enregistrer des activités (avec synchronisation Strava optionnelle), suivre votre poids, gérer des listes d'épicerie et consulter le tout dans un tableau de bord avec graphiques et rapports PDF exportables.
L'application propose une version gratuite ainsi qu'une version Pro via Paddle. La version Pro ajoute des outils de planification : préparation des repas et entraînements à l'avance, suivi d'objectifs de poids avec recommandations de macros, et personnalisation du contenu des rapports.
Architecture
Une seule base de code TypeScript pour le frontend et le backend. React 19 avec Vite côté client, rendu serveur via les loaders de React Router v7 exécutés d'abord sur le serveur puis hydratés dans le navigateur. Express 5 côté backend, avec PostgreSQL pour les données relationnelles.
La recherche alimentaire est la partie la plus intéressante. Au lieu d'interroger une API nutritionnelle tierce à chaque frappe, l'application embarque sa propre copie du jeu de données OpenFoodFacts et l'interroge localement avec DuckDB. Les recherches ne quittent jamais le serveur, ce qui est à la fois plus rapide et un choix délibéré pour la confidentialité.
Les données par élément qui ne correspondent pas à un schéma fixe (nutriments, champs d'activité, étapes de recette) sont stockées dans des colonnes JSONB, avec des index composites optimisés pour les requêtes réellement utilisées, presque toujours par utilisateur et par date.
Auditabilité
Un journal système append-only se trouve au centre de l'application. Chaque action importante est catégorisée (création de compte, aliment enregistré, recette publiée, ouverture du portail de facturation) et chaque requête enregistre sa méthode, son endpoint, son statut et sa durée. Les paramètres sensibles sont masqués avant toute écriture dans les logs.
Les intégrations possèdent leur propre historique. Chaque importation Strava enregistre les temps d'exécution, le nombre d'éléments traités et l'état des tentatives, afin qu'un échec partiel soit visible plutôt que silencieux. Les tentatives de connexion sont journalisées avec l'adresse IP pour détecter les attaques par force brute.
Vie privée dès la conception
Les jetons OAuth des services connectés sont chiffrés au repos avec AES-256-GCM, chacun avec son propre IV. Un champ token_version lié à l'utilisateur permet qu'un changement de mot de passe ou une déconnexion invalide instantanément toutes les sessions actives, sans avoir à maintenir de liste de révocation.
Il n'y a aucun tracker tiers ni reCAPTCHA ; l'inscription utilise plutôt une petite vérification mathématique auto-hébergée, afin qu'aucune donnée sur un nouvel utilisateur ne soit transmise à un réseau publicitaire. L'exportation des données est réelle et authentifiée : un dump JSON complet de tout ce qui est lié à un compte. La suppression des données peut être partielle ou totale, avec une suppression en cascade propre à travers le schéma.
Il est plus honnête de qualifier le projet de “respectueux de la vie privée” plutôt que maximaliste. Les journaux contiennent toujours l'email et l'adresse IP pour des raisons opérationnelles. L'important, c'est que les promesses de confidentialité correspondent à des mécanismes réels dans le code, et non à un simple paragraphe dans une politique.
- Base de données d'aliments auto-hébergée. OpenFoodFacts interrogée localement avec DuckDB. Aucun appel à une API d'aliments tierce.
- Journal en ajout seul. Chaque action catégorisée, chaque requête journalisée et nettoyée.
- Jetons d'intégration chiffrés. Secrets OAuth stockés en AES-256-GCM avec un IV par enregistrement.
- Invalidation de session instantanée. Un champ token_version coupe toutes les sessions au changement de mot de passe ou à la déconnexion.
- File de webhooks asynchrone. Les événements Strava sont mis en file et traités par lots, avec réessais et déduplication.
- Export et suppression réels. Export JSON complet authentifié, suppression limitée dans le temps, propagation à travers le schéma.