13 KiB
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/etdocs/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 ; merge natif sur CI verte |
| 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-webpublie 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, seulwakdo-webpublie un port. - Volumes :
wakdo_db_data(persistance MariaDB),wakdo_uploads(images produits) ; bind-mount./var/backupspour les dumps. wakdo-cronutiliseinit: true(tini comme PID 1 : dcron a besoin d'un init parent poursetpgidsur ses jobs).- Choix d'un subnet RFC 1918 explicite sur
wakdo_internalcote 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 :
wakdo-dbdemarre, devient healthy (scripthealthcheck.shde l'image).wakdo-migrate(service one-shot) applique, par le reseau et de maniere idempotente :db/migrations/*.sql— suivi dans la tableschema_migrations;db/seeds/*.sql— suivi dans la tableseeds_applied. Relancer ne rejoue que les fichiers en attente. Le runner :db/migrate-container.sh.
wakdo-appetwakdo-webattendent la completion dewakdo-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_appliedabsente), back-filler la table avant le premierup(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, matricerole_permissioneditable (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_idresolu par le PIN est ecrit dansaudit_log(RG-T14) dans la meme transaction que l'effet (RG-T08). Les operations de stock tracent viastock_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.
- login par compte (
- 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) selonlow_stock_pct/critical_stock_pct.stock_quantity` est signe (survente assumee).
- 100)
- Disponibilite produit (RG-T21) : un produit est commandable si
is_available = 1ET 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 doubleFakeDatabase, logique pure) + integration contre une vraie MariaDB (auto-skip siWAKDO_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). Fusion par auto-merge NATIF Forgejo (squash,merge_when_checks_succeed) des que les checks requis sont verts — pas de job de merge. - Branch protection :
devetmainproteges (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-Bysur les commits : la transparence vit dans le README etdocs/PROJECT_CONTEXT.mdsection 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.