diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..f479f49
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,25 @@
+name: Build
+
+# Verify that the project compiles on every PR and push.
+# One focused workflow per CI concern (build, test, lint) instead of a
+# monolithic ci.yml (see issue #49, closed as wontfix). Test compilation
+# and execution live in test.yml.
+
+on:
+ pull_request:
+ branches: [dev]
+ push:
+ branches: [dev, main]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: "21"
+ cache: maven
+ - name: Compile the project
+ run: ./mvnw -B -ntp compile
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..6805d49
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,35 @@
+name: Lint
+
+# Code quality check (Checkstyle, Google ruleset) on every PR and push.
+# Deliberately non-blocking on day one for an existing codebase:
+# - checkstyle:checkstyle only generates a report, it never fails on violations
+# - continue-on-error keeps even setup errors from gating merges
+# The report is uploaded as a workflow artifact for review.
+# Tighten later: switch the goal to checkstyle:check and set
+# failOnViolation to true in pom.xml, then drop continue-on-error.
+
+on:
+ pull_request:
+ branches: [dev]
+ push:
+ branches: [dev, main]
+
+jobs:
+ checkstyle:
+ runs-on: ubuntu-latest
+ continue-on-error: true
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: "21"
+ cache: maven
+ - name: Generate the Checkstyle report
+ run: ./mvnw -B -ntp checkstyle:checkstyle
+ - name: Upload the Checkstyle report
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: checkstyle-report
+ path: target/checkstyle-result.xml
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..4f26764
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,34 @@
+name: Tests
+
+# Run the test suite (Surefire, all *Test classes) on every PR and push.
+# Integration-style tests provision a real PostgreSQL via Testcontainers,
+# which uses the Docker daemon built into ubuntu-latest runners.
+# No .env file is needed: @ServiceConnection wires the datasource to the
+# container and the tests exclude the MongoDB autoconfiguration.
+
+on:
+ pull_request:
+ branches: [dev]
+ push:
+ branches: [dev, main]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: "21"
+ cache: maven
+ - name: Run the test suite
+ run: ./mvnw -B -ntp test
+ # JaCoCo is bound to the test phase, so the run above already
+ # produced the coverage report (HTML and XML).
+ - name: Upload the coverage report
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: jacoco-coverage-report
+ path: target/site/jacoco/
diff --git a/GLOSSAIRE.md b/GLOSSAIRE.md
new file mode 100644
index 0000000..00d7073
--- /dev/null
+++ b/GLOSSAIRE.md
@@ -0,0 +1,202 @@
+# Glossaire
+
+Définitions des termes métier et techniques utilisés dans le projet learn-dev.
+Pour les outils concrets et leurs versions, voir [docs/tech-stacks.md](docs/tech-stacks.md) ;
+pour l'articulation des composants, voir [ARCHITECTURE.md](ARCHITECTURE.md) ;
+pour la justification des décisions de conception, voir les [ADR](docs/adr/README.md).
+
+> [!NOTE]
+> 🇬🇧 English version: [GLOSSARY.md](GLOSSARY.md).
+> Les deux fichiers sont la traduction l'un de l'autre : toute entrée ajoutée,
+> modifiée ou supprimée dans l'un doit l'être aussi dans l'autre.
+
+## Termes métier
+
+- **Archive (archiver)** — Dépublier un cours ou une leçon pour qu'il ne soit
+ plus accessible aux étudiants, sans le supprimer.
+- **Course (cours)** — Une unité de contenu pédagogique appartenant à un
+ formateur ; contient des leçons.
+- **Deactivate (désactiver)** — Neutraliser un compte (formateur ou étudiant,
+ par exemple) pour qu'il ne puisse plus être utilisé, sans le supprimer.
+ Voir aussi *compte désactivé*.
+- **Drop a course (abandonner un cours)** — Le retrait d'un étudiant d'un cours
+ avant de l'avoir terminé.
+- **Enrollment (inscription)** — La relation qui lie un étudiant à un cours
+ qu'il a rejoint.
+- **Lesson (leçon)** — Un élément de contenu individuel au sein d'un cours.
+- **Role (rôle)** — Un ensemble nommé de permissions accordées à un
+ utilisateur. Les rôles fournis par défaut sont `STUDENT`, `INSTRUCTOR` et
+ `ADMIN` ; `SUPERADMIN` est prévu (voir l'issue #65).
+
+## Authentification et sécurité
+
+- **Authority (autorité)** — Dans Spring Security, une permission unitaire
+ détenue par un utilisateur authentifié. Les rôles sont représentés comme des
+ autorités préfixées par `ROLE_` (le rôle `ADMIN` devient l'autorité
+ `ROLE_ADMIN`).
+- **BCrypt** — Une fonction de hachage de mots de passe adaptative. Les mots de
+ passe sont stockés sous forme de hachés BCrypt, jamais en clair.
+- **CSRF (Cross-Site Request Forgery)** — Une attaque qui pousse le navigateur
+ d'un utilisateur connecté à soumettre une requête à son insu. Contrée par un
+ jeton par formulaire (injecté par Thymeleaf) et l'attribut de cookie
+ `SameSite`.
+- **Compte désactivé (disabled account)** — Un compte qui existe mais n'est pas
+ autorisé à s'authentifier (issu du drapeau `is_active = false`). À distinguer
+ d'un *compte verrouillé*.
+- **HttpOnly** — Un attribut de cookie qui masque le cookie au JavaScript côté
+ client, ce qui limite le vol de session via XSS.
+- **IDOR (Insecure Direct Object Reference)** — Une faille de contrôle d'accès
+ où un identifiant fourni par le client est utilisé sans vérification
+ d'autorisation. Les clés primaires UUID des utilisateurs limitent
+ l'énumération (voir [ADR-0003](docs/adr/0003-uuid-pk-for-users-bigint-elsewhere.md)).
+- **Compte verrouillé (locked account)** — Un compte temporairement empêché de
+ s'authentifier (par exemple après trop d'échecs de connexion), issu du
+ drapeau `is_locked`. À distinguer d'un *compte désactivé*.
+- **Principal** — L'entité actuellement authentifiée (en général l'utilisateur)
+ dans un contexte de sécurité.
+- **SameSite** — Un attribut de cookie qui contrôle l'envoi du cookie par le
+ navigateur sur les requêtes inter-sites. Positionné sur `Lax` ici comme
+ défense en profondeur contre le CSRF.
+- **Secure (cookie)** — Un attribut de cookie qui restreint le cookie au HTTPS.
+ Activé seulement lorsque l'application sera servie en TLS.
+- **Session (côté serveur)** — L'état d'authentification conservé sur le
+ serveur et référencé par un cookie de session (`JSESSIONID`), plutôt qu'un
+ jeton autoporteur (voir [ADR-0001](docs/adr/0001-use-server-side-sessions-over-jwt.md)).
+- **XSS (Cross-Site Scripting)** — L'injection de scripts malveillants dans des
+ pages vues par d'autres utilisateurs. Limitée par l'échappement automatique
+ de Thymeleaf et par `HttpOnly`.
+
+## Persistance et modélisation des données
+
+- **Changelog / Changeset (Liquibase)** — Un changelog est la liste ordonnée
+ des migrations ; un changeset est une migration atomique, identifiée par
+ `path::id::author`.
+- **ERD (Entity-Relationship Diagram)** — Un diagramme des entités et de leurs
+ relations (produit ici avec Mermaid).
+- **Hibernate** — L'implémentation JPA (ORM) utilisée pour faire correspondre
+ les entités Java aux tables.
+- **JPA (Jakarta Persistence API)** — L'API Java standard du mapping
+ objet-relationnel ; implémentée par Hibernate.
+- **JSESSIONID** — Le nom par défaut du cookie de session servlet.
+- **Liquibase** — L'outil de migration du schéma de base de données. Les
+ migrations sont des fichiers SQL formatés écrits à la main et appliqués au
+ démarrage (voir [ADR-0005](docs/adr/0005-handwrite-liquibase-migrations-over-mcd-ddl.md)).
+- **Merise** — Une méthode française de modélisation des données produisant
+ trois vues : MCD, MLD, MPD.
+- **MCD (Modèle Conceptuel de Données)** — Le modèle conceptuel ; les entités
+ et relations, indépendamment de toute base de données.
+- **MLD (Modèle Logique des Données)** — Le modèle logique ; le schéma
+ relationnel (tables, clés) dérivé du MCD.
+- **MPD (Modèle Physique des Données)** — Le modèle physique ; le schéma
+ concret tel qu'implémenté dans PostgreSQL.
+- **ORM (Object-Relational Mapping)** — La correspondance entre objets Java et
+ tables relationnelles ; assurée par Hibernate/JPA.
+- **UUID** — Un identifiant sur 128 bits utilisé comme clé primaire des
+ utilisateurs pour éviter l'énumération d'identifiants séquentiels.
+
+## Build, tests et outillage
+
+- **ADR (Architecture Decision Record)** — Un document court, numéroté et en
+ ajout seul qui capture une décision de conception et ses compromis, au
+ format MADR.
+- **Bean Validation** — Le standard Jakarta de déclaration de contraintes
+ (`@NotBlank`, `@Email`, `@Size`) sur les champs de formulaires/DTO,
+ appliquées avec `@Valid`.
+- **Checkstyle** — Un outil d'analyse statique qui vérifie les sources Java
+ contre un référentiel de style. Exécuté ici avec le référentiel Google
+ fourni (`google_checks.xml`) en mode rapport seul (voir
+ [ADR-0011](docs/adr/0011-start-ci-quality-checks-as-advisory-reports.md)).
+- **Code coverage (couverture de code)** — Le pourcentage de code exercé par
+ la suite de tests. Mesuré ici par JaCoCo ; rapporté, sans seuil imposé pour
+ l'instant.
+- **DTO (Data Transfer Object)** — Un objet qui transporte des données à
+ travers une frontière, volontairement distinct des entités. Un DTO `...Form`
+ porte un formulaire HTML.
+- **Failsafe** — Le plugin Maven qui exécute les tests d'intégration `*IT`
+ dans la phase `verify`. Ce projet ne l'utilise **pas** (voir
+ [ADR-0009](docs/adr/0009-run-tests-under-surefire-not-failsafe.md)).
+- **FIFO (tube nommé)** — Un fichier spécial qui transmet les données à la
+ lecture. Le `.env` du projet est une FIFO remplie par 1Password ; le
+ `source` du shell ne peut pas la lire (taille nulle au `stat`).
+- **HikariCP** — Le pool de connexions JDBC fourni avec Spring Boot.
+- **Test d'intégration (integration test)** — Un test qui démarre un contexte
+ Spring et exerce plusieurs couches ensemble (ici `@SpringBootTest` contre un
+ vrai conteneur Postgres).
+- **JaCoCo (Java Code Coverage)** — L'outil de couverture de code pour Java.
+ Son plugin Maven instrumente les tests (`prepare-agent`) et écrit un rapport
+ HTML/XML dans `target/site/jacoco/` pendant la phase `test` ; la CI le
+ publie comme artefact de workflow.
+- **Linter** — Un outil qui signale les problèmes de style et de qualité dans
+ le code source sans l'exécuter (analyse statique). Le linter du projet est
+ Checkstyle.
+- **Lombok** — Une bibliothèque qui génère le code répétitif (accesseurs,
+ constructeurs) à partir d'annotations, à la compilation.
+- **MADR (Markdown ADR)** — Le format léger de modèle d'ADR utilisé dans
+ `docs/adr/`.
+- **Maven Wrapper (`mvnw`)** — Un script de lancement versionné qui télécharge
+ et exécute la version de Maven épinglée par le projet, pour que les builds
+ ne dépendent pas d'un Maven installé localement (utilisé par la CI :
+ `./mvnw -B -ntp ...`).
+- **Slice test (test de tranche)** — Un test qui ne charge qu'une couche du
+ contexte (par exemple `@DataJpaTest` pour la couche de persistance).
+- **Smoke test (test de fumée)** — Un test minimal vérifiant que le contexte
+ de l'application démarre (`LearnDevApplicationTests`).
+- **Surefire** — Le plugin Maven qui exécute les tests `*Test`/`*Tests`
+ (unitaires et d'intégration) dans la phase `test`. Tous les tests du projet
+ passent par Surefire.
+- **Testcontainers** — Une bibliothèque qui démarre des conteneurs
+ Docker/Podman jetables pour les tests ; utilisée pour exécuter un vrai
+ PostgreSQL (voir [ADR-0006](docs/adr/0006-test-against-real-postgres-testcontainers.md)).
+- **Ryuk** — Le conteneur compagnon de Testcontainers qui nettoie les
+ ressources ; désactivé sous Podman dans ce projet.
+- **YAGNI (You Aren't Gonna Need It)** — Le principe de ne pas construire une
+ fonctionnalité avant d'en avoir réellement besoin (par exemple le report du
+ rôle `SUPERADMIN`).
+
+## Infrastructure et processus
+
+- **Advisory check (contrôle consultatif)** — Un contrôle de CI qui signale
+ les problèmes sans bloquer la fusion (goal en rapport seul et/ou
+ `continue-on-error`). Le lint et la couverture démarrent en mode consultatif
+ ici (voir [ADR-0011](docs/adr/0011-start-ci-quality-checks-as-advisory-reports.md)).
+- **CI (intégration continue)** — Construire et tester automatiquement chaque
+ changement (chaque PR et chaque push) pour détecter les régressions au plus
+ tôt. Mise en oeuvre avec GitHub Actions (issues #45 à #48).
+- **Docker Compose** — L'orchestration déclarative de plusieurs conteneurs ;
+ fait tourner ici Postgres et Mongo. Sur la machine de développement,
+ `docker` est Podman.
+- **GitButler** — L'outil de gestion de versions qui enveloppe Git ; utilisé
+ via la CLI `but` quand la branche courante est `gitbutler/workspace`.
+- **GitHub Actions** — Le service de CI de GitHub. Chaque workflow est un
+ fichier YAML sous `.github/workflows/` ; ce projet utilise un workflow ciblé
+ par préoccupation (voir [ADR-0010](docs/adr/0010-structure-ci-as-focused-workflows-per-concern.md)).
+- **Podman** — Un moteur de conteneurs sans démon, utilisé comme remplaçant de
+ `docker`.
+- **Runner (exécuteur)** — La machine qui exécute un job GitHub Actions
+ (`ubuntu-latest` ici) ; elle embarque un démon Docker, que Testcontainers
+ utilise directement.
+- **Profil Spring (Spring profile)** — Un jeu de configuration nommé (par
+ exemple `dev`) qui sélectionne des propriétés spécifiques et des contextes
+ Liquibase.
+- **Temurin** — La distribution Eclipse Adoptium de l'OpenJDK ; le build
+ Java 21 utilisé en local (via SDKMAN) et sur la CI (via
+ `actions/setup-java`).
+- **Thymeleaf** — Le moteur de templates HTML côté serveur. Son **dialecte**
+ Spring Security (espace de noms `sec:`) expose l'utilisateur authentifié aux
+ templates.
+- **Workflow (GitHub Actions)** — Un fichier YAML qui déclare quand
+ (déclencheurs) et comment (jobs, étapes) la CI s'exécute. Ce projet contient
+ `build.yml`, `test.yml`, `lint.yml` et `schema-drift.yml`.
+- **Workflow artifact (artefact de workflow)** — Un fichier ou dossier publié
+ depuis une exécution de workflow et téléchargeable depuis la page de
+ l'exécution (ici : le rapport XML de Checkstyle et les rapports JaCoCo).
+
+## Certification
+
+- **CCP (Certificat de Compétences Professionnelles)** — Un bloc de
+ compétences d'un Titre Professionnel français ; le DWWM comprend un CCP
+ front-end et un CCP back-end.
+- **DWWM (Développeur Web et Web Mobile)** — Le Titre Professionnel français
+ visé par ce projet de fin de formation.
+- **REAC (Référentiel Emploi Activités Compétences)** — Le référentiel
+ officiel de compétences qui définit ce que la certification évalue.
diff --git a/GLOSSARY.md b/GLOSSARY.md
index 6ed219e..96d7fe2 100644
--- a/GLOSSARY.md
+++ b/GLOSSARY.md
@@ -5,6 +5,11 @@ project. For the concrete tools and versions, see [docs/tech-stacks.md](docs/tec
for how the pieces fit together, see [ARCHITECTURE.md](ARCHITECTURE.md); for the
rationale behind design decisions, see the [ADRs](docs/adr/README.md).
+> [!NOTE]
+> 🇫🇷 French version: [GLOSSAIRE.md](GLOSSAIRE.md).
+> The two files are translations of each other: when you add, change, or
+> remove an entry in one, apply the same change to the other.
+
## Domain terms
- **Archive** — Unpublish a course or lesson so it is no longer available to
@@ -80,6 +85,11 @@ rationale behind design decisions, see the [ADRs](docs/adr/README.md).
capturing one design decision and its trade-offs, in MADR format.
- **Bean Validation** — The Jakarta standard for declaring constraints
(`@NotBlank`, `@Email`, `@Size`) on form/DTO fields, enforced with `@Valid`.
+- **Checkstyle** — A static-analysis tool that checks Java source against a
+ style ruleset. Runs here with the bundled Google ruleset (`google_checks.xml`)
+ in report-only mode (see [ADR-0011](docs/adr/0011-start-ci-quality-checks-as-advisory-reports.md)).
+- **Code coverage** — The percentage of code exercised by the test suite.
+ Measured here by JaCoCo; reported, not yet enforced as a threshold.
- **DTO (Data Transfer Object)** — An object carrying data across a boundary,
deliberately separate from entities. A `...Form` DTO backs an HTML form.
- **Failsafe** — The Maven plugin that runs `*IT` integration tests in the `verify`
@@ -89,9 +99,17 @@ rationale behind design decisions, see the [ADRs](docs/adr/README.md).
- **HikariCP** — The JDBC connection pool bundled with Spring Boot.
- **Integration test** — A test that boots a Spring context and exercises multiple
layers together (here `@SpringBootTest` against a real Postgres container).
+- **JaCoCo (Java Code Coverage)** — The code-coverage tool for Java. Its Maven
+ plugin instruments the tests (`prepare-agent`) and writes an HTML/XML report to
+ `target/site/jacoco/` during the `test` phase; CI uploads it as a workflow artifact.
+- **Linter** — A tool that flags style and quality issues in source code without
+ running it (static analysis). The project's linter is Checkstyle.
- **Lombok** — A library that generates boilerplate (getters, constructors) from
annotations at compile time.
- **MADR (Markdown ADR)** — The lightweight ADR template format used in `docs/adr/`.
+- **Maven Wrapper (`mvnw`)** — A committed launcher script that downloads and runs
+ the project's pinned Maven version, so builds do not depend on a locally
+ installed Maven (used by CI: `./mvnw -B -ntp ...`).
- **Slice test** — A test that loads only one layer of the context (for example
`@DataJpaTest` for the persistence layer).
- **Smoke test** — A minimal test that the application context starts at all
@@ -107,15 +125,33 @@ rationale behind design decisions, see the [ADRs](docs/adr/README.md).
## Infrastructure and process
+- **Advisory check** — A CI check that reports problems without blocking the
+ merge (report-only goal and/or `continue-on-error`). Linting and coverage
+ start advisory here (see [ADR-0011](docs/adr/0011-start-ci-quality-checks-as-advisory-reports.md)).
+- **CI (Continuous Integration)** — Automatically building and testing every
+ change (each PR and push) to catch regressions early. Implemented with
+ GitHub Actions (issues #45 to #48).
- **Docker Compose** — Declarative multi-container orchestration; here it runs
Postgres and Mongo. `docker` on the dev machine is Podman.
- **GitButler** — The version-control tool wrapping Git; used via the `but` CLI when
the current branch is `gitbutler/workspace`.
+- **GitHub Actions** — GitHub's CI service. Each workflow is a YAML file under
+ `.github/workflows/`; this project uses one focused workflow per concern
+ (see [ADR-0010](docs/adr/0010-structure-ci-as-focused-workflows-per-concern.md)).
- **Podman** — A daemonless container engine, used as the `docker` drop-in.
+- **Runner** — The machine that executes a GitHub Actions job (`ubuntu-latest`
+ here); it ships with a Docker daemon, which Testcontainers uses directly.
- **Spring profile** — A named configuration set (for example `dev`) selecting
profile-specific properties and Liquibase contexts.
+- **Temurin** — The Eclipse Adoptium distribution of the OpenJDK; the Java 21
+ build used locally (via SDKMAN) and on CI (via `actions/setup-java`).
- **Thymeleaf** — The server-side HTML template engine. Its Spring Security
**dialect** (`sec:` namespace) exposes the authenticated user to templates.
+- **Workflow (GitHub Actions)** — A YAML file declaring when (triggers) and how
+ (jobs, steps) CI runs. This project has `build.yml`, `test.yml`, `lint.yml`,
+ and `schema-drift.yml`.
+- **Workflow artifact** — A file or folder uploaded from a workflow run and
+ downloadable from the run page (here: the Checkstyle XML and JaCoCo reports).
## Certification
diff --git a/README.md b/README.md
index 512e996..4991878 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,11 @@
+
+
+[![build status][build-image]][build-url]
+[![test status][test-image]][test-url]
+[![lint status][lint-image]][lint-url]
+[![schema drift status][schema-drift-image]][schema-drift-url]
+[![github issues][github-issues-image]][github-issues-url]
+
# Learn-dev: An Interactive Programming Learning Platform
## Presentation
@@ -367,7 +375,8 @@ visit [this link](https://github.com/users/ebouchut/projects/7/views/3).
- [ARCHITECTURE.md](ARCHITECTURE.md) — how the pieces fit together (layers, request flow, authentication, data, testing).
- [docs/tech-stacks.md](docs/tech-stacks.md) — catalogue of tools, languages, and frameworks with versions used in the project.
-- [GLOSSARY.md](GLOSSARY.md) — definitions of the domain and technical terms used across the project.
+- [GLOSSARY.md](GLOSSARY.md) — definitions of the domain and technical terms used across the project
+ (🇫🇷 French version: [GLOSSAIRE.md](GLOSSAIRE.md)).
- [Architecture Decision Records](docs/adr/README.md) — A list of design decisions and their trade-offs.
@@ -430,3 +439,16 @@ See [LICENSE](LICENSE) for details.
- [Aubry Capitone](https://www.linkedin.com/in/a-capitone/)
- [Esteban Bare](https://www.linkedin.com/in/esteban-bare-337927284/),
- [REAC Developpeur Web et Web mobile](https://www.francecompetences.fr/recherche/rncp/37674/)
+
+
+
+[build-image]: https://github.com/ebouchut/learn-dev/actions/workflows/build.yml/badge.svg?branch=dev&event=push
+[build-url]: https://github.com/ebouchut/learn-dev/actions/workflows/build.yml
+[test-image]: https://github.com/ebouchut/learn-dev/actions/workflows/test.yml/badge.svg?branch=dev&event=push
+[test-url]: https://github.com/ebouchut/learn-dev/actions/workflows/test.yml
+[lint-image]: https://github.com/ebouchut/learn-dev/actions/workflows/lint.yml/badge.svg?branch=dev&event=push
+[lint-url]: https://github.com/ebouchut/learn-dev/actions/workflows/lint.yml
+[schema-drift-image]: https://github.com/ebouchut/learn-dev/actions/workflows/schema-drift.yml/badge.svg?branch=dev&event=push
+[schema-drift-url]: https://github.com/ebouchut/learn-dev/actions/workflows/schema-drift.yml
+[github-issues-image]: https://img.shields.io/github/issues/ebouchut/learn-dev
+[github-issues-url]: https://github.com/ebouchut/learn-dev/issues
diff --git a/docs/adr/0010-structure-ci-as-focused-workflows-per-concern.md b/docs/adr/0010-structure-ci-as-focused-workflows-per-concern.md
new file mode 100644
index 0000000..f2e6bb1
--- /dev/null
+++ b/docs/adr/0010-structure-ci-as-focused-workflows-per-concern.md
@@ -0,0 +1,57 @@
+# Structure CI as focused workflows per concern
+
+- Status: accepted
+- Date: 2026-07-06
+- Deciders: Eric Bouchut
+
+## Context and Problem Statement
+
+The project needs continuous integration on GitHub Actions (issues #45 to #48):
+build verification, test execution, linting, and coverage reporting.
+Should all of this live in a single `ci.yml` workflow, or in separate workflow
+files? Issue #49 (create a monolithic `ci.yml`) was closed as wontfix; this ADR
+records the reasoning so the layout survives the issue tracker.
+
+## Decision Drivers
+
+- Readability of CI results on a PR (one status per concern vs one opaque status)
+- Independent evolution (tighten linting without touching the test workflow)
+- Consistency with the existing `schema-drift.yml` workflow
+- Duplication cost (checkout and setup-java repeated per file)
+
+## Considered Options
+
+- A single monolithic `ci.yml` with multiple jobs
+- One focused workflow file per concern (`build.yml`, `test.yml`, `lint.yml`)
+
+## Decision Outcome
+
+Chosen: "One focused workflow file per concern", because each PR check maps to
+one small readable file, failures are identifiable at a glance from the check
+name, and each workflow can change (or be made blocking) independently.
+This matches the precedent set by `schema-drift.yml`.
+
+### Consequences
+
+- Good: PR checks read as Build / Tests / Lint / Schema drift; a red check
+ names its concern directly.
+- Good: The advisory lint workflow (ADR-0011) can later become blocking
+ without touching build or test.
+- Trade-off: The checkout + setup-java boilerplate is repeated in each file
+ (about 10 lines per workflow).
+- Trade-off: Shared changes (bumping the Java version) must be applied in
+ several files.
+
+## Pros and Cons of the Options
+
+### Monolithic ci.yml
+
+- 👍 One file to maintain; shared setup written once
+- 👎 One check status hides which concern failed; jobs still rerun setup each
+- 👎 Any change risks the whole pipeline; advisory and blocking concerns are
+ entangled
+
+### One workflow per concern
+
+- 👍 Self-describing PR checks; independent lifecycles; matches `schema-drift.yml`
+- 👎 Boilerplate repeated per file
diff --git a/docs/adr/0011-start-ci-quality-checks-as-advisory-reports.md b/docs/adr/0011-start-ci-quality-checks-as-advisory-reports.md
new file mode 100644
index 0000000..8f73c69
--- /dev/null
+++ b/docs/adr/0011-start-ci-quality-checks-as-advisory-reports.md
@@ -0,0 +1,61 @@
+# Start CI quality checks as advisory reports
+
+- Status: accepted
+- Date: 2026-07-06
+- Deciders: Eric Bouchut
+
+## Context and Problem Statement
+
+CI adds linting (#47) and coverage (#48) to a codebase that has never been
+linted and has no coverage baseline. Should these checks block merges from
+day one, and should coverage go to an external service? A blocking linter on
+an unlinted codebase fails every PR until a large style-only cleanup lands,
+which stalls feature work.
+
+## Decision Drivers
+
+- Do not block feature PRs on pre-existing style debt
+- Zero new config files and no external service accounts (solo project,
+ certification deadline)
+- Keep a clear path to tighten later
+
+## Considered Options
+
+- Blocking checks from day one (Checkstyle `check` goal, coverage threshold
+ via `jacoco:check`, Codecov)
+- Advisory reports first: Checkstyle report-only with the bundled
+ `google_checks.xml`, JaCoCo report bound to the test phase, both published
+ as workflow artifacts, no external service
+
+## Decision Outcome
+
+Chosen: "Advisory reports first", because it makes quality visible immediately
+without gating merges on historical debt, adds zero config files (bundled
+Google ruleset, plugin versions pinned in `pom.xml`: Checkstyle 3.6.0,
+JaCoCo 0.8.15), and keeps secrets and accounts out of scope. The lint job also
+sets `continue-on-error: true` so even setup errors stay advisory.
+
+### Consequences
+
+- Good: Every PR publishes a Checkstyle XML report and a JaCoCo HTML/XML
+ report as workflow artifacts.
+- Good: `mvn test` locally now produces `target/site/jacoco/` with no extra
+ flags.
+- Trade-off: Nothing enforces quality yet; violations can accumulate until
+ the gate is switched on.
+- Follow-up: To tighten, switch the lint job to `checkstyle:check` with
+ `failOnViolation=true`, drop `continue-on-error`, and add a `jacoco:check`
+ minimum threshold.
+
+## Pros and Cons of the Options
+
+### Blocking from day one
+
+- 👍 Debt cannot grow
+- 👎 Every PR is red until a big-bang style cleanup; discourages small PRs
+- 👎 Codecov adds an account, a token, and an external dependency
+
+### Advisory reports first
+
+- 👍 Immediate visibility, zero friction, easy to tighten incrementally
+- 👎 Relies on discipline until the gate exists
diff --git a/docs/adr/README.md b/docs/adr/README.md
index 9346b53..ca5a3fb 100644
--- a/docs/adr/README.md
+++ b/docs/adr/README.md
@@ -39,4 +39,6 @@ NNNN-short-title-in-kebab-case.md
| [0006](0006-test-against-real-postgres-testcontainers.md) | Test the persistence layer against a real PostgreSQL (Testcontainers), not H2 | accepted |
| [0007](0007-use-postgresql-over-mysql.md) | Use PostgreSQL as the relational database, not MySQL | accepted |
| [0008](0008-share-singleton-testcontainers-postgres.md) | Share one Testcontainers PostgreSQL as a static singleton, not @Container | accepted |
-| [0009](0009-run-tests-under-surefire-not-failsafe.md) | Run all tests under Surefire with the *Test suffix, not Failsafe/*IT | accepted |
\ No newline at end of file
+| [0009](0009-run-tests-under-surefire-not-failsafe.md) | Run all tests under Surefire with the *Test suffix, not Failsafe/*IT | accepted |
+| [0010](0010-structure-ci-as-focused-workflows-per-concern.md) | Structure CI as focused workflows per concern, not a monolithic ci.yml | accepted |
+| [0011](0011-start-ci-quality-checks-as-advisory-reports.md) | Start CI quality checks as advisory reports, gates come later | accepted |
\ No newline at end of file
diff --git a/docs/plans/2026-07-06-ci-test-pipeline.md b/docs/plans/2026-07-06-ci-test-pipeline.md
new file mode 100644
index 0000000..c0a36c9
--- /dev/null
+++ b/docs/plans/2026-07-06-ci-test-pipeline.md
@@ -0,0 +1,108 @@
+# CI Test Pipeline Implementation Plan
+
+> **Status: executed** on 2026-07-06, branch `ci-test-pipeline`.
+> This is the retrospective record of the plan as it was carried out.
+
+**Goal:** Give the project a GitHub Actions CI pipeline: verify the Maven
+build (#45), run the test suite (#46), report code quality (#47), and publish
+test coverage (#48) on every PR targeting `dev` and every push to `dev`/`main`.
+
+**Architecture:** One focused workflow file per concern instead of a
+monolithic `ci.yml` (issue #49 was closed as wontfix; rationale recorded in
+[ADR-0010](../adr/0010-structure-ci-as-focused-workflows-per-concern.md)).
+Quality checks start as advisory reports, not gates
+([ADR-0011](../adr/0011-start-ci-quality-checks-as-advisory-reports.md)).
+All jobs run on `ubuntu-latest` with `actions/setup-java@v4`
+(Temurin 21, `cache: maven`) and invoke the wrapper as `./mvnw -B -ntp`.
+
+**Key insight:** the tests need no `.env` on CI. Integration-style tests
+extend `AbstractPostgresIT`, which provisions a real PostgreSQL 17 via
+Testcontainers and wires the datasource with `@ServiceConnection`
+(ADR-0006, ADR-0008); Mongo autoconfiguration is excluded in tests. The
+Docker daemon built into GitHub's ubuntu runners is enough; the Podman
+environment variables from the `Makefile` are local-only.
+
+---
+
+## Version Control (GitButler)
+
+Same rules as every plan in this repository: the workspace is on
+`gitbutler/workspace`, so all commits go through `but commit` (never
+`git add`/`git commit`), and **nothing is pushed** by the agent; the user
+reviews, pushes, and opens the PR targeting `dev`.
+
+---
+
+## File Structure
+
+```
+.github/workflows/
+├── build.yml # Build: ./mvnw -B -ntp compile
+├── test.yml # Tests: ./mvnw -B -ntp test + JaCoCo artifact upload
+└── lint.yml # Lint: ./mvnw -B -ntp checkstyle:checkstyle (advisory)
+
+pom.xml # + maven-checkstyle-plugin 3.6.0 (report-only)
+ # + jacoco-maven-plugin 0.8.15 (prepare-agent, report@test)
+```
+
+---
+
+## Task 1: Maven build verification (#45)
+
+- [x] Create `.github/workflows/build.yml`: checkout@v4, setup-java
+ (Temurin 21, Maven cache), `./mvnw -B -ntp compile`
+- [x] Triggers: `pull_request: [dev]`, `push: [dev, main]`
+ (same as `schema-drift.yml`)
+- [x] Commit `ci(build): Verify the Maven build on every PR and push`
+ (Fixes #45) => 9430e67
+
+## Task 2: Test automation (#46)
+
+- [x] Create `.github/workflows/test.yml`: same setup, `./mvnw -B -ntp test`
+- [x] Document in the workflow why no `.env` or Podman wiring is needed
+- [x] Surefire only, all `*Test` classes; no Failsafe (ADR-0009)
+- [x] Commit `ci(test): Run the test suite on every PR and push`
+ (Fixes #46) => 3650ab8
+
+## Task 3: Code quality checks (#47)
+
+- [x] Add `maven-checkstyle-plugin` (pinned 3.6.0, not managed by the Boot
+ parent) to `pom.xml`: bundled `google_checks.xml`, `consoleOutput`,
+ `failOnViolation=false`, `failsOnError=false`, not bound to any phase
+ so `mvn test`/`mvn install` behavior is unchanged
+- [x] Create `.github/workflows/lint.yml`: `checkstyle:checkstyle`
+ (report goal, never fails on violations), `continue-on-error: true`
+ on the job, upload `target/checkstyle-result.xml` as an artifact
+- [x] Commit `ci(lint): Add a non-blocking Checkstyle report`
+ (Fixes #47) => bde8cc5
+
+## Task 4: Test coverage reports (#48)
+
+- [x] Add `jacoco-maven-plugin` (pinned 0.8.15) to `pom.xml`:
+ `prepare-agent` execution plus `report` bound to the `test` phase, so
+ every `mvn test` produces `target/site/jacoco/` (HTML + XML)
+- [x] Add an `actions/upload-artifact@v4` step (`if: always()`) to
+ `test.yml` publishing `target/site/jacoco/`
+- [x] No external coverage service (no Codecov): artifacts only
+- [x] Commit `ci(coverage): Add JaCoCo and upload the coverage report`
+ (Fixes #48) => 50f61d6
+
+## Verification (performed)
+
+- [x] All three workflow files parse as valid YAML
+ (actionlint unavailable; parsed with a YAML library)
+- [x] `make test` after the `pom.xml` changes:
+ `Tests run: 11, Failures: 0, Errors: 0, Skipped: 0` => BUILD SUCCESS
+- [x] `target/site/jacoco/index.html` and `jacoco.xml` produced by the
+ test phase
+- [x] `./mvnw -B -ntp checkstyle:checkstyle` => BUILD SUCCESS,
+ violations reported as warnings only
+- [x] `but status`: exactly 4 commits on `ci-test-pipeline`, no unrelated
+ changes swept in
+
+## Deferred (deliberate)
+
+- Coverage threshold gate (`jacoco:check`): add when a baseline exists
+- Blocking Checkstyle (`checkstyle:check`, `failOnViolation=true`, drop
+ `continue-on-error`): switch on once the existing violations are triaged
+- Path filters on the workflows: build and tests should always run for now
diff --git a/pom.xml b/pom.xml
index 74210db..bc26698 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,6 +55,8 @@
21
5.1.0
+ 3.6.0
+ 0.8.15
@@ -198,6 +200,53 @@
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${maven-checkstyle-plugin.version}
+
+ google_checks.xml
+ true
+ false
+ false
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco-maven-plugin.version}
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+ report
+ test
+
+ report
+
+
+
+