ARCH-006, avec une requalification du constat.
**Le chiffre de l audit surestimait le probleme.** « 58 commit() en routes
pour un seul rollback() » lisait un ratio comme un defaut. Mesure plutot
que suppose :
- Flask-SQLAlchemy demonte la session a la fin de chaque requete, ce qui
annule tout ce qui n a pas ete commite ;
- l unique rollback est dans le gestionnaire 500, c est-a-dire au bon
endroit ;
- depuis la vague D, aucun module de routes ne contient `except
Exception` : les 34 releves sont dans discord_bot.py, les scripts et le
service de notification, ou avaler l erreur est le comportement voulu
et documente.
Ce que le decompte ne pouvait pas voir, c est le vrai defaut : une fonction
qui commite DEUX fois, ou un echec apres le premier commit laisse une
demi-operation persistee. Il y en avait deux dans tout le depot -- une
analyse AST le confirme. edit_user etait la grave, corrigee avec ARCH-008.
register est la seconde : le compte etait commite, puis les gamertags dans
une seconde transaction. Un echec entre les deux laissait un compte dont
les jeux declares etaient absents, l inscription etant annoncee reussie.
Le premier commit devient un flush -- l identifiant est necessaire pour les
lignes suivantes, pas la durabilite.
Les deux commit() de login ne sont pas concernes : ils sont dans des
branches mutuellement exclusives, succes et echec.
tests/test_transactions.py enonce la garantie plutot que de la supposer :
un echec en cours de requete ne laisse aucune ligne, et l inscription est
tout ou rien. Le second echoue sur le code d avant.
Co-Authored-By: Claude Opus 5 <[email protected]>
Plateforme centralisée de tryouts
Application interne du club e-sport de l'UdeS : inscriptions aux sélections, évaluations, gestion des équipes, disponibilités, contrats, et notifications Discord.
Le site est servi en français, l'anglais reste accessible par le sélecteur
de la barre latérale (voir docs/translations.md).
Démarrer
python -m venv .venv
.venv/Scripts/pip install -r requirements.txt -r requirements-dev.txt
cp app/.env.exemple .env # puis remplir les valeurs
.venv/Scripts/python run.py # développement, http://127.0.0.1:5000
SECRET_KEY et DATABASE_URL sont obligatoires : create_app() refuse
de démarrer sans eux. DATABASE_URL doit pointer sur PostgreSQL ; le pilote
psycopg 3 est nommé automatiquement si l'URL n'en nomme pas.
Production : python wsgi.py (Waitress derrière nginx). Voir
docs/deployment.md.
Ce sont les deux seuls points d'entrée.
Vérifier
.venv/Scripts/python -m pytest # suite complète
.venv/Scripts/python -m ruff check . # lint
.venv/Scripts/python -m ruff format --check .
Les trois tournent en CI et y sont bloquants.
Ce que fait l'application
- Comptes et rôles — cinq rôles : président (
admin), gérant (manager), coach, joueur (player), recruteur (scout). Le président attribue les rôles. - Sélections — organisation des tryouts, trois formats de match (équipe contre équipe, joueur contre joueur, scrim), évaluation des joueurs sur dix critères.
- Équipes — effectifs de la saison, matchs et entraînements. Le formulaire d'entraînement affiche les disponibilités des joueurs.
- Disponibilités — créneaux hebdomadaires des joueurs, créneaux réservables des coachs.
- Notes — un coach écrit des notes d'équipe (visibles par l'équipe) et des notes nominatives (visibles par le joueur concerné).
- Un-à-un — un joueur demande une séance à son coach ; le coach répond depuis le site ou par une réaction sur le message privé Discord.
- Contrats — dépôt d'un contrat par le staff, signature par le joueur.
Comment c'est construit
Backend Python 3.12 / Flask, rendu serveur en Jinja2, CSS et JavaScript
maison, sans framework front. Base PostgreSQL via SQLAlchemy. Bot Discord
(discord.py) dans un fil du même processus que le serveur web.
docs/architecture.md contient les diagrammes (classes, paquets, flux
d'une requête).
Sécurité
En place et vérifié par des tests :
- Limitation de débit sur la connexion (10 requêtes/minute par IP).
- Cookies de session
HttpOnly,SameSite=Lax,Secure, avec expiration effective. - CSRF sur tous les formulaires, y compris la déconnexion (en POST).
- HTTPS forcé en production, HSTS.
- CSP sans
unsafe-inlinesurscript-src: aucun gestionnaire d'événement en ligne, chaque bloc<script>porte un nonce par requête. - Redirections validées (rien ne sort du site).
- Validation par schémas marshmallow sur les formulaires de compte, avec politique de mot de passe.
- Autorisation centralisée dans
app/permissions.py. - Journal d'authentification (
logs/auth.log) : connexions, échecs, changements de rôle, suppressions de compte. - Téléversements vérifiés par extension et par signature de fichier.
Ce qui n'est pas fait, pour que personne ne s'y fie :
- Aucune migration de schéma.
db.create_all()crée les tables manquantes et ne modifie jamais une table existante : une colonne ajoutée à un modèle est absente de la production. - Les secrets de l'historique git ne sont pas révoqués — jeton du bot,
mot de passe PostgreSQL,
SECRET_KEY. trusted_proxy='*'danswsgi.py: l'en-têteX-Forwarded-Forest accepté de n'importe quelle source, donc la limitation par IP est contournable. À régler avec la topologie réelle du déploiement.- L'identité Discord transite encore par un champ caché du formulaire d'inscription : elle n'est pas prouvée par le passage OAuth2.
docs/security-checklist.md détaille la liste avant mise en production.
Intégration Discord
Messages privés au coach lors d'une demande d'un-à-un, aux joueurs à la création d'un match ou d'un entraînement les concernant, et rappel 24 h avant un match. Les réponses se font par réaction sur le message ou depuis le site.
L'état des messages en attente de réponse est dans discord_pending.json,
non versionné : c'est de l'état d'exécution, propre à chaque serveur.
Mise en place
Le bot du club existe déjà ; ce qui suit ne concerne qu'une nouvelle installation.
- Créer une application sur le portail développeur Discord, puis un bot.
- Copier le jeton dans
DISCORD_BOT_TOKEN. - Activer Message Content Intent dans les Privileged Gateway Intents. C'est le seul intent privilégié demandé : il sert à lire le motif d'un refus écrit en réponse au message.
- Chaque personne doit partager un serveur avec le bot (ou l'avoir en ami) pour recevoir un message privé, et renseigner son identifiant Discord dans son profil (Discord → Paramètres → Avancés → Mode développeur, puis clic droit sur son profil → Copier l'identifiant).
/health indique si le bot tourne et s'il est connecté.