diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..79c2a15 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,269 @@ +# Architecture — Wakdo + +Vue d'ensemble technique du projet (borne de commande fast-food, certification RNCP 37805). +Point d'entree pour comprendre la stack, le decoupage et les choix de conception. + +- Scope metier, planning, mapping RNCP : `docs/PROJECT_CONTEXT.md`. +- Modelisation detaillee (entites, operations, regles) : `docs/merise/` (dictionary, mcd, mct, mlt). +- Decisions tracees : `docs/adr/` et `docs/journal/`. + +**Auteur : BYAN** (formalisation ; arbitrage et validation par l'auteur du projet). + +--- + +## 1. Vue d'ensemble + +Wakdo simule une borne de commande tactile de restauration rapide, avec back-office +d'administration, workflow cuisine et API REST interne. Deux surfaces applicatives : + +- **Borne (kiosk)** — front statique (HTML/CSS/JS vanilla ES6) servi par Apache, + consommant des donnees (JSON statique en P5, API DB-backed au swap P4). +- **Back-office + API** — application PHP rendue serveur (MVC maison) + endpoints + `/api/*`, derriere authentification et RBAC. + +Trois canaux de commande (`source`) : `kiosk`, `counter`, `drive`. Le cycle de vie +d'une commande et la machine a etats sont decrits dans `docs/merise/` (domaine +commande = phase **P4**, schema en base mais workflow applicatif a venir). + +--- + +## 2. Stack technique + +| Couche | Techno | Note | +|---|---|---| +| Langage back | PHP 8.3 | from scratch, sans framework | +| Autoloader | PSR-4 manuel (`spl_autoload_register`) | namespace `App\` -> `src/app/` | +| Base de donnees | MariaDB 11.4 | PDO, requetes preparees uniquement | +| Serveur web | Apache httpd 2.4 (Alpine) | reverse FastCGI -> PHP-FPM | +| Serveur app | PHP-FPM 8.3 (Alpine) | execute le code back-office + API | +| Front borne | HTML5 + CSS3 + JS ES6 (modules) | vanilla, sans build | +| Conteneurisation | Docker + docker compose v2 | `docker compose up` = stack complete | +| Tests PHP | PHPUnit 11 (`.phar`, sans Composer) | unit + integration DB | +| Tests front | node:test + jsdom | harnais kiosk (`tests/js/`) | +| Analyse statique | PHPStan niveau 6 (`.phar`) | | +| CI/CD | Forgejo Actions | secret-scan, lint, tests, auto-merge | +| Versioning | Git + Forgejo (`git.acadenice.com`, miroir GitHub) | Conventional Commits | + +Justifications (composer-less, from-scratch, etc.) : `docs/PROJECT_CONTEXT.md` section 6. + +--- + +## 3. Topologie de deploiement + +Cinq services Docker. Deux modes, par fichier compose : + +- **`docker-compose.yml`** (versionne) — standalone : tourne en local sans configuration. + `wakdo-web` publie un port hote (`${HTTP_PORT:-8080}`), reseau interne seul. +- **`docker-compose.prod.yml`** (gitignore, propre a chaque hote) — meme stack exposee + via un reverse proxy Traefik (reseau externe + labels TLS), sans port hote. + +``` + [ docker compose up -d ] + | + wakdo-db (MariaDB 11.4, healthcheck) + | service_healthy + v + wakdo-migrate (one-shot : migrations + seed idempotents, puis sort) + | service_completed_successfully + +---------------+----------------+ + v v + wakdo-app (PHP-FPM 8.3) wakdo-web (Apache) + ^ FastCGI :9000 <-----------/ publie ${HTTP_PORT}:80 (mode local) + | ou labels Traefik (mode prod) + | + wakdo-db <-- PDO + + wakdo-cron (dcron) : backup BDD + purges retention (RGPD) +``` + +- **Reseau** : `wakdo_internal` (bridge) isole les services ; aucun port hote en mode + prod (acces par le proxy). En mode local, seul `wakdo-web` publie un port. +- **Volumes** : `wakdo_db_data` (persistance MariaDB), `wakdo_uploads` (images produits) ; + bind-mount `./var/backups` pour les dumps. +- **`wakdo-cron`** utilise `init: true` (tini comme PID 1 : dcron a besoin d'un init + parent pour `setpgid` sur ses jobs). +- Choix d'un **subnet RFC 1918 explicite** sur `wakdo_internal` cote prod : l'hote + mutualise a un allocateur Docker sature ; le subnet evite l'echec d'allocation auto. + +Detail reseaux/volumes : `docs/PROJECT_CONTEXT.md` section 5. + +--- + +## 4. Demarrage : une commande (Cr 7.c.4) + +`docker compose up -d` amene une stack complete et utilisable : + +1. `wakdo-db` demarre, devient *healthy* (script `healthcheck.sh` de l'image). +2. `wakdo-migrate` (service one-shot) applique, par le reseau et de maniere + **idempotente** : + - `db/migrations/*.sql` — suivi dans la table `schema_migrations` ; + - `db/seeds/*.sql` — suivi dans la table `seeds_applied`. + Relancer ne rejoue que les fichiers en attente. Le runner : `db/migrate-container.sh`. +3. `wakdo-app` et `wakdo-web` attendent la **completion** de `wakdo-migrate` + (`depends_on: service_completed_successfully`) avant de servir. + +Le schema (DDL) et les donnees de reference (roles, permissions, catalogue, admin +bootstrap) sont donc en place sans etape manuelle. `db/migrate.sh` (hote, via +`docker exec`) reste disponible pour l'usage manuel / `--status`. + +> Migration de mecanisme : sur une base **deja seedee** avant l'introduction du suivi +> (`seeds_applied` absente), back-filler la table avant le premier `up` (sinon re-seed +> -> conflits d'unicite). Volume vierge : aucun souci. Cf. +> `docs/journal/2026-06-17--makefile-to-compose-migrate.md`. + +--- + +## 5. Structure du code + +Namespace `App\` -> `src/app/` (PSR-4 manuel). Front controller du vhost admin : +`src/public/admin/index.php` (Apache reecrit tout vers ce fichier ; le routeur voit +le `REQUEST_URI` intact). + +``` +src/app/ + Core/ Autoloader, Config, Database (PDO), Request, Response, Router + Auth/ AuthService, SessionManager, SessionGuard, Authorizer, PinVerifier, + PinThrottle, ThrottlePolicy, PasswordHasher, Csrf, PasswordResetService, + UserRepository, RoleRepository, UserDirectory, Mailer/LogMailer + Catalogue/ Category / Product / Menu / Ingredient / Stats Repository + Controllers/ Admin (base), Authenticated (base), Auth, PasswordReset, Profile, Me, + Dashboard, Stats, Category, Product, Menu, Ingredient, User, Role, + Health, Home + Views/ admin/* (pages back-office rendues serveur), auth/* (login/reset) +src/public/ + admin/ front controller + assets (CSS/JS) du back-office + borne/ front kiosk statique (index, categories, products, product, cart, + payment, confirmation) + assets JS modules + data JSON +``` + +Conventions transverses : controleurs non-`final` (seam de test : sous-classe injectant +des doubles via `db()` / `sessionManager()`) ; repository sur `DatabaseInterface` ; +chaque mutation passe par CSRF + validation serveur + allowlist (voir section 7). + +--- + +## 6. Flux d'une requete back-office + +``` +Navigateur --(HTTPS via Traefik | HTTP local)--> wakdo-web (Apache) + | vhost par ServerName (APP_HOST_KIOSK -> public/borne, APP_HOST_ADMIN -> public/admin) + | PHP -> FastCGI :9000 + v +wakdo-app (PHP-FPM) : src/public/admin/index.php + | Router (methode + chemin) -> [Controller, action] + v +Controller (extends AdminController) + | guard(permission) -> SessionGuard (RG-6/RG-T02 : session valide ?) + | + Authorizer::can(role, permission) (RG-T03, recharge DB) + | (mutation) Csrf::validate + validation serveur (RG-T18) + allowlist (RG-T16) + | (action sensible) PinVerifier + throttle, audit_log dans la meme transaction + v +Repository -> PDO (prepared) -> MariaDB + | + v +Vue rendue dans admin/layout (sorties echappees, RG-T15) | ou JSON pour /api/* +``` + +La borne (kiosk) est servie en statique par Apache ; ses pages consomment les donnees +via `fetch` (JSON statique en P5 ; bascule sur `/api/*` DB-backed au swap P4). + +--- + +## 7. Securite (security-by-design) + +Couche transverse, regles `RG-T*` definies dans `docs/merise/mlt.md`. Synthese : + +- **Authentification** : mot de passe hache **argon2id** (cout configurable, defauts + OWASP) ; sessions PHP avec regeneration d'ID au login, idle 4h + absolu 10h ; cookie + nomme `WAKDO_SID`. +- **RBAC** : `Authorizer::can(role_id, permission_code)` teste une **permission** (pas + un nom de role), rechargee depuis la base a chaque verification. 5 roles seedes, 23 + permissions figees, matrice `role_permission` editable (back-office, voir domaine 10). +- **PIN d'action sensible (RG-T13)** : les operations sensibles (annulation, prix/TVA, + suppressions, inventaire, gestion utilisateur, RBAC, effacement PII) exigent une + re-autorisation par PIN equipier (argon2id). L'`acting_user_id` resolu par le PIN est + ecrit dans `audit_log` (RG-T14) dans la **meme transaction** que l'effet (RG-T08). Les + operations de stock tracent via `stock_movement.user_id` (pas de double-journal). +- **Throttling** (backoff degressif, pas de verrou definitif) : + - login par compte (`user.failed_login_attempts` / `lockout_until`) + par IP + (`login_throttle`, RG-8/9) ; + - PIN d'action sensible (`pin_throttle`, RG-T22) — compteur **separe** du login, par + utilisateur agissant. +- **Entrees / sorties** : validation serveur bornee (RG-T18) ; allowlist d'affectation + de masse (RG-T16, empeche d'injecter `role_id`/`price_cents`/`is_active`...) ; toutes + les sorties HTML echappees (RG-T15) ; front borne CSP-safe (pas de script inline cote + code projet). +- **Conventions HTTP** : conflit d'etat (unicite, FK RESTRICT) -> **409** ; validation + qui echoue -> **422** ; CSRF/permission -> **403**. +- **RGPD** : anonymisation (mlt 10.5) qui conserve la ligne (tombstone) pour preserver + les FK et la trace d'audit, en vidant la PII ; purges de retention par `wakdo-cron` + (audit_log, throttle, sessions, commandes). +- **Isolation** : pas de port hote en mode prod (acces par le proxy) ; user applicatif + MariaDB en moindre privilege (DDL reserve au runner migrate root ; cf. + `db/init/10-scope-app-user.sh`). + +Threat model STRIDE + classification des donnees : `docs/PROJECT_CONTEXT.md` section 19. + +--- + +## 8. Modele de donnees + +22 tables (DDL `db/migrations/`), regroupees par domaine : + +- **Catalogue** : `category`, `product`, `menu`, `menu_slot`, `menu_slot_option`, + `ingredient`, `product_ingredient`, `allergen`, `ingredient_allergen`, `stock_movement`. +- **RBAC / comptes** : `user`, `role`, `permission`, `role_permission`, + `role_visible_source`. +- **Commande (P4, schema pret)** : `customer_order`, `order_item`, + `order_item_selection`, `order_item_modifier`. +- **Transverses** : `audit_log` (journal immuable), `login_throttle`, `pin_throttle`. + +Quelques derivations **calculees, non stockees** : + +- **Stock en pourcentage** (mcd 5.3) : `stock_pct = round(stock_quantity / stock_capacity + * 100)` ; 3 bandes (normal / alerte / critique) selon `low_stock_pct` / + `critical_stock_pct`. `stock_quantity` est signe (survente assumee). +- **Disponibilite produit (RG-T21)** : un produit est commandable si `is_available = 1` + ET chaque ingredient non retirable de sa composition est au-dessus de la bande + critique. Pas de cascade ni de colonne stockee. +- **`service_day`** : journee de service (coupure a 10:00) pour les agregations stats, + expression SQL non materialisee. + +MCD / MLD / dictionnaire : `docs/merise/`. + +--- + +## 9. Tests & qualite + +- **PHPUnit** (`.phar`, sans Composer) : tests *unit* (controleurs via double + `FakeDatabase`, logique pure) + *integration* contre une vraie MariaDB (auto-skip si + `WAKDO_DB_TESTS != 1`). Lancement : + `docker run --rm -v "$PWD":/app -w /app wakdo-wakdo-app php phpunit.phar -c phpunit.xml`. +- **Front borne** : `node --test` + jsdom (`tests/js/`). +- **PHPStan niveau 6** (`.phar`). +- **CI Forgejo Actions** (`.forgejo/workflows/ci.yml`) : `secret-scan` (gitleaks), + `php-lint`, `static-tests` (PHPStan + PHPUnit avec service MariaDB ephemere migre + + seede), `js-tests` (Node 20), `auto-merge` (squash sur label + CI verte). +- **Branch protection** : `dev` et `main` proteges (PR requise, force-push bloque, + checks requis). + +Pyramide visee : Unit > Integration > E2E. Les tests E2E navigateur (Playwright) sont +une initiative a venir. + +--- + +## 10. Methodologie & tracabilite + +Projet developpe avec l'appui de **BYAN** (agents IA custom, Merise Agile + 64 mantras) +et d'outils d'IA generative, conformement a l'autorisation du centre de formation. + +- Decisions d'architecture, scope et design : prises par l'auteur. +- Code, tests, doc : co-rediges et valides par l'auteur avant commit. +- **Pas de trailer `Co-Authored-By`** sur les commits : la transparence vit dans le + README et `docs/PROJECT_CONTEXT.md` section 17, pas dans les metadonnees git. +- Tracabilite : `docs/journal/` (retros par session et par feature). + +--- + +*Document vivant — mis a jour au fil de l'implementation. Source de verite scope/RNCP : +`docs/PROJECT_CONTEXT.md`.* diff --git a/docs/DEVELOPER.md b/docs/DEVELOPER.md new file mode 100644 index 0000000..78a144c --- /dev/null +++ b/docs/DEVELOPER.md @@ -0,0 +1,144 @@ +# Guide developpeur — Wakdo + +Comment lancer, tester et contribuer. Pour l'architecture (stack, services, modele, +securite), voir `docs/ARCHITECTURE.md`. Pour le scope et le mapping RNCP, +`docs/PROJECT_CONTEXT.md`. + +--- + +## 1. Prerequis + +- Docker Engine + docker compose v2 (https://docs.docker.com/engine/install/). +- Node 20+ (uniquement pour les tests front borne ; pas requis pour faire tourner l'app). + +Aucune installation de PHP / Composer / PHPUnit sur l'hote : tout passe par les +conteneurs et des `.phar` autonomes. + +--- + +## 2. Lancer en local + +```bash +cp .env.example .env +docker compose up -d +``` + +- Borne : http://kiosk.localhost:8080 +- Admin + API : http://admin.localhost:8080 + +`*.localhost` resout vers `127.0.0.1`. Changer le port via `HTTP_PORT` dans `.env`. +Le `.env.example` fonctionne tel quel en local (valeurs dev). Au boot, le service +`wakdo-migrate` applique migrations + seed (admin bootstrap inclus) avant que l'app +ne serve. + +Commandes utiles : + +```bash +docker compose ps # etat des services +docker compose logs -f wakdo-app # logs PHP-FPM +docker compose down # arret (volumes preserves) +docker compose down -v # arret + suppression des donnees +``` + +Deploiement derriere un reverse proxy : voir le `README.md` (section prod) + +`docker-compose.prod.yml` (gitignore, propre a l'hote). + +--- + +## 3. Base de donnees : migrations & seed + +- `db/migrations/*.sql` (DDL) et `db/seeds/*.sql` (donnees de reference) sont appliques + de maniere **idempotente** par `wakdo-migrate` (suivi `schema_migrations` / + `seeds_applied`). Relancer `docker compose up` ne rejoue que les fichiers en attente. +- **Ajouter une migration** : creer `db/migrations/000N_description.sql` (ordre + lexicographique). Appliquee au prochain `docker compose up`, ou a la main : + +```bash +bash db/migrate.sh # applique les migrations en attente (hote) +bash db/migrate.sh --status # liste l'etat sans rien appliquer +``` + +- Idem pour un seed : `db/seeds/000N_description.sql`. + +--- + +## 4. Tests & analyse statique + +Les tests PHP tournent dans l'image applicative (PHPUnit `.phar`). La stack doit etre +demarree pour les tests d'integration (ils ciblent le service `wakdo-db`). + +```bash +# Tests unitaires (sans base) +docker run --rm -v "$PWD":/app -w /app wakdo-wakdo-app \ + php phpunit.phar -c phpunit.xml --testsuite unit + +# Tests d'integration (vraie MariaDB ; auto-skip si WAKDO_DB_TESTS != 1) +docker run --rm --network wakdo_wakdo_internal --env-file .env -e WAKDO_DB_TESTS=1 \ + -v "$PWD":/app -w /app wakdo-wakdo-app php phpunit.phar -c phpunit.xml + +# Analyse statique PHPStan niveau 6 +docker run --rm -v "$PWD":/app -w /app wakdo-wakdo-app \ + php -d memory_limit=512M phpstan.phar analyse -c phpstan.neon --no-progress +``` + +Tests front borne (Node + jsdom) : + +```bash +npm install # une fois (devDependency jsdom) +npm run test:js # node --test tests/js/ +``` + +> Le nom de reseau `wakdo_wakdo_internal` et l'image `wakdo-wakdo-app` derivent du +> nom de projet compose (`name: wakdo`). Les `.phar` (phpunit, phpstan) sont +> gitignores ; les retelecharger si absents (voir `docs/journal/`). + +--- + +## 5. Conventions de code + +- **PSR-4 manuel** : namespace `App\` -> `src/app/`. Pas de framework. +- **Controleurs** : non-`final` (seam de test ; les tests sous-classent et injectent + des doubles via `db()` / `sessionManager()`). Heritent de `AdminController` + (back-office) ou `AuthenticatedController`. +- **Acces donnees** : un repository par entite, dependant de `DatabaseInterface` + (PDO en prod, `FakeDatabase` en test). Requetes preparees uniquement. +- **Mutations** : CSRF (`Csrf::validate`) + validation serveur bornee (RG-T18) + + allowlist de colonnes (RG-T16). Sorties HTML echappees (RG-T15). +- **Actions sensibles** : PIN equipier (`PinVerifier`) + `audit_log` dans la meme + transaction ; throttle PIN (`PinThrottle`). Voir `docs/ARCHITECTURE.md` section 7. +- **Statuts HTTP** : conflit -> 409 ; validation -> 422 ; CSRF/permission -> 403. +- **Pas d'emoji** dans le code, les commits, les specs (Mantra IA-23). + +Detail par entite : `docs/merise/` et `docs/domaines/` (a venir). + +--- + +## 6. Git & CI + +- **Conventional Commits** (anglais) : `type(scope): description` — types `feat`, `fix`, + `docs`, `refactor`, `test`, `chore`, `ci`, `db`, `perf`, `style`. +- **Branches** depuis `dev` : `feat/*`, `fix/*`, `docs/*`, `chore/*`, `ci/*`, `db/*`, + `refactor/*`, `test/*`. Merge vers `dev` par **PR squashee**. Periodiquement + `dev -> main` avec tag semver. +- **Auto-merge** : poser le label `auto-merge` sur la PR -> fusion automatique des que + la CI Forgejo est verte (secret-scan, php-lint, static-tests, js-tests). + Script : `scripts/forgejo-pr-automerge.sh`. +- **Pas de trailer `Co-Authored-By`** : la transparence sur l'usage de l'IA vit dans le + `README.md` et `docs/PROJECT_CONTEXT.md` section 17. + +--- + +## 7. Ou trouver quoi + +| Besoin | Emplacement | +|---|---| +| Architecture, stack, securite, modele | `docs/ARCHITECTURE.md` | +| Scope metier, planning, mapping RNCP | `docs/PROJECT_CONTEXT.md` | +| Modelisation Merise (dictionnaire, MCD/MCT/MLT, regles RG-T*) | `docs/merise/` | +| Decisions d'architecture (le pourquoi) | `docs/adr/` | +| Retros par session / feature | `docs/journal/` | +| Methodologie agents | `.claude/CLAUDE.md` + `.claude/rules/` | + +--- + +*Document vivant — mis a jour au fil de l'implementation.*