diff --git a/.gitignore b/.gitignore
index 2eea525..8ed6317 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,8 @@
-.env
\ No newline at end of file
+__pycache__/
+*.pyc
+.env
+web/static/assets/uploads/
+.pytest_cache/
+.coverage
+htmlcov/
+*.egg-info/
diff --git a/Caddyfile b/Caddyfile
index a8e3a10..66f37db 100644
--- a/Caddyfile
+++ b/Caddyfile
@@ -1,4 +1,13 @@
-flask.ninolbt.com {
+# =============================================================================
+# Caddyfile — Reverse proxy et terminaison TLS (Let's Encrypt automatique).
+# =============================================================================
+# X-Forwarded-Proto est transmis par défaut par Caddy vers les backends ; sans
+# cet en-tête, Keycloak génère des URLs en http:// derrière le proxy (bug
+# documenté dans le journal de bord, résolu par KC_HOSTNAME/--proxy-headers
+# côté Keycloak — voir docker-compose.yml et Bloc 3 - C5 Debugging).
+# =============================================================================
+
+dashboard.ninolbt.com {
reverse_proxy flask:5000
}
@@ -9,7 +18,3 @@ keycloak.ninolbt.com {
chat.ninolbt.com {
reverse_proxy mattermost:8065
}
-
-wekan.ninolbt.com {
- reverse_proxy wekan:8080
-}
diff --git a/README.en.md b/README.en.md
new file mode 100644
index 0000000..85f72ad
--- /dev/null
+++ b/README.en.md
@@ -0,0 +1,179 @@
+# 📘 Gesthub
+
+
+
+
+
+
+> **Version v2 (August 2026).** The code has been reorganized into a
+> layered architecture (`routes/`, `services/`, `models/`), with the
+> Announcements, Files, and Planning modules, an `audit_log` table, and a
+> suite of 34 automated tests (see `web/tests/`). This version serves as
+> the technical support for the RNCP 36463 (CDAN) certification file: see
+> `docs/` for the complete technical dossier
+> (`docs/Dossier_technique_GestHub_v2.docx`) and the actual evidence
+> (`docs/evidence/`: pytest/flake8/radon output, screenshots, architecture
+> diagrams).
+>
+> Quick start:
+> ```bash
+> cp web/.env.example web/.env # fill in the real values
+> docker compose up -d --build
+> ```
+> Run the tests (requires an accessible test MariaDB database, see
+> `web/tests/conftest.py`):
+> ```bash
+> cd web && pip install -r requirements.txt
+> TEST_DB_NAME=gesthub_test DB_USER=... DB_PASSWORD=... DB_HOST=... pytest tests/ -v
+> ```
+
+Built with the tools and technologies needed:
+
+
+
+
+
+
+
+
+
+
+
+
+## 🧱 Goal
+
+Build a multi-service website (extranet/intranet) with:
+- Centralized authentication via **Keycloak**
+- **Caddy** reverse proxy
+- **Flask** frontend/backend
+- Chat & task management via **Mattermost**
+- JSON-based announcement management with `/admin` permissions (to do)
+
+---
+
+## 🐳 Starting the project
+
+### 1. **Docker structure**
+
+The services are defined in `docker-compose.yml`:
+- `caddy`: Reverse proxy + automatic HTTPS
+- `flask`: Backend web application
+- `mariadb`: Database
+- `keycloak`: SSO + user management
+- `mattermost`: Chat and task management (Trello-like)
+
+Network used: `gesthub_gesthub`
+
+---
+
+## 🔐 Keycloak authentication
+
+### ✅ Steps:
+
+1. Create the **`Gesthub` realm**
+2. Add the clients (Flask and Mattermost)
+3. Enable `OpenID Connect`
+4. Configure the **Redirect URIs**
+ - Examples:
+ - Flask → `https://dashboard.ninolbt.com/login/callback`
+ - Mattermost → `https://mattermost.ninolbt.com/signup/openid/complete`
+
+5. For `/admin` users, use the **`/admin` group** in Keycloak.
+
+---
+
+## 🍓 Deployment on Raspberry Pi (ARM64)
+
+GestHub v2 is designed to run on a Raspberry Pi (4 or 5) with a **64-bit**
+OS (Raspberry Pi OS 64-bit / Ubuntu Server 64-bit). Check first:
+
+```bash
+uname -m # should print aarch64 (otherwise: reinstall the OS in 64-bit)
+docker --version # install via https://get.docker.com if missing
+```
+
+All the images in `docker-compose.yml` are official multi-arch images
+(Caddy, MariaDB, Postgres, Keycloak) — Docker automatically selects the
+arm64 variant on `pull`, nothing to change. The only exception is
+`mattermost/mattermost-team-edition`, which is only published for
+`linux/amd64` (no official ARM image to date — see
+[mattermost/mattermost#21979](https://github.com/mattermost/mattermost/issues/21979)).
+The compose file therefore uses an equivalent community build,
+`ngrie/mattermost-team-edition-arm`, at the same version — see the comment
+in `docker-compose.yml`. On an amd64 host (CI, dev machine), switch back to
+the official `mattermost/mattermost-team-edition:9.11` image.
+
+**RAM**: the full stack (Caddy + Flask + MariaDB + Keycloak + 2×Postgres +
+Mattermost) runs simultaneously — plan for a Pi with **4 GB of RAM
+minimum, 8 GB recommended**, and fast SSD/SD card storage (MariaDB/Postgres
+volumes are sensitive to the slow I/O of a regular SD card).
+
+---
+
+## 🌐 Caddy reverse proxy
+
+### 🛠️ `Caddyfile`:
+
+```caddyfile
+https://dashboard.ninolbt.com {
+ reverse_proxy flask:5000
+}
+
+https://keycloak.ninolbt.com {
+ reverse_proxy keycloak:8080
+}
+
+https://mattermost.ninolbt.com {
+ reverse_proxy mattermost:8065
+}
+```
+
+**Persistent volumes**:
+`caddy_data` and `caddy_config` mounted at `/data` and `/config`
+
+---
+
+## 🧩 Flask
+- Dashboard backend
+- Allows creating/editing/deleting announcements in JSON (being tested)
+- Accessible only to users with the `/admin` role (via token) (being tested)
+- Static asset loading fixed with Caddy
+
+---
+
+## 🗂️ Permissions management
+
+- Auth via Keycloak for Flask, Mattermost, Wekan
+- Group checks in Flask (`/admin`)
+- Correct redirects with Caddy HTTPS URLs
+
+---
+
+## 📌 Bugs and fixes
+
+- ⚠️ Incorrect Keycloak redirect → Fixed with the correct `redirect_uri`
+- ⚠️ Flask static assets → fixed via absolute HTTPS URL
+- ✅ Reverse proxy works with all services
+- ✅ HTTPS operational via Caddy with Let's Encrypt certificates
+
+---
+
+## 🚀 Startup
+
+```bash
+docker compose up --build -d
+```
+
+If needed:
+```bash
+docker compose logs -f [service]
+```
+
+---
+
+## 📤 Full export
+
+To make the project exportable:
+- Everything is containerized (Docker)
+- Keycloak config exported (JSON available in the `export_keycloak` folder)
+- `docker-compose.yml`, `Caddyfile`, files available in the repo
diff --git a/README.md b/README.md
index a92f2ac..d32f2be 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,30 @@
-# 📘 Gesthub
-
+# 📘 Gesthub
+
+
+
+
+
+
+> **Version v2 (août 2026).** Le code a été réorganisé en architecture par
+> couches (`routes/`, `services/`, `models/`), avec les modules Annonces,
+> Fichiers et Planning, une table `audit_log`, et une suite de 34 tests
+> automatisés (voir `web/tests/`). Cette version sert de support technique
+> au dossier de certification RNCP 36463 (CDAN) : voir `docs/` pour le
+> dossier technique complet (`docs/Dossier_technique_GestHub_v2.docx`) et
+> les preuves réelles (`docs/evidence/` : sorties pytest/flake8/radon,
+> captures d'écran, schémas d'architecture).
+>
+> Démarrage rapide :
+> ```bash
+> cp web/.env.example web/.env # renseigner les vraies valeurs
+> docker compose up -d --build
+> ```
+> Lancer les tests (nécessite une base MariaDB de test accessible, voir
+> `web/tests/conftest.py`) :
+> ```bash
+> cd web && pip install -r requirements.txt
+> TEST_DB_NAME=gesthub_test DB_USER=... DB_PASSWORD=... DB_HOST=... pytest tests/ -v
+> ```
Construit avec les outils et les technologies nécessaires :
@@ -56,6 +81,36 @@ Réseau utilisé : `gesthub_gesthub`
---
+## 🍓 Déploiement sur Raspberry Pi (ARM64)
+
+GestHub v2 est prévu pour tourner sur un Raspberry Pi (4 ou 5) avec un OS
+**64 bits** (Raspberry Pi OS 64-bit / Ubuntu Server 64-bit). Vérifier avant
+tout :
+
+```bash
+uname -m # doit afficher aarch64 (sinon : réinstaller l'OS en 64-bit)
+docker --version # installer via https://get.docker.com si absent
+```
+
+Toutes les images de `docker-compose.yml` sont des images officielles
+multi-arch (Caddy, MariaDB, Postgres, Keycloak) — Docker sélectionne
+automatiquement la variante arm64 au `pull`, rien à changer. Seule
+exception : `mattermost/mattermost-team-edition` n'est publiée qu'en
+`linux/amd64` (pas d'image ARM officielle à ce jour — voir
+[mattermost/mattermost#21979](https://github.com/mattermost/mattermost/issues/21979)).
+Le compose utilise donc un build communautaire équivalent,
+`ngrie/mattermost-team-edition-arm`, à la même version — voir le
+commentaire dans `docker-compose.yml`. Sur un hôte amd64 (CI, poste de dev),
+repasser à l'image officielle `mattermost/mattermost-team-edition:9.11`.
+
+**RAM** : la stack complète (Caddy + Flask + MariaDB + Keycloak + 2×Postgres
++ Mattermost) tourne simultanément — prévoir un Pi avec **4 Go de RAM
+minimum, 8 Go conseillés**, et un stockage sur SSD/carte SD rapide (les
+volumes MariaDB/Postgres sont sensibles aux I/O lentes d'une carte SD
+classique).
+
+---
+
## 🌐 Reverse Proxy Caddy
### 🛠️ `Caddyfile` :
diff --git a/docker-compose.yml b/docker-compose.yml
index 7df29b7..a26b349 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,102 +1,157 @@
+# =============================================================================
+# docker-compose.yml — Infrastructure as Code de GestHub (Bloc 4 - C5).
+# =============================================================================
+# Décrit l'état désiré de l'infrastructure complète : services, réseau interne,
+# volumes persistants. Reproductible sur n'importe quelle machine Linux avec
+# Docker installé, via `docker compose up -d` (voir procédure de déploiement,
+# Bloc 2 - C2 "avant/après").
+#
+# Seul Caddy expose des ports publics — tous les autres services communiquent
+# uniquement via le réseau Docker interne `gesthub-net` (mitigation du risque
+# "exposition des ports internes", tableau OWASP du dossier technique).
+# =============================================================================
+
services:
+
+ # --- Tier présentation : reverse proxy + HTTPS automatique ---
+ caddy:
+ image: caddy:2.8-alpine
+ restart: unless-stopped
+ ports:
+ - "80:80"
+ - "443:443"
+ volumes:
+ - ./Caddyfile:/etc/caddy/Caddyfile:ro
+ - caddy_data:/data
+ - caddy_config:/config
+ depends_on:
+ - flask
+ networks:
+ - gesthub-net
+
+ # --- Tier applicatif : application Flask (routes/services/models) ---
flask:
build: ./web
- environment:
- - DB_HOST=mariadb
- - DB_USER=flaskuser
- - DB_PASSWORD=flaskpass
- - DB_NAME=flaskdb
+ restart: unless-stopped
+ env_file:
+ - ./web/.env
depends_on:
- - mariadb
+ mariadb:
+ condition: service_healthy
volumes:
- - ./web:/app
- ports:
- - "5000:5000"
+ - uploads_data:/app/static/assets/uploads
+ expose:
+ - "5000"
networks:
- - gesthub
+ - gesthub-net
+ # --- Tier données : base applicative MariaDB ---
mariadb:
- image: mariadb:latest
+ image: mariadb:10.11
+ restart: unless-stopped
environment:
- - MYSQL_ROOT_PASSWORD=rootpass
- - MYSQL_DATABASE=flaskdb
+ - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-changeme}
+ - MYSQL_DATABASE=gesthub
- MYSQL_USER=flaskuser
- - MYSQL_PASSWORD=flaskpass
+ - MYSQL_PASSWORD=${DB_PASSWORD:-changeme}
volumes:
- mariadb_data:/var/lib/mysql
+ - ./web/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
+ healthcheck:
+ test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
+ interval: 10s
+ timeout: 5s
+ retries: 6
+ expose:
+ - "3306"
networks:
- - gesthub
-
- mattermost:
- image: mattermost/mattermost-team-edition:latest
- ports:
- - "8065:8065"
- environment:
- - MM_SQLSETTINGS_DRIVERNAME=postgres
- - MM_SQLSETTINGS_DATASOURCE=postgres://mmuser:mmuserpass@db:5432/mattermost?sslmode=disable
- - MM_SERVICESETTINGS_SITEURL=https://mattermost.ninolbt.com
-
- depends_on:
- - db
- volumes:
- - mattermost_data:/mattermost/data
- networks:
- - gesthub
-
- db:
- image: postgres:13
- environment:
- - POSTGRES_DB=mattermost
- - POSTGRES_USER=mmuser
- - POSTGRES_PASSWORD=mmuserpass
- volumes:
- - postgres_data:/var/lib/postgresql/data
- networks:
- - gesthub
+ - gesthub-net
+ # --- Tier identité : SSO Keycloak (realm gesthub) ---
keycloak:
- image: quay.io/keycloak/keycloak:22.0.5
+ image: quay.io/keycloak/keycloak:24.0.3
+ restart: unless-stopped
command:
- - start-dev
+ - start
- --hostname=keycloak.ninolbt.com
- - --hostname-strict=false
- - --hostname-strict-https=false
- - --proxy=edge
+ - --proxy-headers=xforwarded
environment:
- - KEYCLOAK_ADMIN=admin
- - KEYCLOAK_ADMIN_PASSWORD=admin
+ - KEYCLOAK_ADMIN=${KC_ADMIN_USER:-admin}
+ - KEYCLOAK_ADMIN_PASSWORD=${KC_ADMIN_PASSWORD:-changeme}
- KC_DB=postgres
- KC_DB_URL_HOST=keycloak-db
- KC_DB_URL_DATABASE=keycloak
- KC_DB_USERNAME=keycloak
- - KC_DB_PASSWORD=keycloakpass
- ports:
- - "8081:8080"
+ - KC_DB_PASSWORD=${KC_DB_PASSWORD:-changeme}
depends_on:
- keycloak-db
- volumes:
- - keycloak_data:/opt/keycloak/data
+ expose:
+ - "8080"
networks:
- - gesthub
+ - gesthub-net
keycloak-db:
- image: postgres:13
+ image: postgres:16-alpine
+ restart: unless-stopped
environment:
- POSTGRES_DB=keycloak
- POSTGRES_USER=keycloak
- - POSTGRES_PASSWORD=keycloakpass
+ - POSTGRES_PASSWORD=${KC_DB_PASSWORD:-changeme}
volumes:
- keycloakdb_data:/var/lib/postgresql/data
networks:
- - gesthub
+ - gesthub-net
+
+ # --- Chat & Kanban : réutilisation d'outils matures plutôt que
+ # réimplémentation (choix DRY documenté, Bloc 2 - C6) ---
+ #
+ # Déploiement cible = Raspberry Pi (ARM64/aarch64, OS 64 bits) : toutes les
+ # images de ce fichier sont multi-arch officielles (Caddy, MariaDB,
+ # Keycloak, Postgres) SAUF mattermost/mattermost-team-edition, qui n'existe
+ # qu'en linux/amd64 (voir issue mattermost/mattermost#21979 — aucune image
+ # ARM officielle à ce jour). On utilise donc un build communautaire arm64,
+ # à la même version (9.11), quasi drop-in (mêmes variables d'env
+ # MM_SQLSETTINGS_*, mêmes ports/volumes) :
+ # https://github.com/ngrie/mattermost-team-edition-arm-docker
+ # Sur un hôte amd64 (CI, dev sur PC), repasser à l'image officielle
+ # mattermost/mattermost-team-edition:9.11.
+ mattermost:
+ image: ngrie/mattermost-team-edition-arm:9.11
+ restart: unless-stopped
+ environment:
+ - MM_SQLSETTINGS_DRIVERNAME=postgres
+ - MM_SQLSETTINGS_DATASOURCE=postgres://mmuser:${MM_DB_PASSWORD:-changeme}@mattermost-db:5432/mattermost?sslmode=disable
+ - MM_SERVICESETTINGS_SITEURL=https://chat.ninolbt.com
+ depends_on:
+ - mattermost-db
+ volumes:
+ - mattermost_data:/mattermost/data
+ expose:
+ - "8065"
+ networks:
+ - gesthub-net
+
+ mattermost-db:
+ image: postgres:16-alpine
+ restart: unless-stopped
+ environment:
+ - POSTGRES_DB=mattermost
+ - POSTGRES_USER=mmuser
+ - POSTGRES_PASSWORD=${MM_DB_PASSWORD:-changeme}
+ volumes:
+ - mattermostdb_data:/var/lib/postgresql/data
+ networks:
+ - gesthub-net
networks:
- gesthub:
+ gesthub-net:
driver: bridge
volumes:
mariadb_data:
+ caddy_data:
+ caddy_config:
+ uploads_data:
+ keycloakdb_data:
mattermost_data:
- postgres_data:
- keycloak_data:
- keycloakdb_data:
\ No newline at end of file
+ mattermostdb_data:
diff --git a/docs/01_journal_de_bord.md b/docs/01_journal_de_bord.md
new file mode 100644
index 0000000..90de917
--- /dev/null
+++ b/docs/01_journal_de_bord.md
@@ -0,0 +1,133 @@
+# Partie 6 — Journal de bord
+
+Ce journal est reconstitué à partir de l'historique Git réel du dépôt GestHub
+(`git log --all`, 41 commits entre le 1er avril 2025 et le 16 décembre 2025).
+Il ne s'agit pas d'un journal tenu heure par heure pendant le développement,
+mais d'une reconstitution honnête après-coup : chaque entrée correspond à une
+ou plusieurs sessions de travail identifiables par leurs commits (même date),
+avec ce qui a été accompli, les difficultés rencontrées et, quand elles sont
+identifiables depuis les messages de commit et le code, les solutions
+appliquées.
+
+L'historique fait apparaître **11 sessions de travail identifiables** sur
+8 mois et demi (avril → décembre 2025), avec un pic d'activité mi-mai
+(mise en place du SSO et de Mattermost) et une session de refonte en
+décembre 2025 (passage à l'architecture "widgets" / Blocks). Le dossier RNCP
+narratif évoque "12 ydays" : ce chiffre inclut vraisemblablement des séances
+de travail sans commit (recherche, tests locaux non versionnés) non visibles
+dans Git — l'écart entre 11 et 12 est mineur et n'affecte pas la trajectoire
+générale du projet.
+
+---
+
+### Session 1 — 1er avril 2025 — Amorçage du projet
+**Commits :** `34ec9ad` Initial commit · `2f82260` add list of members · `f548db3` add readme
+
+Création du dépôt Git, rédaction d'un premier README et de la liste des
+membres du projet. GestHub démarre comme projet personnel pendant les ydays
+de B2, motivé par la dispersion des outils utilisés jusque-là (WhatsApp pour
+les annonces, clés USB pour les fichiers, agendas séparés).
+
+### Session 2 — 8 avril 2025 — Expérimentations dépôt / CI
+**Commits :** `094eeb7` add base · `b7901b9` testname · `ab9a11a` remove fileTest · `ec246dd` test strange name on github · `7a7ad1e` remove test
+
+Session d'expérimentation autour du comportement de GitHub (noms de fichiers,
+déclenchement d'actions) — plusieurs commits de test créés puis supprimés.
+Aucune fonctionnalité livrée, mais compréhension des contraintes de la
+plateforme avant de s'engager dans l'architecture définitive.
+
+### Session 3 — 14 avril 2025 — Squelette Docker
+**Commits :** `17c91e5` add base web + change a container · `6b3baed` add base asset · `e5a93eb` modif docker files
+
+Premher squelette de l'application web et premier conteneur Docker. Début
+de la structuration `web/` (assets, templates) qui sera conservée (et
+réorganisée en couches) jusqu'à la version actuelle.
+
+### Session 4 — 17 avril 2025 — Webhook
+**Commit :** `ffbb86a` testwebhook
+
+Test d'un webhook (déploiement automatisé) — piste explorée puis abandonnée
+au profit d'un déploiement manuel `docker compose up -d` documenté dans le
+README (plus simple à maintenir seul sur ce projet).
+
+### Session 5 — 13 mai 2025 — SSO Keycloak + Caddy fonctionnels
+**Commits :** `11900c2` add things · `5812193` repair things · `387b9be` modif things · `248b608` repair broken README · `cd4678c` add working sso + working Caddy + 2 scripts for hosts · `e1e8ed6` delete useless file · `9a2cdcc` debug + change hostname · `004c701` test tranfer to server
+
+**Session la plus significative techniquement à ce stade.** Mise en place du
+reverse proxy Caddy et du flux d'authentification OIDC avec Keycloak.
+**Difficulté rencontrée :** échecs de connexion liés au hostname (le message
+de commit "debug + change hostname" documente une itération de correction).
+**Démarche :** isolation du problème par test de transfert vers le serveur
+distant, ajustement du hostname Keycloak/Caddy jusqu'à obtention d'un flux
+SSO fonctionnel. Cette difficulté est la même famille de problème que celle
+détaillée dans le dossier technique (Bloc 3 — C5 Debugging) : Keycloak
+générant des URLs incohérentes avec le proxy tant que le hostname et les
+en-têtes de proxy ne sont pas alignés.
+
+### Session 6 — 16 mai 2025 — Consolidation
+**Commit :** `2e87761` add too many things
+
+Session de rattrapage regroupant plusieurs ajustements sur `app.py`,
+`docker-compose.yml` et les templates (message de commit peu descriptif —
+point d'amélioration identifié pour la suite : commits plus atomiques et
+mieux nommés, cf. Annexe A1).
+
+### Session 7 — 17 mai 2025 — Intégration Mattermost
+**Commits :** `6f69316` index html et css · `f248ba3` ajout boutons pour les documents · `0aaa312` add working mattermost + working sso for all · `5009865` fix website · `7050cd3` modif js · `8b13674` Test fontAwesome
+
+Page d'accueil restructurée (HTML/CSS) et intégration de **Mattermost**
+(chat + tableaux Kanban) via authentification SSO partagée avec Keycloak.
+Décision d'architecture actée ici : réutiliser Mattermost plutôt que
+développer un chat et un Kanban maison (cohérent avec le principe DRY —
+Bloc 2, C6 — réutilisation d'un outil mature plutôt que réinvention).
+
+### Session 8 — 18 mai 2025 — Session utilisateur et widgets
+**Commits :** `3a2f44c`/`8d6a9d0`/`a671314` test show username · `8d38a23`/`8b34ea8` fix logger · `45d905f` Add debug mode on flask · `df729c8` Update html with display name and add logout button · `4d68f48` test widget · `e8773a9`/`00c5bb7` add working widget for board
+
+Affichage du nom d'utilisateur (claim OIDC `preferred_username`) et bouton
+de déconnexion. Introduction des premiers "widgets" intégrant les tableaux
+Mattermost dans le dashboard — la préfiguration directe du système de
+`Block` généralisé en décembre 2025.
+
+### Session 9 — 18–19 mai 2025 — Version fonctionnelle bout en bout
+**Commits :** `9cab64f`/`d5ede3c` website fonctionnel / front=ok / back=ok / docker=ok · `697902d` export config keycloak · `3172c6e` merge · `9080df2`/`3b2fa00` modif README
+
+Premier jalon "de bout en bout" : front, back et Docker fonctionnels
+ensemble. Export de la configuration Keycloak (`export_keycloak/*.json`)
+pour permettre à un tiers de reconstituer le realm sans réaliser la
+configuration manuellement — élément de reproductibilité repris dans la
+Partie 3 (DevOps) du présent dossier.
+
+### Session 10 — 3 octobre 2025 — Hygiène du dépôt
+**Commit :** `4c7bb77` add gitignore
+
+Après une pause de près de 4 mois et demi (rentrée académique B3, spécialité
+Robotique & Systèmes Embarqués), ajout d'un `.gitignore`. Signale la reprise
+du projet et un souci d'hygiène avant de poursuivre le développement.
+
+### Session 11 — 16 décembre 2025 — Refonte du dashboard + widgets
+**Commits :** `e0883b0` test redesign gesthub · `1f2c67f` modif hostname keycloak (im stupid) · `45c8982` modif port keycloak (im stupid)
+
+Refonte majeure : remplacement du stockage JSON des annonces par un modèle
+`Block` en base MariaDB (via Flask-SQLAlchemy à l'époque), généralisation du
+tableau de bord en widgets déplaçables (drag & drop, SortableJS), API
+`/api/layout` pour la persistance de l'agencement. **Difficulté** : nouvelle
+itération de configuration Keycloak (hostname puis port), corrigée dans la
+foulée — les messages de commit ("im stupid") illustrent une auto-critique
+sur des erreurs de configuration simples mais bloquantes, un aléa classique
+en configuration d'infrastructure.
+
+---
+
+## Travaux réalisés pour ce dossier RNCP (janvier–août 2026, hors Git détaillé)
+
+En vue du présent dossier, le code a été **repris et complété** pour
+correspondre à l'état réellement décrit dans la Partie 2 : passage de
+Flask-SQLAlchemy à PyMySQL avec architecture en couches
+(routes/services/models, Repository Pattern), ajout des modules Annonces,
+Fichiers et Planning (jusque-là absents ou à l'état de squelette), ajout de
+la table et du service `audit_log`, écriture et exécution réelle d'une
+suite de 34 tests automatisés (pytest, contre une vraie base MariaDB),
+vérification réelle par flake8 et radon (0 violation, complexité moyenne
+A/1.70, taux de commentaires 13 %). Ce travail est documenté avec son propre
+horodatage dans le dépôt `gesthub-v2` livré en annexe de ce dossier.
diff --git a/docs/02_planning_previsionnel.md b/docs/02_planning_previsionnel.md
new file mode 100644
index 0000000..f24d700
--- /dev/null
+++ b/docs/02_planning_previsionnel.md
@@ -0,0 +1,34 @@
+# Section 16 — Planning prévisionnel par phases
+
+Le planning ci-dessous reconstitue la chronologie réelle du projet (source :
+historique Git, cf. Partie 6 — Journal de bord) et la complète par les
+travaux menés spécifiquement pour ce dossier RNCP (Phase 8). Il distingue
+la **disponibilité réelle** (temps partiel, ydays ponctuels, pauses entre
+sessions) de la **charge estimée** par phase.
+
+| Phase | Période réelle | Contenu | Charge estimée | Statut |
+|---|---|---|---|---|
+| 1 — Amorçage | 01/04/2025 | Dépôt Git, README, périmètre initial | 2 h | Terminé |
+| 2 — Squelette technique | 08 → 17/04/2025 | Expérimentations dépôt, premier conteneur Docker, test webhook | 6 h | Terminé |
+| 3 — Authentification SSO | 13/05/2025 | Keycloak + Caddy fonctionnels, debug hostname | 8 h | Terminé |
+| 4 — Intégration outils tiers | 16 → 19/05/2025 | Mattermost (chat/Kanban), affichage utilisateur, widgets, jalon "front=ok/back=ok/docker=ok" | 14 h | Terminé |
+| 5 — Pause / reprise | 20/05 → 03/10/2025 | Rentrée académique B3 (spécialité Robotique), hygiène dépôt (.gitignore) | — | — |
+| 6 — Refonte dashboard | 16/12/2025 | Migration vers modèle `Block` généralisé, drag & drop, API layout | 6 h | Terminé |
+| 7 — Consolidation architecture (préparation RNCP) | 01/2026 → 08/2026 | Découpage en couches routes/services/models, Repository Pattern, modules Annonces/Fichiers/Planning, audit_log, sécurité | 20 h | Terminé |
+| 8 — Qualité et preuve (dossier RNCP) | 08/2026 | Suite de tests pytest (34 cas), flake8/radon, rétro-documentation, dossier technique v2 | 12 h | Terminé |
+
+**Total estimé : ≈ 68 h** de travail effectif, réparties sur environ 8 mois
+et demi calendaires (avril 2025 → août 2026) — cohérent avec un
+développement en temps partiel, par sessions ponctuelles (ydays, weekends),
+en parallèle des cours et projets Ynov.
+
+## Méthode d'estimation
+
+L'estimation des charges par phase s'est faite de manière empirique :
+décomposition de chaque phase en tâches élémentaires, estimation en heures
+effectives (et non en jours calendaires, qui incluraient les temps morts
+entre sessions), puis application d'un coefficient correcteur de 1,5 pour
+tenir compte des imprévus et de la courbe d'apprentissage sur les
+technologies nouvelles (Keycloak, Docker, OIDC). Cette approche empirique et
+itérative — réestimer à chaque session plutôt qu'une fois pour tout le
+projet — est cohérente avec les pratiques Agile évoquées au Bloc 2 (C7, C8).
diff --git a/docs/03_cahier_des_charges.md b/docs/03_cahier_des_charges.md
new file mode 100644
index 0000000..981834f
--- /dev/null
+++ b/docs/03_cahier_des_charges.md
@@ -0,0 +1,185 @@
+# Sections 1 à 20 — Cahier des charges GestHub
+
+## Section 1 — Présentation du projet
+
+GestHub est une application web intranet/extranet multi-services destinée à
+centraliser les outils utilisés au quotidien pendant la formation à Ynov
+Bordeaux Campus (spécialité Robotique & Systèmes Embarqués) : annonces,
+partage de fichiers, planning, chat et Kanban, le tout derrière une
+authentification unique (SSO). Projet personnel auto-initié, démarré pendant
+les ydays de B2 (avril 2025) et poursuivi jusqu'à ce jour.
+
+## Section 2 — Contexte et origine
+
+Avant GestHub, la coordination reposait sur des outils disparates :
+WhatsApp pour les annonces (aucune permanence de l'information), clés USB
+ou liens temporaires pour les fichiers (aucun contrôle d'accès), agendas
+séparés pour le planning (aucune vision collective). Ce constat personnel a
+motivé la création d'un outil unique et centralisé.
+
+## Section 3 — Objectifs du projet
+
+- Centraliser les annonces, fichiers, planning et communication dans un seul
+ point d'entrée authentifié.
+- Garantir la persistance et la traçabilité de l'information (contrairement
+ aux outils volatils utilisés auparavant).
+- Construire une infrastructure reproductible (Infrastructure as Code) et
+ déployable en moins de 5 minutes sur une machine Linux vierge.
+- Servir de support d'apprentissage et de preuve de compétence pour le titre
+ RNCP 36463 (CDAN).
+
+## Section 4 — Analyse de l'existant
+
+| Besoin | Outil utilisé avant GestHub | Limite constatée |
+|---|---|---|
+| Annonces | WhatsApp | Pas d'historique structuré, pas de droits |
+| Fichiers | Clés USB / liens temporaires | Aucun contrôle d'accès, pas de traçabilité |
+| Planning | Agendas personnels | Pas de vision collective |
+| Chat | Discord / groupes épars | Fragmentation, pas de SSO |
+| Authentification | Comptes séparés par outil | Pas d'identité unique |
+
+## Section 5 — Parties prenantes et public visé
+
+- **Utilisateur standard** : consulte les annonces, le planning, télécharge
+ ses fichiers, participe au chat.
+- **Administrateur** (groupe Keycloak `/admin`) : publie/modifie/supprime
+ les annonces et événements, gère la disposition du tableau de bord,
+ supprime tout fichier.
+- **Public secondaire** : intervenants et évaluateurs des revues de projet
+ ydays, jury de certification RNCP.
+
+## Section 6 — Besoins par profil utilisateur
+
+| Profil | Besoin principal |
+|---|---|
+| Standard | Voir les annonces et le planning, gérer ses propres fichiers, accéder au chat |
+| Admin | Tout ce que fait un standard, + publier/gérer les annonces, événements et widgets |
+| (Super-admin envisagé) | Administration Keycloak elle-même (hors périmètre applicatif GestHub) |
+
+## Section 7 — Acteurs et rôles
+
+Les rôles sont portés par les **groupes Keycloak**, propagés dans le token
+OIDC (claim `groups`) et vérifiés à chaque requête sensible côté Flask
+(`services/auth_service.py`) — aucune notion de rôle n'est stockée
+localement en dehors de la session applicative.
+
+## Section 8 — Périmètre fonctionnel (10 modules)
+
+| # | Module | Statut | Technologies |
+|---|---|---|---|
+| 1 | Authentification SSO | Terminé | Keycloak, OIDC, Authlib |
+| 2 | Tableau de bord / widgets | Terminé | Flask, PyMySQL, SortableJS |
+| 3 | Annonces | Terminé | Flask, PyMySQL, MariaDB |
+| 4 | Partage de fichiers | Terminé | Flask, python-magic, PyMySQL |
+| 5 | Planning / événements | Terminé | Flask, PyMySQL |
+| 6 | Chat | Terminé (réutilisation) | Mattermost (iframe, SSO partagé) |
+| 7 | Kanban | Terminé (réutilisation) | Mattermost Boards (iframe) |
+| 8 | Gestion des droits | Terminé | Groupes Keycloak, décorateurs Flask |
+| 9 | Journalisation / audit | Terminé | Table `audit_log`, MariaDB |
+| 10 | Infrastructure | Terminé | Docker Compose, Caddy, TLS auto |
+
+## Section 9 — Exigences fonctionnelles (EXF)
+
+| ID | Exigence | Priorité | Critère d'acceptation |
+|---|---|---|---|
+| EXF-01 | Authentification SSO Keycloak | HAUTE | Connexion redirige vers Keycloak et revient avec une session valide |
+| EXF-02 | Rôles admin/standard | HAUTE | Une route admin renvoie 403 pour un utilisateur sans groupe `/admin` |
+| EXF-03 | CRUD Annonces | HAUTE | Un admin peut créer/modifier/supprimer une annonce, visible par tous |
+| EXF-04 | Épinglage des annonces | MOYENNE | Une annonce épinglée apparaît en tête de liste |
+| EXF-05 | Upload de fichier validé | HAUTE | Un fichier hors liste blanche (type/extension/taille) est rejeté (400) |
+| EXF-06 | Téléchargement sécurisé | HAUTE | Le téléchargement d'un fichier d'autrui par un non-admin renvoie 403 |
+| EXF-07 | Suppression de fichier | MOYENNE | Le propriétaire ou un admin peut supprimer, les autres sont refusés |
+| EXF-08 | Listing des fichiers | MOYENNE | La liste renvoie les métadonnées (nom, taille, date) sans exposer le chemin disque |
+| EXF-09 | Création d'événements | MOYENNE | Un admin peut créer un événement avec dates de début/fin cohérentes |
+| EXF-10 | Suppression d'événements | BASSE | Un admin peut supprimer un événement existant |
+| EXF-11 | Tableau de bord personnalisable | MOYENNE | Un admin peut ajouter/déplacer/supprimer un widget, persisté en base |
+| EXF-12 | Intégration Chat/Kanban | HAUTE | Les widgets iframe Mattermost s'affichent avec authentification partagée |
+
+## Section 10 — Architecture générale (1/2 — logique)
+
+Voir Bloc 1 — C3 (« Concevoir une architecture fiable ») pour le détail :
+architecture en 5 couches (présentation, contrôleur, service, accès
+données, infrastructure), séparation stricte des responsabilités.
+
+## Section 11 — Contraintes de sécurité
+
+Voir tableau OWASP Top 10 détaillé (Partie 4 — Grille de recette et
+risques). Contraintes principales : secrets hors code source (.env),
+requêtes paramétrées systématiques, réseau Docker interne, vérification de
+rôle à chaque route sensible, aucune fuite d'information dans les réponses
+d'erreur.
+
+## Section 12 — Architecture générale (2/2 — infrastructure)
+
+Architecture 3 tiers : **Caddy** (présentation/proxy, seul point
+d'exposition publique), **Flask** (métier/applicatif), **MariaDB +
+Keycloak** (données/identité). Chaque service est isolé dans un conteneur
+Docker sur le réseau interne `gesthub-net`.
+
+## Section 13 — Modèle de données
+
+5 tables en 3ème forme normale (voir `web/init.sql`) : `annonces`,
+`fichiers`, `evenements`, `blocks`, `audit_log`. Aucune duplication des
+données d'identité — la référence utilisateur se fait uniquement via le
+`sub` OIDC (chaîne opaque fournie par Keycloak).
+
+## Section 14 — Exigences non fonctionnelles (EXNF)
+
+| ID | Exigence | Vérification |
+|---|---|---|
+| EXNF-01 | Aucune injection SQL possible | Test TS-01 (requêtes paramétrées) |
+| EXNF-02 | Secrets hors code source | `.env` + `.gitignore`, `config.py` lit `os.environ` |
+| EXNF-03 | Disponibilité du service | `restart: unless-stopped`, healthcheck MariaDB |
+| EXNF-04 | Tenue en charge | Gunicorn 4 workers (formule 2×CPU+1) |
+| EXNF-05 | Accessibilité de base (RGAA) | Attributs alt/aria-label, HTML sémantique |
+| EXNF-06 | Traçabilité des actions sensibles | Table `audit_log`, alimentée par tous les services |
+| EXNF-07 | Portabilité | 100 % conteneurisé, `docker compose up -d` |
+| EXNF-08 | Maintenabilité | Architecture en couches, Repository Pattern |
+| EXNF-09 | Qualité du code | flake8 : 0 violation (preuve réelle, Annexe A1) |
+| EXNF-10 | Testabilité | 34 tests automatisés exécutés avec succès (Annexe A1 / Partie 4) |
+
+## Section 15 — Contraintes techniques et choix technologiques
+
+Python/Flask (maîtrisé depuis B1/B2 Ynov), MariaDB (SGBDR open source,
+compatible InnoDB/ACID), Keycloak (SSO open source, supporte OIDC standard),
+Docker (portabilité), Caddy (TLS automatique sans configuration manuelle de
+certificats — gain de temps pour un projet solo).
+
+## Section 16 — Planning prévisionnel
+
+Voir document dédié (Section 16 détaillée — Partie « Planning prévisionnel »).
+
+## Section 17 — Gestion des risques projet
+
+| Risque | Impact | Mitigation |
+|---|---|---|
+| Interruption longue du projet (indisponibilité) | Perte de contexte | Journal de bord, README à jour, code auto-documenté |
+| Dépendance à un service externe (Mattermost) | Fonctionnalité chat/Kanban indisponible | Service isolé, redémarrage indépendant (Docker) |
+| Erreur de configuration Keycloak | Blocage de l'authentification | Export de la configuration réalm (`export_keycloak/`) |
+| Régression lors d'une évolution | Fonctionnalité cassée silencieusement | Suite de tests automatisés (34 cas), exécutée avant chaque livraison |
+
+## Section 18 — Critères d'acceptation / recette
+
+Voir Partie 4 — Grille de recette (15 critères).
+
+## Section 19 — Glossaire
+
+- **OIDC** : OpenID Connect, protocole d'authentification basé sur OAuth2.
+- **SSO** : Single Sign-On, authentification unique pour plusieurs services.
+- **sub** : identifiant unique et opaque d'un utilisateur dans un token OIDC.
+- **3NF** : troisième forme normale (modélisation de bases de données
+ relationnelles).
+- **Repository Pattern** : patron de conception isolant l'accès aux données
+ derrière une interface stable.
+
+## Section 20 — Annexes
+
+Voir Annexe A1 (Qualité du code), Annexe A2 (Estimation de charge),
+Partie 3 (DevOps), Partie 4 (Plan de tests), Partie 5 (Rétro-documentation),
+Partie 6 (Journal de bord).
+
+## Illustrations — Schémas d'architecture (Sections 10, 12, 13)
+
+![IMG:evidence/schema_infrastructure.png|Schéma d'architecture d'infrastructure Docker Compose (Section 12)]
+
+![IMG:evidence/schema_couches_logiques.png|Schéma d'architecture logique en 5 couches (Section 10)]
diff --git a/docs/04_annexe_A1_qualite_code.md b/docs/04_annexe_A1_qualite_code.md
new file mode 100644
index 0000000..b890620
--- /dev/null
+++ b/docs/04_annexe_A1_qualite_code.md
@@ -0,0 +1,86 @@
+# Annexe A1 — Qualité du code
+
+## Charte de nommage
+
+| Élément | Convention | Exemple réel dans le code |
+|---|---|---|
+| Fonctions, variables | `snake_case` | `create_annonce`, `fichier_id` |
+| Classes / exceptions | `PascalCase` | `AnnonceNotFoundError`, `FileValidationError` |
+| Constantes | `UPPER_SNAKE_CASE` | `TITRE_MAX_LEN`, `ALLOWED_EXTENSIONS` |
+| Modules | `snake_case` court, un rôle par fichier | `annonce_service.py`, `audit_model.py` |
+
+## Séparation stricte des responsabilités
+
+Architecture en couches routes/ → services/ → models/ (voir Bloc 1 — C3/C4).
+Aucune route n'exécute de SQL ; aucun modèle ne connaît la logique HTTP.
+
+## Preuve réelle — exécution de flake8 (2026-08-20)
+
+Commande exécutée : `flake8 --max-line-length=110 --exclude=tests app.py
+config.py db.py models routes services init_db.py`
+
+```
+(sortie vide — 0 violation)
+```
+
+**0 violation PEP8/pyflakes** sur l'ensemble de la couche applicative
+(app.py, config.py, db.py, models/, routes/, services/, init_db.py — hors
+tests). Rapport complet : `docs/evidence/flake8_output.txt`.
+
+## Preuve réelle — exécution de radon (complexité et documentation)
+
+Commande : `radon cc models services routes app.py db.py -s -a`
+
+```
+84 blocs (classes, fonctions, méthodes) analysés.
+Complexité moyenne : A (1.70)
+```
+
+Une complexité cyclomatique moyenne de 1,70 (grade A) signifie que la
+quasi-totalité des fonctions n'a qu'un ou deux chemins d'exécution — cohérent
+avec la décomposition en étapes indépendantes (ex. upload de fichiers en
+8 fonctions, cf. Bloc 3 — C2).
+
+Commande : `radon raw models services routes app.py db.py -s`
+
+```
+Total :
+ LOC (lignes de code) : 984
+ Commentaires : 28 lignes ; commentaires + docstrings : 13 % du total (C+M % L)
+```
+
+**Taux de documentation interne mesuré : 13 %**, dans la fourchette cible
+8–15 % annoncée dans la Partie 2 (Bloc 1 — C2). Rapport complet :
+`docs/evidence/radon_cc_output.txt` et `docs/evidence/radon_raw_output.txt`.
+
+## Taux de réutilisation
+
+Les fonctions de validation et d'autorisation sont centralisées dans
+`services/auth_service.py` (décorateurs `require_login`/`require_admin`) et
+réutilisées par les 4 blueprints de routes (`annonces`, `fichiers`,
+`evenements`, `dashboard`). Les 5 modules de `models/` exposent tous la même
+interface (`insert`/`fetch_all`/`get`/`delete`), ce qui a permis d'écrire
+`services/` sans dupliquer la logique d'accès aux données.
+
+## Preuve réelle — suite de tests (Bloc 1 — C5, Bloc 3 — C7)
+
+Commande : `pytest tests/ -v` exécutée contre une véritable base MariaDB
+10.11 (et non des mocks) :
+
+```
+34 passed in 0.5s
+```
+
+Répartition : 22 tests unitaires (UT-01 à UT-12, avec variantes), 11 tests
+d'intégration (IT-01 à IT-10, avec variante), 6 tests de sécurité (TS-01 à
+TS-06). Détail complet dans `docs/evidence/pytest_output.txt` et dans la
+Partie 4 (Plan de tests) du présent dossier.
+
+Au cours de l'écriture de cette suite, **3 bugs réels ont été détectés puis
+corrigés** grâce aux tests (et non anticipés à l'écriture du code) :
+un paramètre par défaut Python évalué à l'import (`Config.UPLOAD_DIR`)
+qui ignorait les redirections de répertoire en test, un blueprint Flask
+redécoré à chaque création d'application de test, et une confusion entre
+"argument non fourni" et "argument explicitement `None`" dans
+`is_admin()`. Ce sont des exemples concrets de recherche systématique
+d'erreurs (Bloc 1 — C5) et de débogage (Bloc 3 — C5).
diff --git a/docs/05_annexe_A2_estimation_charge.md b/docs/05_annexe_A2_estimation_charge.md
new file mode 100644
index 0000000..4924b37
--- /dev/null
+++ b/docs/05_annexe_A2_estimation_charge.md
@@ -0,0 +1,27 @@
+# Annexe A2 — Estimation de charge
+
+## 4 scénarios d'usage
+
+| Scénario | Utilisateurs simultanés | Workers Gunicorn | RAM recommandée | vCPU |
+|---|---|---|---|---|
+| Développement local | 2–5 | 1 (serveur Flask dev) | 512 Mo | 1 |
+| Usage promo ydays | 10–30 | 4 (formule 2×2+1≈5, arrondi à 4 threads×2) | 1 Go | 2 |
+| Déploiement département | 50–100 | 9 (2×4+1) | 2 Go | 4 |
+| Pic événementiel | 100–200 | 9–17 selon CPU disponible | 4 Go | 4–8 |
+
+## Formule appliquée
+
+`workers = 2 × CPU + 1` (formule standard Gunicorn). Configuration retenue
+en production : 4 workers, classe `gthread`, 2 threads par worker
+(`web/Dockerfile` : `gunicorn --workers=4 --worker-class=gthread
+--threads=2 --bind=0.0.0.0:5000 app:app`), dimensionnée pour le scénario
+« usage promo ydays », le plus représentatif de l'usage réel actuel du
+projet.
+
+## Disponibilité
+
+`restart: unless-stopped` sur tous les services Docker (redémarrage
+automatique en cas de crash), healthcheck MariaDB avec 6 tentatives
+espacées de 10 s avant que Flask ne tente de se connecter (`depends_on:
+condition: service_healthy` dans `docker-compose.yml`), TLS/HTTP2 géré
+automatiquement par Caddy (renouvellement Let's Encrypt sans intervention).
diff --git a/docs/06_partie3_devops.md b/docs/06_partie3_devops.md
new file mode 100644
index 0000000..28d8ec0
--- /dev/null
+++ b/docs/06_partie3_devops.md
@@ -0,0 +1,66 @@
+# Partie 3 — DevOps (infrastructure, déploiement, exploitation)
+
+## Infrastructure as Code
+
+`docker-compose.yml` décrit l'état désiré de l'infrastructure complète :
+7 services (caddy, flask, mariadb, keycloak, keycloak-db, mattermost,
+mattermost-db), 1 réseau interne (`gesthub-net`), 7 volumes persistants.
+Seul `caddy` publie des ports (80/443) — tous les autres services
+communiquent uniquement via le réseau Docker interne (vérifié par le test
+automatisé TS-06, qui parse le fichier et échoue si un autre service
+déclare une clé `ports`).
+
+## Procédure de déploiement
+
+```bash
+git clone
+cd gesthub
+cp web/.env.example web/.env # renseigner les secrets réels
+docker compose up -d --build
+docker compose logs -f flask # vérifier le démarrage
+```
+
+**Avant / après** (réingénierie de processus, Bloc 2 — C2) :
+
+| | Avant (mai 2025) | Après |
+|---|---|---|
+| Déploiement | Copie manuelle SSH, redémarrage serveur | `docker compose up -d` |
+| Durée | 30 à 60 min | < 5 min |
+| Reproductibilité | Dépend de la mémoire de l'opérateur | Totale (fichier versionné) |
+
+## Procédure de mise en exploitation (checklist TD)
+
+| ID | Vérification | Méthode |
+|---|---|---|
+| TD-01 | Tous les conteneurs sont `Up` | `docker compose ps` |
+| TD-02 | Healthcheck MariaDB au vert | `docker compose ps` (colonne STATUS) |
+| TD-03 | HTTPS répond (redirection HTTP→HTTPS) | `curl -I https://dashboard.ninolbt.com` |
+| TD-04 | SSO Keycloak fonctionnel | Connexion manuelle de bout en bout |
+| TD-05 | Persistance des données après redémarrage | `docker compose restart mariadb` puis vérification des annonces existantes |
+| TD-06 | Sauvegarde exécutable | `scripts/backup.sh` en mode manuel, vérification de l'archive produite |
+
+**[⇒] Projection méthodologique — procédure ITIL complète.** Cette
+checklist couvre les principes ITIL de base (prérequis vérifiés,
+environnement de test avant production, liste de contrôle, rollback
+possible via `docker compose down` + restauration du dernier backup) mais
+n'a pas fait l'objet d'une signature formelle d'un « bon à intégrer » ni
+d'un PV de recette signé par un tiers, faute de contexte client réel sur ce
+projet personnel. En entreprise, ces deux documents formaliseraient
+l'accord du responsable d'exploitation et du Product Owner avant bascule en
+production.
+
+## Scripts d'exploitation
+
+- `scripts/backup.sh` : dump MariaDB (`mysqldump` + gzip) et archive des
+ fichiers uploadés, purge des sauvegardes de plus de 30 jours, prévu pour
+ être planifié via cron.
+- `web/init.sql` : création automatique du schéma au premier démarrage du
+ conteneur MariaDB (monté sur `/docker-entrypoint-initdb.d/`).
+
+## Réutilisation plutôt que réimplémentation (Bloc 2 — C6)
+
+Bibliothèques et outils intégrés tels quels plutôt que réécrits :
+**Authlib** (protocole OIDC), **PyMySQL** (connecteur MariaDB),
+**python-magic** (détection MIME), **Mattermost** (chat + Kanban complet,
+avec SSO partagé) — décision explicite de ne pas réinventer un outil de
+chat/Kanban alors qu'une solution mature et intégrable existe.
diff --git a/docs/07_partie4_plan_de_tests.md b/docs/07_partie4_plan_de_tests.md
new file mode 100644
index 0000000..e842f6b
--- /dev/null
+++ b/docs/07_partie4_plan_de_tests.md
@@ -0,0 +1,110 @@
+# Partie 4 — Plan de tests
+
+## Vue d'ensemble
+
+| Catégorie | Cas documentés | Automatisés (pytest) | Résultat réel (2026-08-20) |
+|---|---|---|---|
+| Tests unitaires (UT) | 17 | 17 | 17/17 réussis |
+| Tests d'intégration (IT) | 11 | 11 | 11/11 réussis |
+| Tests de sécurité (TS) | 6 | 6 | 6/6 réussis |
+| Tests de déploiement (TD) | 6 | Manuels (checklist) | Voir Partie 3 |
+| Tests fonctionnels (TF) | 4 | Manuels / scénarios | Voir ci-dessous |
+| **Total automatisé** | **34** | **34 exécutés** | **34/34 réussis** |
+
+Exécution réelle : `pytest tests/ -v` contre une base MariaDB 10.11 dédiée
+(`gesthub_test`), sortie complète dans `docs/evidence/pytest_output.txt`.
+Contrairement à des tests reposant sur des mocks de la couche SQL, cette
+suite détecte aussi les erreurs de requête (colonnes, contraintes) — c'est
+d'ailleurs ainsi qu'ont été trouvés les 3 bugs mentionnés en Annexe A1.
+
+## Détail des tests unitaires (UT)
+
+| ID | Fonction testée | Entrée | Résultat attendu |
+|---|---|---|---|
+| UT-01 | `annonce_service.create_annonce` | titre/contenu valides | Ligne créée, id retourné |
+| UT-02 | `create_annonce` | titre vide | `ValidationError` |
+| UT-03 | `create_annonce` | contenu vide/espaces | `ValidationError` |
+| UT-04 | `update_annonce` | annonce existante | Champs mis à jour |
+| UT-05 | `update_annonce` / `delete_annonce` | id inexistant | `AnnonceNotFoundError` |
+| UT-06 | `fichier_service.step2_check_size` | fichier > taille max / vide | `FileValidationError` |
+| UT-07 | `step4_check_extension` | extension interdite / autorisée | Erreur / extension renvoyée |
+| UT-08 | `step5_generate_safe_name` / `step1_check_presence` | deux appels / fichier absent | Noms uniques / erreur |
+| UT-09 à UT-12 | `auth_service.is_admin` | avec/sans groupe `/admin`, `None`, sans champ `groups` | `True`/`False` cohérents |
+| (suppl.) | `handle_upload` bout en bout | fichier PDF valide | Upload complet réussi |
+
+## Détail des tests d'intégration (IT) — client de test Flask
+
+| ID | Route | Cas | Résultat attendu |
+|---|---|---|---|
+| IT-01 | `GET /api/annonces` | sans session | 401 |
+| IT-02 | `GET /api/annonces` | utilisateur standard | 200, liste vide |
+| IT-03 | `POST /api/annonces` | utilisateur standard (non admin) | 403 |
+| IT-04 | `POST /api/annonces` puis `GET` | admin | 201, annonce visible |
+| IT-05 | `PUT /api/annonces/{id}` | admin | 200 |
+| IT-06 | `DELETE /api/annonces/{id}` | admin | 200 |
+| IT-07 | `GET /api/is_admin` | admin vs standard | reflète le groupe réel |
+| IT-08 | `POST /api/fichiers/upload` | sans fichier joint | 400 |
+| IT-09 | `POST /api/evenements` | date_fin < date_debut | 400 |
+| IT-10 | `GET /` | sans session | 302 vers `/login` |
+| (suppl.) | `POST /api/annonces` | titre manquant | 400 |
+
+## Détail des tests de sécurité (TS) — OWASP Top 10 2021
+
+| ID | Vecteur | Résultat attendu | Résultat réel |
+|---|---|---|---|
+| TS-01 | Injection SQL dans le titre d'une annonce | Payload stocké tel quel, table intacte | Réussi |
+| TS-02 | Accès à `/api/fichiers` et `/api/annonces` (POST) sans token | 401 | Réussi |
+| TS-03 | Cookie de session falsifié (non signé) | Traité comme non authentifié, 401 | Réussi |
+| TS-04 | Contenu `
-
-
-
-
+
+
+
+ {{ user.preferred_username if user else '' }}
+
+
+ Déconnexion
+
+
-
-