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:
GGThed
2026-08-08 18:25:20 -04:00
co-authored by Claude Opus 5
parent e5c29d8113
commit aebc28fb8a
3 changed files with 158 additions and 1099 deletions
-747
View File
@@ -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 &copy; 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>
<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; }
# Architecture
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(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>
Source unique. `docs/architecture.html` et `docs/architecture-v3.html`
coexistaient, en HTML versionné, sans rien qui indique laquelle faisait foi
ni où était passée la v2 (DOC-002). Ce fichier reprend le contenu de la v3,
la plus complète des deux, en Markdown : lisible en revue de code,
comparable en diff, et rendu directement par Gitea comme par GitHub.
<header>
<h1>🏆 Team Tryouts — Architecture v3 (Refactored)</h1>
<p>UML Class Diagram & Functional Graph — 7 Subpackages + 6 Standalone Models</p>
<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>
Les diagrammes sont en Mermaid. Ils décrivent la **structure**, qui bouge
lentement ; les chiffres qui bougent vite (nombre de routes, de tests) sont
tenus à jour dans le README, pas ici.
<main>
---
<!-- ============ STATS ============ -->
<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>
## 1. Vue d'ensemble
<!-- ============ UML CLASS DIAGRAM ============ -->
<section id="uml">
<h2><span class="icon">📐</span> UML Class Diagram — All 24 Classes, Relationships & Inheritance</h2>
<div class="mermaid-wrapper">
| Couche | Contenu |
|---|---|
| Entrée | `wsgi.py` (Waitress, production) · `run.py` (développement) |
| 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
class BaseAvailability {
+int day_of_week
@@ -494,17 +336,13 @@ classDiagram
TeamMatch "1" --> "*" TeamMatchParticipant : participants
User .. load_user : loads
</div>
```
</div>
</section>
---
<!-- ============ PACKAGE STRUCTURE ============ -->
<section id="packages">
<h2><span class="icon">📦</span> Model Package Structure — 7 Subpackages</h2>
<div class="mermaid-wrapper">
## 3. Structure du paquet `app/models`
<div class="mermaid">
```mermaid
graph TD
subgraph MODELS["app/models/ — 31 files"]
direction TB
@@ -576,17 +414,13 @@ graph TD
INIT --> CONST
INIT --> LOADERS
INIT --> ASSOC
</div>
```
</div>
</section>
---
<!-- ============ FUNCTIONAL ARCHITECTURE ============ -->
<section id="functional">
<h2><span class="icon">🔀</span> Functional Architecture — Request Flow & Component Graph</h2>
<div class="mermaid-wrapper">
## 4. Flux d'une requête
<div class="mermaid">
```mermaid
graph TD
subgraph CLIENT["Client Layer"]
BROWSER["Browser / User"]
@@ -711,89 +545,18 @@ graph TD
USER_M -.-> TRN_API
MONITOR --> HEALTH
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>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>
## 5. Ce que ces diagrammes ne disent pas
</main>
<footer>
<p>Team Tryouts &copy; 2026 — Generated from refactored codebase (commit fc1bdc5)</p>
<p>31 files · 7 subpackages · 24 classes · 8 blueprints · 4 extensions · 8 utilities</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,
},
securityLevel: 'loose',
});
</script>
</body>
</html>
- **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** :
une colonne ajoutée à un modèle n'existe pas en production. C'est
`DB-002`/`DB-004`, et c'est ce qui bloque la moitié du reste.
- **Le bot Discord** tourne dans un fil démon **du même processus** que le
serveur web. Son état est exposé par `/health`.
- **Les autorisations réelles.** Le diagramme montre les classes, pas les
règles. Celles-ci sont dans `app/permissions.py` et dans les méthodes
`can_*` des sous-classes de `User`.