EsTcalade.fr est une application web Python servie par un seul processus, sans étape de compilation front et sans dépendance JavaScript à installer. Voici les briques retenues et, surtout, ce que chacune apporte réellement au projet.
Le socle : FastAPI et Jinja2
FastAPI assure le routage, la validation et l'injection de dépendances ; Uvicorn sert l'application. Le rendu est fait côté serveur avec Jinja2 : les pages sont du HTML complet, l'interactivité s'ajoute par petites touches. Aucun paquet npm, aucune chaîne de compilation à maintenir — sur un projet mené sur son temps libre, c'est ce qui évite de passer une soirée à réparer l'outillage avant même d'écrire une ligne utile.
Le code est découpé en trois familles : routes (une par domaine : authentification, séances, discussions, teams, profil, groupes, statistiques, administration, push), modèles et services — envoi de push, courriels, rappels, réglages. Chaque module de route porte le vocabulaire du domaine, ce qui rend le projet lisible sans plan détaillé.
Les données : PostgreSQL et SQLAlchemy 2
SQLAlchemy 2 pour l'accès aux données, PostgreSQL 16 en conteneur pour le stockage. Le modèle suit les objets métier : utilisateurs, teams et appartenances, séances, participations, discussions et messages, ascensions, sorties randonnée et vélo, lieux, groupes, messages flash, abonnements push.
Deux choix méritent d'être expliqués.
- Le type d'activité est porté par la séance, mais les performances vivent dans des tables séparées — une par sport. Une randonnée n'a pas de cotation, une voie n'a pas de dénivelé : les forcer dans une même table aurait produit un modèle plein de colonnes vides et de conditions.
- Les compteurs dérivés sont maintenus à l'écriture. Le score d'assurage est incrémenté quand une ascension désigne un assureur, et décrémenté si l'ascension est supprimée — plutôt que recalculé à chaque affichage de profil.
Les migrations passent par Alembic, doublé d'un script de migration maison idempotent pour les mises à jour en production : il applique les changements de schéma avec des instructions conditionnelles et s'arrête en erreur explicite si l'un d'eux échoue. On peut le relancer sans crainte, ce qui est exactement ce que l'on veut à 23 h sur un serveur distant.
Les tâches de fond : APScheduler
Les rappels de séance — la veille, puis une heure avant — sont produits par un ordonnanceur APScheduler qui se réveille toutes les cinq minutes, repère les séances concernées et déclenche les envois.
C'est là qu'un bug instructif s'est produit : les rappels partaient toutes les cinq minutes au lieu d'une seule fois. La cause n'était pas dans la logique de rappel mais dans le mélange des mondes — le code planifiait une tâche asynchrone depuis un thread qui n'avait pas de boucle d'événements. L'erreur levée était rattrapée par le bloc de secours, qui annulait la transaction… y compris le drapeau « rappel envoyé », remis à zéro. Cinq minutes plus tard, la condition était de nouveau vraie.
La correction tient en une ligne — exécuter la coroutine dans sa propre boucle — mais la leçon est plus large : un bloc d'exception qui annule une transaction peut transformer une erreur ponctuelle en boucle infinie. C'est le genre de défaut qui ne se voit pas en développement, où l'on n'attend jamais cinq minutes.
Les notifications : Web Push et VAPID
Les notifications utilisent l'API Web Push avec des clés VAPID, côté serveur via pywebpush. Sept événements en déclenchent : invitation à une séance, rappels, annulation, nouveau message dans une discussion rejointe, message flash, changement d'appartenance à une team.
Deux précautions ont été prises. Les envois, bloquants par nature, sont isolés dans un pool de threads pour ne pas figer le serveur ; et chaque message part avec une durée de vie de 24 heures, de sorte qu'un téléphone éteint le reçoive à son réveil plutôt que de le perdre. Un échec d'envoi individuel est journalisé sans interrompre le lot : si un abonnement est expiré, les autres membres reçoivent quand même leur invitation.
Les graphiques : Chart.js, chargé au bon moment
Les statistiques s'appuient sur Chart.js — histogrammes empilés par cotation, anneau voie/bloc, radar mensuel, distances et dénivelés. La bibliothèque n'est chargée que sur la page qui en a besoin, via le bloc d'en-tête du gabarit correspondant. Les autres pages n'en paient pas le prix.
Les jeux de données sont calculés côté serveur et sérialisés en JSON dans le gabarit : aucune requête supplémentaire au chargement, et toute la logique de regroupement — par cotation, par niveau de difficulté, par mois — reste en Python, testable et lisible.

Les jeux de données sont calculés en Python et sérialisés dans le gabarit : aucune requête supplémentaire au chargement de la page.
La sécurité, au niveau d'un club
- Identifiant et code PIN à quatre chiffres — un compromis assumé pour un usage mobile fréquent —, le PIN étant haché avec bcrypt et jamais stocké en clair.
- Jeton JWT signé pour la session, clé secrète tirée de la configuration.
- Aucune inscription automatique : un administrateur ou un modérateur valide chaque demande avant que le compte n'existe.
- Les droits sont vérifiés côté serveur à chaque action sensible, pas seulement en masquant un bouton dans un gabarit. Masquer une action n'est pas la protéger.
L'exploitation : Docker Compose et une image publiée
Le déploiement se résume à deux conteneurs — l'application et PostgreSQL — décrits dans un fichier Compose, avec un contrôle de santé sur la base pour que l'application ne démarre pas avant elle. L'image applicative est publiée sur un registre public, et les fichiers téléversés (avatars, photos de lieux) sont montés hors du conteneur pour survivre aux mises à jour.
Mettre à jour tient alors en trois commandes : tirer l'image, relancer, appliquer les migrations. La configuration — base, clé secrète, clés VAPID, serveur de courriel, adresse publique — vit entièrement dans des variables d'environnement, avec un fichier d'exemple commenté dans le dépôt et aucun secret versionné.
Ce que ces choix ont en commun
Rien d'exotique, et c'est délibéré : un langage, un serveur, une base, un fichier Compose. Le temps disponible passe dans les fonctionnalités et dans la correction des vrais défauts — comme celui des rappels — plutôt que dans l'entretien d'une architecture plus impressionnante que nécessaire.
Essayer l'application
EsTcalade.fr est gratuite et ouverte aux groupes, aux équipes et aux clubs.
- L'application : app.estcalade.fr — inscription avec un identifiant et un code PIN, puis validation par un administrateur.
- La présentation : www.estcalade.fr — fonctionnalités, captures d'écran et instructions d'installation sur mobile.
Sur téléphone, ouvrez l'application dans le navigateur puis utilisez « Ajouter à l'écran d'accueil » : elle s'installe comme une application et les notifications deviennent disponibles.