docs: une seule source d architecture, et un README qui dit vrai
DOC-002. docs/architecture.html (746 l.) et docs/architecture-v3.html (798 l.) coexistaient, en HTML versionne, sans rien qui indique laquelle faisait foi ni ou etait passee la v2. docs/architecture.md reprend la v3 -- la plus complete : 24 classes au lieu de 19, plus la structure des paquets -- en Markdown avec les diagrammes en Mermaid. Lisible en revue, comparable en diff, rendu directement par Gitea comme par GitHub. Ajoute au document ce que les diagrammes ne montrent pas et qu il faut savoir avant de les lire : la double modelisation coach/equipe, l absence de migrations, et le bot dans le meme processus que le web. README Il annoncait « Authorization Checks: Proper ownership validation on all sensitive operations » a une epoque ou trois IDOR etaient ouverts, et « Rate Limiting » sans mentionner que trusted_proxy='*' la rend contournable. Il ne disait pas non plus comment installer, lancer ni tester le projet. Reecrit en francais, avec deux sections distinctes : ce qui est en place et verifie par des tests, et **ce qui ne l est pas** -- migrations absentes, secrets non revoques, proxy de confiance, identite Discord non prouvee. Une liste de securite qui ne mentionne que les bonnes nouvelles est pire qu absente : on s y fie. Instructions d installation, de lancement et de verification ajoutees, avec les deux seuls points d entree (ARCH-007). Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -1,90 +1,133 @@
|
|||||||
# Plateforme centralisée de tryouts
|
# Plateforme centralisée de tryouts
|
||||||
|
|
||||||
## Security Configuration
|
Application interne du club e-sport de l'UdeS : inscriptions aux sélections,
|
||||||
|
évaluations, gestion des équipes, disponibilités, contrats, et notifications
|
||||||
|
Discord.
|
||||||
|
|
||||||
### Required Environment Variables
|
Le site est servi **en français**, l'anglais reste accessible par le sélecteur
|
||||||
|
de la barre latérale (voir `docs/translations.md`).
|
||||||
|
|
||||||
Before deploying, create a `.env` file which integrates everything in the .env.exemple.
|
---
|
||||||
Ensure you follow the comments of the exemple if you are to use this tool in production.
|
|
||||||
|
|
||||||
|
## Démarrer
|
||||||
|
|
||||||
### Security Features Implemented
|
```bash
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
- **Rate Limiting**: Login endpoint limited to 10 requests per minute to prevent brute-force attacks
|
`SECRET_KEY` et `DATABASE_URL` sont **obligatoires** : `create_app()` refuse
|
||||||
- **Secure Session Cookies**: HTTPSOnly, SameSite=Lax, and Secure flags enabled
|
de démarrer sans eux. `DATABASE_URL` doit pointer sur PostgreSQL ; le pilote
|
||||||
- **CSRF Protection**: Enabled by default on all forms
|
psycopg 3 est nommé automatiquement si l'URL n'en nomme pas.
|
||||||
- **HTTPS Enforcement**: Automatic redirect to HTTPS in production
|
|
||||||
- **Security Headers**: X-Frame-Options, X-Content-Type-Options, Content-Security-Policy, HSTS
|
|
||||||
- **Open Redirect Prevention**: URL validation on login redirect
|
|
||||||
- **Authorization Checks**: Proper ownership validation on all sensitive operations
|
|
||||||
- **nginx**: reverse-proxy and load balancer
|
|
||||||
- **Waitress WSGI**: Production ready WSGI
|
|
||||||
|
|
||||||
### When true in .env:
|
Production : `python wsgi.py` (Waitress derrière nginx). Voir
|
||||||
- **Forces HTTPS only**
|
`docs/deployment.md`.
|
||||||
- **Forcer secure cookies**
|
|
||||||
|
|
||||||
## App details
|
Ce sont les **deux seuls** points d'entrée.
|
||||||
|
|
||||||
### Code
|
## Vérifier
|
||||||
|
|
||||||
- Full python backend using flask
|
```bash
|
||||||
- statics are pure HTML and CSS
|
.venv/Scripts/python -m pytest # suite complète
|
||||||
- Some js to add logic to styling and showing certain pages/cards
|
.venv/Scripts/python -m ruff check . # lint
|
||||||
|
.venv/Scripts/python -m ruff format --check .
|
||||||
|
```
|
||||||
|
|
||||||
### Functionalities
|
Les trois tournent en CI et y sont bloquants.
|
||||||
|
|
||||||
- **User base with sign-ins**: Forces users to create an account and register pertinent information for tryouts and teams. The admin can attribute them a role.
|
---
|
||||||
- **User-Role-Based Permissions**: admin - full acces, coach/manager - access to team management, player - views what he is registered in (no management), scout - view only
|
|
||||||
- **Tryout Management**: manage internal tryout teams, organise internal tryouts matches (3 formats, team vs team, PvP, scrim). Coaches can Evaluate players based on 10 criteria
|
|
||||||
- **Team Management**: manage teams for the season, create matches and practices. When planning a practice there will be a calendar showing player availabitlities slots to help chose a time
|
|
||||||
- **Coach and Player Availabilities**: Allow better planning for the coaches, and for players to book One on Ones with their coach.
|
|
||||||
- **Player Notes**: Coaches can give notes to their players. The players will see them and there is a history which keeps the most recent notes.
|
|
||||||
- **Team Notes**: Coaches can give notes to their teams, where all players from that team can see the note.
|
|
||||||
- **One on One**: Players can request a One on One meeting with their coach. This sends a discord dm to the coach to accept or refuse. The player is then notified of the response.
|
|
||||||
- **Availabilities**: Allow players and coach to enter the moments they are available. Allows for easier practice setup and One on One planning.
|
|
||||||
|
|
||||||
|
## Ce que fait l'application
|
||||||
|
|
||||||
## Discord Integration
|
- **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.
|
||||||
|
|
||||||
The application supports sending Discord direct messages to coaches when players request One on One sessions,
|
## Comment c'est construit
|
||||||
when matches/tryouts/practices are created and a player is in it, and the players get match reminders 24h before a match.
|
|
||||||
|
|
||||||
When sending a **One on One** request, the coach can accept via the platform or react to the discord message to answer the booking request.
|
Backend Python 3.12 / Flask, rendu serveur en Jinja2, CSS et JavaScript
|
||||||
Same thing with **matches** and **practices**, the players can react or answer on the platform.
|
maison, sans framework front. Base PostgreSQL via SQLAlchemy. Bot Discord
|
||||||
|
(`discord.py`) dans un fil du même processus que le serveur web.
|
||||||
|
|
||||||
### Setup Instructions
|
`docs/architecture.md` contient les diagrammes (classes, paquets, flux
|
||||||
|
d'une requête).
|
||||||
|
|
||||||
#### 1. Create a Discord Bot (Not needed for UdeS user, the bot already exists)
|
---
|
||||||
|
|
||||||
1. Go to the [Discord Developer Portal](https://discord.com/developers/applications)
|
## Sécurité
|
||||||
2. Create a new application
|
|
||||||
3. Go to the "Bot" tab and create a bot user
|
|
||||||
4. Copy the bot token - this will be your `DISCORD_BOT_TOKEN`
|
|
||||||
5. Enable the "Message Content Intent" under Privileged Gateway Intents (required for sending messages)
|
|
||||||
|
|
||||||
#### 2. Add Bot to your server
|
En place et vérifié par des tests :
|
||||||
|
|
||||||
For the bot to send DMs:
|
- **Limitation de débit** sur la connexion (10 requêtes/minute par IP).
|
||||||
1. Each user must have the bot added to their Discord server OR be friends with the bot
|
- **Cookies de session** `HttpOnly`, `SameSite=Lax`, `Secure`, avec
|
||||||
2. Users need to add their Discord User ID to their profile:
|
expiration effective.
|
||||||
- Enable Developer Mode in Discord (User Settings → Advanced → Developer Mode)
|
- **CSRF** sur tous les formulaires, y compris la déconnexion (en POST).
|
||||||
- Right-click on their profile → Copy ID
|
- **HTTPS** forcé en production, **HSTS**.
|
||||||
- Enter this numeric ID in the "Discord User ID" field in their profile settings
|
- **CSP sans `unsafe-inline`** sur `script-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 :
|
||||||
|
|
||||||
### How It Works
|
- **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='*'`** dans `wsgi.py` : l'en-tête `X-Forwarded-For` est
|
||||||
|
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.
|
||||||
|
|
||||||
When a player submits a One on One request:
|
`docs/security-checklist.md` détaille la liste avant mise en production.
|
||||||
1. The system checks if the coach has a Discord User ID configured
|
|
||||||
2. If configured, a direct message is sent to the coach via the Discord bot
|
|
||||||
|
|
||||||
### Message Format
|
---
|
||||||
|
|
||||||
The Discord DM includes:
|
## Intégration Discord
|
||||||
- Player name
|
|
||||||
- Team name
|
Messages privés au coach lors d'une demande d'un-à-un, aux joueurs à la
|
||||||
- Requested date and time slot
|
création d'un match ou d'un entraînement les concernant, et rappel 24 h
|
||||||
- Discussion points (if provided)
|
avant un match. Les réponses se font par réaction sur le message ou depuis
|
||||||
- Link to the application for approval/rejection
|
le site.
|
||||||
- Two provided reactions to accept or refuse via discord
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
1. Créer une application sur le [portail développeur
|
||||||
|
Discord](https://discord.com/developers/applications), puis un bot.
|
||||||
|
2. Copier le jeton dans `DISCORD_BOT_TOKEN`.
|
||||||
|
3. 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.
|
||||||
|
4. 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é.
|
||||||
|
|||||||
@@ -1,747 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Team Tryouts — Architecture Documentation</title>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg: #1a1a2e;
|
|
||||||
--surface: #16213e;
|
|
||||||
--primary: #0f3460;
|
|
||||||
--accent: #e94560;
|
|
||||||
--text: #eee;
|
|
||||||
--text-muted: #aaa;
|
|
||||||
--border: #2a2a4a;
|
|
||||||
--card-bg: #1e2a3a;
|
|
||||||
}
|
|
||||||
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
background: var(--surface);
|
|
||||||
border-bottom: 2px solid var(--accent);
|
|
||||||
padding: 1.5rem 2rem;
|
|
||||||
text-align: center;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 100;
|
|
||||||
box-shadow: 0 2px 20px rgba(0,0,0,0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
header h1 {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
color: var(--accent);
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header p {
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-top: 1rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a {
|
|
||||||
background: var(--primary);
|
|
||||||
color: var(--text);
|
|
||||||
padding: 0.5rem 1.5rem;
|
|
||||||
border-radius: 6px;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
transition: background 0.2s, transform 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a:hover {
|
|
||||||
background: var(--accent);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
max-width: 1400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
section h2 {
|
|
||||||
color: var(--accent);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
section h2 .icon {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mermaid-wrapper {
|
|
||||||
background: var(--card-bg);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 2rem;
|
|
||||||
overflow-x: auto;
|
|
||||||
box-shadow: 0 4px 24px rgba(0,0,0,0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mermaid-wrapper.full-width {
|
|
||||||
/* full bleed for wide diagrams */
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend {
|
|
||||||
background: var(--card-bg);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
margin-top: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend-dot {
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
border-radius: 3px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend-dot.model { background: #4fc3f7; }
|
|
||||||
.legend-dot.blueprint { background: #ffb74d; }
|
|
||||||
.legend-dot.extension { background: #81c784; }
|
|
||||||
.legend-dot.utility { background: #ba68c8; }
|
|
||||||
.legend-dot.external { background: #e57373; }
|
|
||||||
.legend-dot.middleware { background: #4dd0e1; }
|
|
||||||
|
|
||||||
footer {
|
|
||||||
text-align: center;
|
|
||||||
padding: 2rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
background: var(--card-bg);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card .stat-num {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card .stat-label {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
main { padding: 1rem; }
|
|
||||||
header h1 { font-size: 1.3rem; }
|
|
||||||
.mermaid-wrapper { padding: 1rem; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<header>
|
|
||||||
<h1>🏆 Team Tryouts — Architecture Documentation</h1>
|
|
||||||
<p>UML Class Diagram & Functional System Architecture</p>
|
|
||||||
<nav>
|
|
||||||
<a href="#stats">Overview</a>
|
|
||||||
<a href="#uml">UML Class Diagram</a>
|
|
||||||
<a href="#functional">Functional Graph</a>
|
|
||||||
<a href="#legend">Legend</a>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
|
|
||||||
<!-- ============ STATS ============ -->
|
|
||||||
<section id="stats">
|
|
||||||
<h2><span class="icon">📊</span> Project Statistics</h2>
|
|
||||||
<div class="stats">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-num">19</div>
|
|
||||||
<div class="stat-label">SQLAlchemy Model Classes</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-num">8</div>
|
|
||||||
<div class="stat-label">Route Blueprints</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-num">4</div>
|
|
||||||
<div class="stat-label">Flask Extensions</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-num">2</div>
|
|
||||||
<div class="stat-label">Association Tables (M2M)</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-num">50+</div>
|
|
||||||
<div class="stat-label">Route Endpoints</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-num">8</div>
|
|
||||||
<div class="stat-label">Utility Modules</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- ============ UML CLASS DIAGRAM ============ -->
|
|
||||||
<section id="uml">
|
|
||||||
<h2><span class="icon">📐</span> UML Class Diagram — All 19 Model Classes</h2>
|
|
||||||
<div class="mermaid-wrapper">
|
|
||||||
|
|
||||||
<div class="mermaid">
|
|
||||||
classDiagram
|
|
||||||
class User {
|
|
||||||
+int id
|
|
||||||
+str username
|
|
||||||
+str password_hash
|
|
||||||
+str role
|
|
||||||
+str full_name
|
|
||||||
+str email
|
|
||||||
+str phone
|
|
||||||
+bool is_active_account
|
|
||||||
+datetime created_at
|
|
||||||
+int failed_login_attempts
|
|
||||||
+datetime locked_until
|
|
||||||
+str games
|
|
||||||
+str discord_username
|
|
||||||
+str discord_user_id
|
|
||||||
+str league_os_profile
|
|
||||||
+get_games_list()
|
|
||||||
+has_role()
|
|
||||||
+can_evaluate()
|
|
||||||
+can_manage_users()
|
|
||||||
+can_manage_tryouts()
|
|
||||||
+can_manage_teams()
|
|
||||||
+can_schedule_matches()
|
|
||||||
+can_manage_this_tryout()
|
|
||||||
+can_manage_this_org_team()
|
|
||||||
+get_gamertags()
|
|
||||||
+get_org_teams()
|
|
||||||
}
|
|
||||||
class UserGamertag {
|
|
||||||
+int id
|
|
||||||
+int user_id
|
|
||||||
+str game
|
|
||||||
+str gamertag
|
|
||||||
+str platform
|
|
||||||
+get_trn_url()
|
|
||||||
}
|
|
||||||
class OrgTeam {
|
|
||||||
+int id
|
|
||||||
+str name
|
|
||||||
+int created_by
|
|
||||||
+datetime created_at
|
|
||||||
+int coach_id
|
|
||||||
+int manager_id
|
|
||||||
+get_coaches()
|
|
||||||
+get_managers()
|
|
||||||
+players()
|
|
||||||
+get_players_with_status()
|
|
||||||
}
|
|
||||||
class TeamPlayer {
|
|
||||||
+int id
|
|
||||||
+int player_id
|
|
||||||
+int org_team_id
|
|
||||||
+str status
|
|
||||||
+str position
|
|
||||||
+datetime added_at
|
|
||||||
}
|
|
||||||
class Tryout {
|
|
||||||
+int id
|
|
||||||
+str title
|
|
||||||
+str description
|
|
||||||
+str game
|
|
||||||
+date date
|
|
||||||
+str location
|
|
||||||
+str status
|
|
||||||
+int max_players
|
|
||||||
+int created_by
|
|
||||||
+int target_org_team_id
|
|
||||||
+int manager_id
|
|
||||||
+int coach_id
|
|
||||||
+datetime created_at
|
|
||||||
}
|
|
||||||
class TryoutRegistration {
|
|
||||||
+int id
|
|
||||||
+int tryout_id
|
|
||||||
+int player_id
|
|
||||||
+datetime registered_at
|
|
||||||
+str status
|
|
||||||
+str notes
|
|
||||||
}
|
|
||||||
class Evaluation {
|
|
||||||
+int id
|
|
||||||
+int tryout_id
|
|
||||||
+int player_id
|
|
||||||
+int evaluator_id
|
|
||||||
+int mecanics_score
|
|
||||||
+int cohesion_score
|
|
||||||
+int communication_score
|
|
||||||
+int gamesense_score
|
|
||||||
+int versatility_score
|
|
||||||
+int discipline_score
|
|
||||||
+int analysis_score
|
|
||||||
+int sport_ethics_score
|
|
||||||
+int mental_score
|
|
||||||
+float overall_score
|
|
||||||
+str comments
|
|
||||||
+str position_recommendation
|
|
||||||
+datetime created_at
|
|
||||||
+datetime updated_at
|
|
||||||
}
|
|
||||||
class Team {
|
|
||||||
+int id
|
|
||||||
+int tryout_id
|
|
||||||
+str name
|
|
||||||
+int created_by
|
|
||||||
+datetime created_at
|
|
||||||
}
|
|
||||||
class TeamMember {
|
|
||||||
+int id
|
|
||||||
+int team_id
|
|
||||||
+int player_id
|
|
||||||
+str position
|
|
||||||
+datetime added_at
|
|
||||||
}
|
|
||||||
class Match {
|
|
||||||
+int id
|
|
||||||
+int tryout_id
|
|
||||||
+str title
|
|
||||||
+str description
|
|
||||||
+date date
|
|
||||||
+time start_time
|
|
||||||
+time end_time
|
|
||||||
+str location
|
|
||||||
+str status
|
|
||||||
+str match_type
|
|
||||||
+int created_by
|
|
||||||
+datetime created_at
|
|
||||||
+int team1_id
|
|
||||||
+int team2_id
|
|
||||||
+get_participating_players()
|
|
||||||
}
|
|
||||||
class PlayerDisponibility {
|
|
||||||
+int id
|
|
||||||
+int player_id
|
|
||||||
+int day_of_week
|
|
||||||
+time start_time
|
|
||||||
+time end_time
|
|
||||||
+datetime created_at
|
|
||||||
+datetime updated_at
|
|
||||||
}
|
|
||||||
class MatchParticipant {
|
|
||||||
+int id
|
|
||||||
+int match_id
|
|
||||||
+int player_id
|
|
||||||
+int team_side
|
|
||||||
+str position
|
|
||||||
+bool attendance_confirmed
|
|
||||||
+datetime added_at
|
|
||||||
}
|
|
||||||
class Contract {
|
|
||||||
+int id
|
|
||||||
+int player_id
|
|
||||||
+int team_id
|
|
||||||
+int uploaded_by_id
|
|
||||||
+str original_filename
|
|
||||||
+str stored_filename
|
|
||||||
+str file_path
|
|
||||||
+str signed_filename
|
|
||||||
+str signed_file_path
|
|
||||||
+str status
|
|
||||||
+str notes
|
|
||||||
+datetime uploaded_at
|
|
||||||
+datetime signed_at
|
|
||||||
+can_view()
|
|
||||||
+can_upload_signed()
|
|
||||||
}
|
|
||||||
class CoachAvailability {
|
|
||||||
+int id
|
|
||||||
+int coach_id
|
|
||||||
+int day_of_week
|
|
||||||
+time start_time
|
|
||||||
+time end_time
|
|
||||||
+datetime created_at
|
|
||||||
+datetime updated_at
|
|
||||||
}
|
|
||||||
class TeamNote {
|
|
||||||
+int id
|
|
||||||
+int org_team_id
|
|
||||||
+int coach_id
|
|
||||||
+str content
|
|
||||||
+datetime created_at
|
|
||||||
+datetime updated_at
|
|
||||||
}
|
|
||||||
class PersonalNote {
|
|
||||||
+int id
|
|
||||||
+int player_id
|
|
||||||
+int coach_id
|
|
||||||
+str content
|
|
||||||
+datetime created_at
|
|
||||||
+datetime updated_at
|
|
||||||
+int match_id
|
|
||||||
+int team_id
|
|
||||||
+int tryout_id
|
|
||||||
}
|
|
||||||
class OneOnOneRequest {
|
|
||||||
+int id
|
|
||||||
+int player_id
|
|
||||||
+int coach_id
|
|
||||||
+int org_team_id
|
|
||||||
+date date
|
|
||||||
+time start_time
|
|
||||||
+time end_time
|
|
||||||
+str points
|
|
||||||
+str status
|
|
||||||
+datetime created_at
|
|
||||||
+datetime responded_at
|
|
||||||
+bigint discord_message_id
|
|
||||||
+str coach_rejection_message
|
|
||||||
}
|
|
||||||
class TeamMatch {
|
|
||||||
+int id
|
|
||||||
+int org_team_id
|
|
||||||
+str title
|
|
||||||
+str description
|
|
||||||
+str opponent
|
|
||||||
+date date
|
|
||||||
+time start_time
|
|
||||||
+time end_time
|
|
||||||
+str location
|
|
||||||
+str status
|
|
||||||
+int created_by
|
|
||||||
+datetime created_at
|
|
||||||
+get_confirmed_count()
|
|
||||||
}
|
|
||||||
class TeamMatchParticipant {
|
|
||||||
+int id
|
|
||||||
+int team_match_id
|
|
||||||
+int player_id
|
|
||||||
+bool is_confirmed
|
|
||||||
+datetime added_at
|
|
||||||
}
|
|
||||||
class load_user {
|
|
||||||
+load_user(user_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
User "1" --> "*" UserGamertag : gamertags
|
|
||||||
User "1" --> "*" Tryout : created_by
|
|
||||||
User "1" --> "*" Tryout : managed_tryouts
|
|
||||||
User "1" --> "*" Tryout : coached_tryouts
|
|
||||||
User "1" --> "*" Evaluation : evaluations_given
|
|
||||||
User "1" --> "*" Evaluation : evaluations_received
|
|
||||||
User "1" --> "*" TryoutRegistration : tryout_registrations
|
|
||||||
User "1" --> "*" TeamMember : team_assignments
|
|
||||||
User "1" --> "*" TeamPlayer : team_placements
|
|
||||||
User "1" --> "*" Match : created_matches
|
|
||||||
User "1" --> "*" TeamMatch : created_team_matches
|
|
||||||
User "1" --> "*" PlayerDisponibility : disponibilities
|
|
||||||
User "1" --> "*" CoachAvailability : coach_availabilities
|
|
||||||
User "1" --> "*" Team : created_teams
|
|
||||||
User "1" --> "*" Contract : contracts
|
|
||||||
User "1" --> "*" PersonalNote : personal_notes
|
|
||||||
User "1" --> "*" OneOnOneRequest : one_on_one_requests
|
|
||||||
|
|
||||||
OrgTeam "1" --> "*" TeamPlayer : team_players
|
|
||||||
OrgTeam "1" --> "*" Tryout : tryouts
|
|
||||||
OrgTeam "1" --> "*" TeamNote : team_notes
|
|
||||||
OrgTeam "1" --> "*" TeamMatch : team_matches
|
|
||||||
OrgTeam "1" --> "*" OneOnOneRequest : requests
|
|
||||||
OrgTeam "1" --> "*" Contract : contracts
|
|
||||||
|
|
||||||
Tryout "1" --> "*" TryoutRegistration : registrations
|
|
||||||
Tryout "1" --> "*" Evaluation : evaluations
|
|
||||||
Tryout "1" --> "*" Team : teams
|
|
||||||
Tryout "1" --> "*" Match : matches
|
|
||||||
Tryout "1" --> "*" PersonalNote : notes
|
|
||||||
|
|
||||||
Team "1" --> "*" TeamMember : members
|
|
||||||
Team "1" --> "*" Match : matches_as_team1
|
|
||||||
Team "1" --> "*" Match : matches_as_team2
|
|
||||||
Match "1" --> "*" MatchParticipant : participants
|
|
||||||
Match "1" --> "*" PersonalNote : notes
|
|
||||||
|
|
||||||
TeamMatch "1" --> "*" TeamMatchParticipant : participants
|
|
||||||
|
|
||||||
User .. load_user : loads
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- ============ FUNCTIONAL ARCHITECTURE GRAPH ============ -->
|
|
||||||
<section id="functional">
|
|
||||||
<h2><span class="icon">🔀</span> Functional Architecture — Request Flow & Component Graph</h2>
|
|
||||||
<div class="mermaid-wrapper">
|
|
||||||
|
|
||||||
<div class="mermaid">
|
|
||||||
graph TD
|
|
||||||
subgraph CLIENT["🌐 Client Layer"]
|
|
||||||
BROWSER["Browser / User"]
|
|
||||||
DISCORD_APP["Discord App"]
|
|
||||||
MONITOR["Monitoring / LB"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph PROXY["🔄 Reverse Proxy"]
|
|
||||||
NGINX["Nginx<br/>(TLS termination,<br/>static files,<br/>rate limiting)"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph APP["🐍 Flask Application (create_app)"]
|
|
||||||
direction TB
|
|
||||||
|
|
||||||
subgraph INGRESS["Incoming"]
|
|
||||||
BEFORE_REQ["@before_request<br/>force_https()"]
|
|
||||||
CSRF_CHECK["CSRF Validate"]
|
|
||||||
LIMITER_CHECK["Rate Limiter<br/>200/day · 50/hr"]
|
|
||||||
LOGIN_CHECK["@login_required"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph BLUEPRINTS["📦 Route Blueprints (8 total)"]
|
|
||||||
AUTH_BP["auth_bp<br/>━━━━━━━<br/>⁕ /auth/login<br/>⁕ /auth/register<br/>⁕ /auth/logout<br/>⁕ /auth/profile<br/>⁕ /auth/change-password<br/>⁕ /auth/delete-account"]
|
|
||||||
MAIN_BP["main_bp<br/>━━━━━━━<br/>⁕ /<br/>⁕ /dashboard<br/>⁕ /privacy<br/>⁕ /terms"]
|
|
||||||
TRYOUTS_BP["tryouts_bp<br/>━━━━━━━<br/>⁕ /tryouts/<br/>⁕ /tryouts/create<br/>⁕ /tryouts/{id}<br/>⁕ /tryouts/{id}/register<br/>⁕ /tryouts/{id}/edit<br/>⁕ /tryouts/{id}/delete"]
|
|
||||||
TEAMS_BP["teams_bp<br/>━━━━━━━<br/>⁕ /teams/<br/>⁕ /teams/create<br/>⁕ /teams/{id}<br/>⁕ /teams/{id}/edit<br/>⁕ /teams/{id}/delete<br/>⁕ /teams/{id}/add-player"]
|
|
||||||
MATCHES_BP["matches_bp<br/>━━━━━━━<br/>⁕ /matches/<br/>⁕ /matches/create<br/>⁕ /matches/{id}<br/>⁕ /matches/{id}/edit"]
|
|
||||||
TEAM_MATCHES_BP["team_matches_bp<br/>━━━━━━━<br/>⁕ /team-matches/<br/>⁕ /team-matches/create<br/>⁕ /team-matches/{id}<br/>⁕ /team-matches/{id}/edit<br/>⁕ /team-matches/{id}/delete"]
|
|
||||||
USERS_BP["users_bp<br/>━━━━━━━<br/>⁕ /users/<br/>⁕ /users/create<br/>⁕ /users/{id}<br/>⁕ /users/{id}/delete<br/>⁕ /users/disponibilities/<br/>⁕ /users/coach-availability/<br/>⁕ /users/api/gamertags"]
|
|
||||||
EVALS_BP["evaluations_bp<br/>━━━━━━━<br/>⁕ /evaluations/<br/>⁕ /evaluations/create<br/>⁕ /evaluations/{id}<br/>⁕ /evaluations/{id}/edit<br/>⁕ /evaluations/{id}/delete"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph EGRESS["Outgoing"]
|
|
||||||
AFTER_REQ["@after_request<br/>add_security_headers()"]
|
|
||||||
ERROR_HANDLERS["Error Handlers<br/>400 · 401 · 403 · 404<br/>429 · 500 · HTTPException"]
|
|
||||||
HEALTH["GET /health"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph EXTENSIONS["🔌 Flask Extensions"]
|
|
||||||
DB_EXT["SQLAlchemy<br/>(db)"]
|
|
||||||
LOGIN_EXT["Flask-Login<br/>(login_manager)"]
|
|
||||||
CSRF_EXT["CSRFProtect<br/>(csrf)"]
|
|
||||||
LIMITER_EXT["Flask-Limiter<br/>(limiter)"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph MODELS["🗄️ SQLAlchemy Models (models.py)"]
|
|
||||||
direction LR
|
|
||||||
USER_M["User"]
|
|
||||||
TRYOUT_M["Tryout"]
|
|
||||||
EVAL_M["Evaluation"]
|
|
||||||
ORGTEAM_M["OrgTeam"]
|
|
||||||
TEAM_M["Team"]
|
|
||||||
MATCH_M["Match"]
|
|
||||||
TEAMMATCH_M["TeamMatch"]
|
|
||||||
OTHERS_M["...15 more classes"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph DB["💾 Database"]
|
|
||||||
SQLITE["SQLite / PostgreSQL<br/>(SQLALCHEMY_DATABASE_URI)"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph UTILS["🛠️ Utility Modules"]
|
|
||||||
VALIDATORS["validators.py<br/>Password/username validation"]
|
|
||||||
LOGGING["logging_config.py<br/>Structured logging"]
|
|
||||||
DISCORD_BOT["discord_bot.py<br/>Discord notifications<br/>+ reaction handling"]
|
|
||||||
SECURITY["security_scan.py<br/>Security audit tool"]
|
|
||||||
SEED["seed.py<br/>Database seeding"]
|
|
||||||
BACKUP["backup.py<br/>Database backup"]
|
|
||||||
MIGRATE["migrate_usernames.py<br/>Username migration"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph TEMPLATES["🖼️ Jinja2 Templates"]
|
|
||||||
PAGES["templates/pages/<br/>(~30 HTML pages)"]
|
|
||||||
LAYOUTS["templates/layouts/<br/>(base, nav)"]
|
|
||||||
ERRORS_TPL["templates/errors/<br/>(400, 401, 403, 404, 429, 500)"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph STATIC["📁 Static Assets"]
|
|
||||||
CSS_F["static/css/"]
|
|
||||||
JS_F["static/js/"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph EXTERNAL["🌍 External APIs"]
|
|
||||||
TRN_API["TRN / Tracker.gg<br/>(gamertag profiles)"]
|
|
||||||
DISCORD_API["Discord API<br/>(bot notifications)"]
|
|
||||||
end
|
|
||||||
|
|
||||||
%% ========== FLOW ==========
|
|
||||||
BROWSER --> NGINX
|
|
||||||
NGINX --> BEFORE_REQ
|
|
||||||
BEFORE_REQ --> CSRF_CHECK
|
|
||||||
CSRF_CHECK --> LIMITER_CHECK
|
|
||||||
LIMITER_CHECK --> LOGIN_CHECK
|
|
||||||
LOGIN_CHECK --> BLUEPRINTS
|
|
||||||
BLUEPRINTS --> AFTER_REQ
|
|
||||||
AFTER_REQ --> BROWSER
|
|
||||||
|
|
||||||
%% Blueprint to models
|
|
||||||
AUTH_BP -.-> USER_M
|
|
||||||
TRYOUTS_BP -.-> TRYOUT_M
|
|
||||||
EVALS_BP -.-> EVAL_M
|
|
||||||
TEAMS_BP -.-> ORGTEAM_M
|
|
||||||
MATCHES_BP -.-> MATCH_M
|
|
||||||
TEAM_MATCHES_BP -.-> TEAMMATCH_M
|
|
||||||
USERS_BP -.-> USER_M
|
|
||||||
|
|
||||||
%% Models to DB
|
|
||||||
MODELS --> DB
|
|
||||||
|
|
||||||
%% Extensions
|
|
||||||
DB_EXT --> MODELS
|
|
||||||
LOGIN_EXT --> USER_M
|
|
||||||
CSRF_EXT --> BLUEPRINTS
|
|
||||||
LIMITER_EXT --> BLUEPRINTS
|
|
||||||
|
|
||||||
%% Utilities
|
|
||||||
VALIDATORS -.-> AUTH_BP
|
|
||||||
VALIDATORS -.-> USERS_BP
|
|
||||||
LOGGING -.-> APP
|
|
||||||
DISCORD_BOT --> DISCORD_API
|
|
||||||
DISCORD_BOT -.-> MATCHES_BP
|
|
||||||
DISCORD_BOT -.-> TEAM_MATCHES_BP
|
|
||||||
DISCORD_BOT -.-> USERS_BP
|
|
||||||
SEED -.-> DB
|
|
||||||
BACKUP -.-> DB
|
|
||||||
|
|
||||||
%% Templates & Static
|
|
||||||
BLUEPRINTS --> TEMPLATES
|
|
||||||
TEMPLATES --> STATIC
|
|
||||||
|
|
||||||
%% External
|
|
||||||
USER_M -.-> TRN_API
|
|
||||||
MONITOR --> HEALTH
|
|
||||||
|
|
||||||
%% Discord
|
|
||||||
DISCORD_APP --> DISCORD_BOT
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- ============ LEGEND ============ -->
|
|
||||||
<section id="legend">
|
|
||||||
<h2><span class="icon">🗺️</span> Legend</h2>
|
|
||||||
<div class="legend">
|
|
||||||
<div class="legend-item">
|
|
||||||
<span class="legend-dot model"></span>
|
|
||||||
<span><strong>SQLAlchemy Models</strong> — Database entity classes</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<span class="legend-dot blueprint"></span>
|
|
||||||
<span><strong>Route Blueprints</strong> — Flask route groups</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<span class="legend-dot extension"></span>
|
|
||||||
<span><strong>Flask Extensions</strong> — SQLAlchemy, Login, CSRF, Limiter</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<span class="legend-dot utility"></span>
|
|
||||||
<span><strong>Utility Modules</strong> — Logging, validators, seeding, backups</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<span class="legend-dot external"></span>
|
|
||||||
<span><strong>External APIs</strong> — Discord, TRN/Tracker.gg</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<span class="legend-dot middleware"></span>
|
|
||||||
<span><strong>Middleware</strong> — Security headers, HTTPS redirect, error handlers</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<span style="font-weight:bold;">─── Solid arrow</span>
|
|
||||||
<span>= HTTP request flow</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<span style="font-weight:bold; color:#999;">- - - Dashed arrow</span>
|
|
||||||
<span>= Data access / logical dependency</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<p>Team Tryouts © 2026 — Generated from codebase analysis</p>
|
|
||||||
<p>19 SQLAlchemy models · 8 blueprints · 4 Flask extensions · 8 utility modules</p>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
mermaid.initialize({
|
|
||||||
startOnLoad: true,
|
|
||||||
theme: 'dark',
|
|
||||||
themeVariables: {
|
|
||||||
primaryColor: '#0f3460',
|
|
||||||
primaryTextColor: '#eee',
|
|
||||||
primaryBorderColor: '#4fc3f7',
|
|
||||||
lineColor: '#4fc3f7',
|
|
||||||
secondaryColor: '#1e2a3a',
|
|
||||||
tertiaryColor: '#16213e',
|
|
||||||
noteBkgColor: '#1a1a2e',
|
|
||||||
noteTextColor: '#aaa',
|
|
||||||
fontFamily: 'Segoe UI, system-ui, -apple-system, sans-serif',
|
|
||||||
fontSize: '13px',
|
|
||||||
},
|
|
||||||
class: {
|
|
||||||
useMaxWidth: false,
|
|
||||||
},
|
|
||||||
flowchart: {
|
|
||||||
useMaxWidth: false,
|
|
||||||
htmlLabels: true,
|
|
||||||
curve: 'basis',
|
|
||||||
padding: 20,
|
|
||||||
nodeSpacing: 30,
|
|
||||||
rankSpacing: 60,
|
|
||||||
defaultRenderer: 'dagre-wrapper',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,199 +1,41 @@
|
|||||||
<!DOCTYPE html>
|
# Architecture
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Team Tryouts — Architecture v3 (Refactored)</title>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg: #1a1a2e;
|
|
||||||
--surface: #16213e;
|
|
||||||
--primary: #0f3460;
|
|
||||||
--accent: #e94560;
|
|
||||||
--text: #eee;
|
|
||||||
--text-muted: #aaa;
|
|
||||||
--border: #2a2a4a;
|
|
||||||
--card-bg: #1e2a3a;
|
|
||||||
}
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
header {
|
|
||||||
background: var(--surface);
|
|
||||||
border-bottom: 2px solid var(--accent);
|
|
||||||
padding: 1.5rem 2rem;
|
|
||||||
text-align: center;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 100;
|
|
||||||
box-shadow: 0 2px 20px rgba(0,0,0,0.5);
|
|
||||||
}
|
|
||||||
header h1 {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
color: var(--accent);
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
header p {
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
nav {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-top: 1rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
nav a {
|
|
||||||
background: var(--primary);
|
|
||||||
color: var(--text);
|
|
||||||
padding: 0.5rem 1.5rem;
|
|
||||||
border-radius: 6px;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
transition: background 0.2s, transform 0.2s;
|
|
||||||
}
|
|
||||||
nav a:hover {
|
|
||||||
background: var(--accent);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
main {
|
|
||||||
max-width: 1500px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
}
|
|
||||||
section h2 {
|
|
||||||
color: var(--accent);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
section h2 .icon { font-size: 1.4rem; }
|
|
||||||
.mermaid-wrapper {
|
|
||||||
background: var(--card-bg);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 2rem;
|
|
||||||
overflow-x: auto;
|
|
||||||
box-shadow: 0 4px 24px rgba(0,0,0,0.3);
|
|
||||||
}
|
|
||||||
.legend {
|
|
||||||
background: var(--card-bg);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
margin-top: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
.legend-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
.legend-dot {
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
border-radius: 3px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.legend-dot.model { background: #4fc3f7; }
|
|
||||||
.legend-dot.subpackage { background: #aed581; }
|
|
||||||
.legend-dot.blueprint { background: #ffb74d; }
|
|
||||||
.legend-dot.extension { background: #81c784; }
|
|
||||||
.legend-dot.utility { background: #ba68c8; }
|
|
||||||
.legend-dot.external { background: #e57373; }
|
|
||||||
.legend-dot.middleware { background: #4dd0e1; }
|
|
||||||
|
|
||||||
footer {
|
Source unique. `docs/architecture.html` et `docs/architecture-v3.html`
|
||||||
text-align: center;
|
coexistaient, en HTML versionné, sans rien qui indique laquelle faisait foi
|
||||||
padding: 2rem;
|
ni où était passée la v2 (DOC-002). Ce fichier reprend le contenu de la v3,
|
||||||
color: var(--text-muted);
|
la plus complète des deux, en Markdown : lisible en revue de code,
|
||||||
font-size: 0.8rem;
|
comparable en diff, et rendu directement par Gitea comme par GitHub.
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
.stats {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
.stat-card {
|
|
||||||
background: var(--card-bg);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.stat-card .stat-num {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
.stat-card .stat-label {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
main { padding: 1rem; }
|
|
||||||
header h1 { font-size: 1.3rem; }
|
|
||||||
.mermaid-wrapper { padding: 1rem; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<header>
|
Les diagrammes sont en Mermaid. Ils décrivent la **structure**, qui bouge
|
||||||
<h1>🏆 Team Tryouts — Architecture v3 (Refactored)</h1>
|
lentement ; les chiffres qui bougent vite (nombre de routes, de tests) sont
|
||||||
<p>UML Class Diagram & Functional Graph — 7 Subpackages + 6 Standalone Models</p>
|
tenus à jour dans le README, pas ici.
|
||||||
<nav>
|
|
||||||
<a href="#stats">Overview</a>
|
|
||||||
<a href="#uml">UML Class Diagram</a>
|
|
||||||
<a href="#packages">Package Structure</a>
|
|
||||||
<a href="#functional">Functional Graph</a>
|
|
||||||
<a href="#legend">Legend</a>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
---
|
||||||
|
|
||||||
<!-- ============ STATS ============ -->
|
## 1. Vue d'ensemble
|
||||||
<section id="stats">
|
|
||||||
<h2><span class="icon">📊</span> Project Statistics</h2>
|
|
||||||
<div class="stats">
|
|
||||||
<div class="stat-card"><div class="stat-num">31</div><div class="stat-label">Python files in models/</div></div>
|
|
||||||
<div class="stat-card"><div class="stat-num">7</div><div class="stat-label">Subpackages</div></div>
|
|
||||||
<div class="stat-card"><div class="stat-num">24</div><div class="stat-label">Classes (incl. abstract)</div></div>
|
|
||||||
<div class="stat-card"><div class="stat-num">8</div><div class="stat-label">Route Blueprints</div></div>
|
|
||||||
<div class="stat-card"><div class="stat-num">4</div><div class="stat-label">Flask Extensions</div></div>
|
|
||||||
<div class="stat-card"><div class="stat-num">8</div><div class="stat-label">Utility Modules</div></div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- ============ UML CLASS DIAGRAM ============ -->
|
| Couche | Contenu |
|
||||||
<section id="uml">
|
|---|---|
|
||||||
<h2><span class="icon">📐</span> UML Class Diagram — All 24 Classes, Relationships & Inheritance</h2>
|
| Entrée | `wsgi.py` (Waitress, production) · `run.py` (développement) |
|
||||||
<div class="mermaid-wrapper">
|
| Fabrique | `app/app.py` — `create_app(config=None)`, blueprints, CSP, journalisation |
|
||||||
|
| Routes | 7 blueprints. `users` est un **paquet** de six modules, un seul blueprint |
|
||||||
|
| Services | `app/services/` — notifications Discord, annonce des matchs planifiés |
|
||||||
|
| Autorisation | `app/permissions.py` — point de vérité unique (ARCH-002) |
|
||||||
|
| Modèles | `app/models/` — héritage polymorphe à table unique sur `User` |
|
||||||
|
| Gabarits | Jinja2, rendu serveur, nonce CSP sur chaque bloc `<script>` |
|
||||||
|
|
||||||
<div class="mermaid">
|
**À savoir avant de lire le reste.** Un coach est rattaché à une équipe de
|
||||||
|
deux façons — la colonne héritée `OrgTeam.coach_id` et la relation
|
||||||
|
many-to-many `OrgTeam.coaches` — et les deux sont peuplées. Le diagramme de
|
||||||
|
classes montre les deux. Ne jamais interroger l'une sans l'autre : passer
|
||||||
|
par `app/permissions.py`. La fusion des deux est `ARCH-001`, qui attend une
|
||||||
|
migration de données.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Diagramme de classes
|
||||||
|
|
||||||
|
```mermaid
|
||||||
classDiagram
|
classDiagram
|
||||||
class BaseAvailability {
|
class BaseAvailability {
|
||||||
+int day_of_week
|
+int day_of_week
|
||||||
@@ -494,17 +336,13 @@ classDiagram
|
|||||||
TeamMatch "1" --> "*" TeamMatchParticipant : participants
|
TeamMatch "1" --> "*" TeamMatchParticipant : participants
|
||||||
|
|
||||||
User .. load_user : loads
|
User .. load_user : loads
|
||||||
</div>
|
```
|
||||||
|
|
||||||
</div>
|
---
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- ============ PACKAGE STRUCTURE ============ -->
|
## 3. Structure du paquet `app/models`
|
||||||
<section id="packages">
|
|
||||||
<h2><span class="icon">📦</span> Model Package Structure — 7 Subpackages</h2>
|
|
||||||
<div class="mermaid-wrapper">
|
|
||||||
|
|
||||||
<div class="mermaid">
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
subgraph MODELS["app/models/ — 31 files"]
|
subgraph MODELS["app/models/ — 31 files"]
|
||||||
direction TB
|
direction TB
|
||||||
@@ -576,17 +414,13 @@ graph TD
|
|||||||
INIT --> CONST
|
INIT --> CONST
|
||||||
INIT --> LOADERS
|
INIT --> LOADERS
|
||||||
INIT --> ASSOC
|
INIT --> ASSOC
|
||||||
</div>
|
```
|
||||||
|
|
||||||
</div>
|
---
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- ============ FUNCTIONAL ARCHITECTURE ============ -->
|
## 4. Flux d'une requête
|
||||||
<section id="functional">
|
|
||||||
<h2><span class="icon">🔀</span> Functional Architecture — Request Flow & Component Graph</h2>
|
|
||||||
<div class="mermaid-wrapper">
|
|
||||||
|
|
||||||
<div class="mermaid">
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
subgraph CLIENT["Client Layer"]
|
subgraph CLIENT["Client Layer"]
|
||||||
BROWSER["Browser / User"]
|
BROWSER["Browser / User"]
|
||||||
@@ -711,89 +545,18 @@ graph TD
|
|||||||
USER_M -.-> TRN_API
|
USER_M -.-> TRN_API
|
||||||
MONITOR --> HEALTH
|
MONITOR --> HEALTH
|
||||||
DISCORD_APP --> DISCORD_BOT
|
DISCORD_APP --> DISCORD_BOT
|
||||||
</div>
|
```
|
||||||
|
|
||||||
</div>
|
---
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- ============ LEGEND ============ -->
|
## 5. Ce que ces diagrammes ne disent pas
|
||||||
<section id="legend">
|
|
||||||
<h2><span class="icon">🗺️</span> Legend</h2>
|
|
||||||
<div class="legend">
|
|
||||||
<div class="legend-item">
|
|
||||||
<span class="legend-dot model"></span>
|
|
||||||
<span><strong>Models (abstract)</strong> — BaseAvailability, BaseMatch, BaseParticipant</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<span class="legend-dot subpackage"></span>
|
|
||||||
<span><strong>Subpackages</strong> — user_model, availability, match_model, participant, org_team, tryout, team</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<span class="legend-dot blueprint"></span>
|
|
||||||
<span><strong>Blueprints</strong> — Flask route groups</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<span class="legend-dot extension"></span>
|
|
||||||
<span><strong>Extensions</strong> — SQLAlchemy, Login, CSRF, Limiter</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<span class="legend-dot utility"></span>
|
|
||||||
<span><strong>Utilities</strong> — Logging, validators, seed, backup, bot</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<span class="legend-dot external"></span>
|
|
||||||
<span><strong>External APIs</strong> — Discord API, TRN/Tracker.gg</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<span class="legend-dot middleware"></span>
|
|
||||||
<span><strong>Middleware</strong> — HTTPS redirect, security headers, errors</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<span style="font-weight:bold;color:#ccc;">━━ Solid</span>
|
|
||||||
<span>= HTTP / data flow</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<span style="font-weight:bold;color:#999;">┅┅ Dashed</span>
|
|
||||||
<span>= Logical dependency</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</main>
|
- **Les migrations.** Il n'y en a pas. Le schéma est créé par
|
||||||
|
`db.create_all()`, qui crée les tables absentes et **n'ALTER jamais** :
|
||||||
<footer>
|
une colonne ajoutée à un modèle n'existe pas en production. C'est
|
||||||
<p>Team Tryouts © 2026 — Generated from refactored codebase (commit fc1bdc5)</p>
|
`DB-002`/`DB-004`, et c'est ce qui bloque la moitié du reste.
|
||||||
<p>31 files · 7 subpackages · 24 classes · 8 blueprints · 4 extensions · 8 utilities</p>
|
- **Le bot Discord** tourne dans un fil démon **du même processus** que le
|
||||||
</footer>
|
serveur web. Son état est exposé par `/health`.
|
||||||
|
- **Les autorisations réelles.** Le diagramme montre les classes, pas les
|
||||||
<script>
|
règles. Celles-ci sont dans `app/permissions.py` et dans les méthodes
|
||||||
mermaid.initialize({
|
`can_*` des sous-classes de `User`.
|
||||||
startOnLoad: true,
|
|
||||||
theme: 'dark',
|
|
||||||
themeVariables: {
|
|
||||||
primaryColor: '#0f3460',
|
|
||||||
primaryTextColor: '#eee',
|
|
||||||
primaryBorderColor: '#4fc3f7',
|
|
||||||
lineColor: '#4fc3f7',
|
|
||||||
secondaryColor: '#1e2a3a',
|
|
||||||
tertiaryColor: '#16213e',
|
|
||||||
noteBkgColor: '#1a1a2e',
|
|
||||||
noteTextColor: '#aaa',
|
|
||||||
fontFamily: 'Segoe UI, system-ui, -apple-system, sans-serif',
|
|
||||||
fontSize: '13px',
|
|
||||||
},
|
|
||||||
class: { useMaxWidth: false },
|
|
||||||
flowchart: {
|
|
||||||
useMaxWidth: false,
|
|
||||||
htmlLabels: true,
|
|
||||||
curve: 'basis',
|
|
||||||
padding: 20,
|
|
||||||
nodeSpacing: 30,
|
|
||||||
rankSpacing: 60,
|
|
||||||
},
|
|
||||||
securityLevel: 'loose',
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user