docs : update project documentation and frontend submodule pointer
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
36
CLAUDE.md
36
CLAUDE.md
@@ -49,13 +49,14 @@ Inventory/ # Backend Symfony (repo principal)
|
|||||||
# Docker
|
# Docker
|
||||||
make start # Démarrer les containers
|
make start # Démarrer les containers
|
||||||
make stop # Arrêter
|
make stop # Arrêter
|
||||||
make shell # Shell dans le container PHP
|
make shell # Shell interactif (nécessite un TTY)
|
||||||
make install # Install complet (composer + npm + build)
|
make install # Install complet (composer + npm + build)
|
||||||
|
|
||||||
# Backend
|
# Backend
|
||||||
make test # PHPUnit
|
make test # PHPUnit (tous les tests)
|
||||||
docker compose exec php vendor/bin/php-cs-fixer fix # Linter PHP
|
make test FILES=tests/Api/Entity/MachineTest.php # Un test spécifique
|
||||||
docker compose exec php php bin/console doctrine:migrations:migrate
|
make php-cs-fixer-allow-risky # Linter PHP (cs-fixer)
|
||||||
|
docker exec -u www-data php-inventory-apache php bin/console doctrine:migrations:migrate
|
||||||
|
|
||||||
# Frontend (dans Inventory_frontend/)
|
# Frontend (dans Inventory_frontend/)
|
||||||
npm run dev # Dev server (port 3001)
|
npm run dev # Dev server (port 3001)
|
||||||
@@ -138,13 +139,18 @@ ROLE_ADMIN → ROLE_GESTIONNAIRE → ROLE_VIEWER → ROLE_USER
|
|||||||
|
|
||||||
## Règles Importantes
|
## Règles Importantes
|
||||||
|
|
||||||
|
### CLAUDE.md — Maintenance obligatoire
|
||||||
|
- **Toujours consulter** ce fichier en début de conversation pour respecter les conventions
|
||||||
|
- **Mettre à jour** ce fichier quand une nouvelle convention, pattern ou décision architecturale est établie
|
||||||
|
- **Utiliser comme source de vérité** pour les commandes, patterns et règles du projet
|
||||||
|
|
||||||
### Toujours faire AVANT de modifier du code
|
### Toujours faire AVANT de modifier du code
|
||||||
1. **Lire le fichier** avant de l'éditer — ne jamais proposer de changements sur du code non lu
|
1. **Lire le fichier** avant de l'éditer — ne jamais proposer de changements sur du code non lu
|
||||||
2. **Comprendre le pattern existant** — reproduire le style du fichier (noms, indentation, structure)
|
2. **Comprendre le pattern existant** — reproduire le style du fichier (noms, indentation, structure)
|
||||||
3. **Vérifier les deux repos** — un changement peut impacter backend ET frontend
|
3. **Vérifier les deux repos** — un changement peut impacter backend ET frontend
|
||||||
|
|
||||||
### Après chaque modification
|
### Après chaque modification
|
||||||
1. Backend PHP : `docker compose exec php vendor/bin/php-cs-fixer fix`
|
1. Backend PHP : `make php-cs-fixer-allow-risky`
|
||||||
2. Frontend : `npm run lint:fix` puis `npx nuxi typecheck` si fichiers TS modifiés
|
2. Frontend : `npm run lint:fix` puis `npx nuxi typecheck` si fichiers TS modifiés
|
||||||
|
|
||||||
### Ne jamais faire
|
### Ne jamais faire
|
||||||
@@ -161,6 +167,26 @@ Quand les branches `master` et `develop` divergent sur l'un des deux repos, **to
|
|||||||
- Main repo : `git checkout master && git merge develop && git push`
|
- Main repo : `git checkout master && git merge develop && git push`
|
||||||
- Frontend : `git checkout develop && git merge master && git push` (ou l'inverse selon le cas)
|
- Frontend : `git checkout develop && git merge master && git push` (ou l'inverse selon le cas)
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
### Stack de test
|
||||||
|
- **PHPUnit 12** + **API Platform Test** (`ApiTestCase`)
|
||||||
|
- **DAMA DoctrineTestBundle** — wrappe chaque test dans une transaction avec rollback automatique (pas de TRUNCATE)
|
||||||
|
- Base de test : même PG, env `test`
|
||||||
|
|
||||||
|
### Commandes
|
||||||
|
```bash
|
||||||
|
make test # Tous les tests
|
||||||
|
make test FILES=tests/Api/Entity/MachineTest.php # Un fichier spécifique
|
||||||
|
make test-setup # Créer/mettre à jour le schéma test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern de test
|
||||||
|
- Hériter de `AbstractApiTestCase` (helpers auth + factories)
|
||||||
|
- Ne PAS faire de TRUNCATE/cleanup dans tearDown — DAMA s'en occupe par rollback
|
||||||
|
- Factories : `createProfile()`, `createMachine()`, `createSite()`, etc.
|
||||||
|
- Auth : `createViewerClient()`, `createGestionnaireClient()`, `createAdminClient()`
|
||||||
|
|
||||||
## URLs Locales
|
## URLs Locales
|
||||||
- API Symfony : `http://localhost:8081/api`
|
- API Symfony : `http://localhost:8081/api`
|
||||||
- Nuxt dev : `http://localhost:3001`
|
- Nuxt dev : `http://localhost:3001`
|
||||||
|
|||||||
214
DEPLOY.md
214
DEPLOY.md
@@ -1,17 +1,29 @@
|
|||||||
# Inventory - Guide de Déploiement & Release
|
# Inventory — Guide de Déploiement
|
||||||
|
|
||||||
## Architecture
|
Guide pour déployer l'application sur un serveur de production.
|
||||||
|
|
||||||
|
## Architecture de production
|
||||||
|
|
||||||
```
|
```
|
||||||
inventory.malio-dev.fr/ → Frontend Nuxt (statique)
|
inventory.malio-dev.fr/ → Frontend Nuxt (fichiers statiques servis par Nginx)
|
||||||
inventory.malio-dev.fr/api → Backend Symfony (PHP-FPM)
|
inventory.malio-dev.fr/api → Backend Symfony (PHP-FPM derrière Nginx)
|
||||||
```
|
```
|
||||||
|
|
||||||
| Composant | Technologie | Emplacement serveur |
|
| Composant | Technologie | Emplacement serveur |
|
||||||
|-----------|-------------|---------------------|
|
|-----------|-------------|---------------------|
|
||||||
| Backend | Symfony 8 + API Platform | `/var/www/Inventory/` |
|
| Backend | Symfony 8 + API Platform | `/var/www/Inventory/` |
|
||||||
| Frontend | Nuxt 4 (statique) | `/var/www/Inventory/Inventory_frontend/.output/public/` |
|
| Frontend | Nuxt 4 (site statique) | `/var/www/Inventory/Inventory_frontend/.output/public/` |
|
||||||
| Base de données | PostgreSQL 16 | `inventory` |
|
| Base de données | PostgreSQL 16 | Base `inventory` |
|
||||||
|
|
||||||
|
### Schéma simplifié
|
||||||
|
|
||||||
|
```
|
||||||
|
Navigateur
|
||||||
|
↓ HTTPS
|
||||||
|
Nginx (reverse proxy)
|
||||||
|
├── /api/* → PHP-FPM (Symfony) → PostgreSQL
|
||||||
|
└── /* → Fichiers statiques (Nuxt build)
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -24,7 +36,8 @@ inventory.malio-dev.fr/api → Backend Symfony (PHP-FPM)
|
|||||||
- **PostgreSQL** : 16
|
- **PostgreSQL** : 16
|
||||||
- **Composer**
|
- **Composer**
|
||||||
|
|
||||||
Vérifier :
|
### Vérification des prérequis
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
php -v # PHP 8.4+
|
php -v # PHP 8.4+
|
||||||
php -m | grep -E 'pgsql|intl|zip|gd|mbstring'
|
php -m | grep -E 'pgsql|intl|zip|gd|mbstring'
|
||||||
@@ -73,10 +86,10 @@ psql -U ferme_user -h 127.0.0.1 -d inventory -f /tmp/backup_v1.0.0_clean.sql
|
|||||||
```bash
|
```bash
|
||||||
cd /var/www/Inventory
|
cd /var/www/Inventory
|
||||||
|
|
||||||
# Installer les dépendances
|
# Installer les dépendances (sans les outils de dev)
|
||||||
composer install --no-dev --optimize-autoloader
|
composer install --no-dev --optimize-autoloader
|
||||||
|
|
||||||
# Créer .env.local
|
# Créer le fichier de configuration locale
|
||||||
cat > .env.local << 'EOF'
|
cat > .env.local << 'EOF'
|
||||||
APP_ENV=prod
|
APP_ENV=prod
|
||||||
APP_DEBUG=0
|
APP_DEBUG=0
|
||||||
@@ -85,28 +98,20 @@ APP_SECRET=CHANGE_ME
|
|||||||
DATABASE_URL="postgresql://ferme_user:fermerecette@127.0.0.1:5432/inventory?serverVersion=16"
|
DATABASE_URL="postgresql://ferme_user:fermerecette@127.0.0.1:5432/inventory?serverVersion=16"
|
||||||
|
|
||||||
CORS_ALLOW_ORIGIN='^https?://inventory\.malio-dev\.fr$'
|
CORS_ALLOW_ORIGIN='^https?://inventory\.malio-dev\.fr$'
|
||||||
|
|
||||||
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
|
|
||||||
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
|
|
||||||
JWT_PASSPHRASE=inventoryjwt
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Générer APP_SECRET
|
# Générer un secret aléatoire
|
||||||
sed -i "s/CHANGE_ME/$(openssl rand -hex 32)/" .env.local
|
sed -i "s/CHANGE_ME/$(openssl rand -hex 32)/" .env.local
|
||||||
|
|
||||||
# Générer les clés JWT
|
# Permissions pour le dossier var/ (cache, logs)
|
||||||
mkdir -p config/jwt
|
|
||||||
openssl genrsa -out config/jwt/private.pem -aes256 4096
|
|
||||||
# Passphrase : inventoryjwt
|
|
||||||
openssl rsa -pubout -in config/jwt/private.pem -out config/jwt/public.pem
|
|
||||||
chmod 600 config/jwt/private.pem
|
|
||||||
|
|
||||||
# Permissions
|
|
||||||
sudo chown -R www-data:www-data var/
|
sudo chown -R www-data:www-data var/
|
||||||
sudo chmod -R 775 var/
|
sudo chmod -R 775 var/
|
||||||
|
|
||||||
# Vider le cache
|
# Vider le cache
|
||||||
php bin/console cache:clear --env=prod
|
php bin/console cache:clear --env=prod
|
||||||
|
|
||||||
|
# Appliquer les migrations (si première installation ou mise à jour)
|
||||||
|
php bin/console doctrine:migrations:migrate --no-interaction
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Configurer le frontend Nuxt
|
### 4. Configurer le frontend Nuxt
|
||||||
@@ -120,7 +125,7 @@ sudo chown -R malio:malio .
|
|||||||
# Installer les dépendances
|
# Installer les dépendances
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
# Créer .env
|
# Créer le fichier d'environnement
|
||||||
cat > .env << 'EOF'
|
cat > .env << 'EOF'
|
||||||
NUXT_PUBLIC_API_BASE_URL=http://inventory.malio-dev.fr/api
|
NUXT_PUBLIC_API_BASE_URL=http://inventory.malio-dev.fr/api
|
||||||
EOF
|
EOF
|
||||||
@@ -141,7 +146,7 @@ server {
|
|||||||
listen 80;
|
listen 80;
|
||||||
server_name inventory.malio-dev.fr;
|
server_name inventory.malio-dev.fr;
|
||||||
|
|
||||||
# Gros fichiers (100MB max)
|
# Gros fichiers (100MB max pour les uploads de documents)
|
||||||
client_max_body_size 100M;
|
client_max_body_size 100M;
|
||||||
client_body_timeout 300s;
|
client_body_timeout 300s;
|
||||||
send_timeout 300s;
|
send_timeout 300s;
|
||||||
@@ -149,12 +154,13 @@ server {
|
|||||||
access_log /var/log/nginx/inventory-access.log;
|
access_log /var/log/nginx/inventory-access.log;
|
||||||
error_log /var/log/nginx/inventory-error.log;
|
error_log /var/log/nginx/inventory-error.log;
|
||||||
|
|
||||||
# Backend Symfony - /api
|
# Backend Symfony — toutes les requêtes /api
|
||||||
location /api {
|
location /api {
|
||||||
root /var/www/Inventory/public;
|
root /var/www/Inventory/public;
|
||||||
try_files $uri /index.php$is_args$args;
|
try_files $uri /index.php$is_args$args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# PHP-FPM (exécute le code PHP)
|
||||||
location ~ ^/index\.php(/|$) {
|
location ~ ^/index\.php(/|$) {
|
||||||
fastcgi_pass unix:/run/php/php-fpm.sock;
|
fastcgi_pass unix:/run/php/php-fpm.sock;
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.*)$;
|
fastcgi_split_path_info ^(.+\.php)(/.*)$;
|
||||||
@@ -165,27 +171,27 @@ server {
|
|||||||
internal;
|
internal;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Frontend statique
|
# Frontend statique — tout le reste
|
||||||
location / {
|
location / {
|
||||||
root /var/www/Inventory/Inventory_frontend/.output/public;
|
root /var/www/Inventory/Inventory_frontend/.output/public;
|
||||||
index index.html;
|
index index.html;
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html; # SPA fallback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Activer :
|
Activer le site :
|
||||||
```bash
|
```bash
|
||||||
sudo ln -s /etc/nginx/sites-available/inventory /etc/nginx/sites-enabled/
|
sudo ln -s /etc/nginx/sites-available/inventory /etc/nginx/sites-enabled/
|
||||||
sudo nginx -t
|
sudo nginx -t # Vérifier la syntaxe
|
||||||
sudo systemctl reload nginx
|
sudo systemctl reload nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. Vérifier
|
### 6. Vérifier
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl http://inventory.malio-dev.fr
|
curl http://inventory.malio-dev.fr # Frontend
|
||||||
curl http://inventory.malio-dev.fr/api
|
curl http://inventory.malio-dev.fr/api # API (doc Swagger)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -197,12 +203,13 @@ curl http://inventory.malio-dev.fr/api
|
|||||||
```bash
|
```bash
|
||||||
cd /var/www/Inventory
|
cd /var/www/Inventory
|
||||||
|
|
||||||
# Pull les changements
|
# Récupérer les changements
|
||||||
git pull
|
git pull
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
|
||||||
# Backend
|
# Backend
|
||||||
composer install --no-dev --optimize-autoloader
|
composer install --no-dev --optimize-autoloader
|
||||||
|
php bin/console doctrine:migrations:migrate --no-interaction
|
||||||
php bin/console cache:clear --env=prod
|
php bin/console cache:clear --env=prod
|
||||||
sudo chown -R www-data:www-data var/
|
sudo chown -R www-data:www-data var/
|
||||||
|
|
||||||
@@ -214,56 +221,76 @@ npx nuxi generate
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Versioning & Releases
|
## Backup base de données
|
||||||
|
|
||||||
### Source de vérité
|
### Export (faire un backup)
|
||||||
|
|
||||||
Le fichier `VERSION` à la racine contient le numéro de version (ex: `1.0.0`).
|
|
||||||
|
|
||||||
Cette version est synchronisée avec :
|
|
||||||
- Le footer de l'application
|
|
||||||
- `config/packages/api_platform.yaml`
|
|
||||||
|
|
||||||
### Créer une release
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Depuis le PC de dev
|
pg_dump -U ferme_user -h 127.0.0.1 -d inventory \
|
||||||
./scripts/release.sh patch # 1.0.0 → 1.0.1
|
--no-owner --no-acl --inserts --column-inserts \
|
||||||
./scripts/release.sh minor # 1.0.0 → 1.1.0
|
--clean --if-exists > backup_inventory_$(date +%Y%m%d).sql
|
||||||
./scripts/release.sh major # 1.0.0 → 2.0.0
|
|
||||||
./scripts/release.sh 2.0.0 # Version exacte
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Le script :
|
### Import (restaurer un backup)
|
||||||
1. Vérifie/commit le submodule frontend
|
|
||||||
2. Met à jour `VERSION` et `api_platform.yaml`
|
|
||||||
3. Commit et tag les deux repos
|
|
||||||
4. Affiche les commandes pour push
|
|
||||||
|
|
||||||
### Pousser la release
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Frontend (submodule)
|
psql -U ferme_user -h 127.0.0.1 -d inventory -f backup_inventory_YYYYMMDD.sql
|
||||||
cd Inventory_frontend && git push && git push --tags && cd ..
|
|
||||||
|
|
||||||
# Backend
|
|
||||||
git push && git push --tags
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Créer la release sur Gitea
|
|
||||||
|
|
||||||
1. Aller sur le dépôt Gitea
|
|
||||||
2. **Releases** > **New Release**
|
|
||||||
3. Sélectionner le tag `vX.Y.Z`
|
|
||||||
4. Ajouter les notes de release
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Commandes utiles
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Erreur 502 Bad Gateway
|
||||||
|
|
||||||
|
PHP-FPM ne tourne pas ou est crashé :
|
||||||
|
```bash
|
||||||
|
systemctl status php8.4-fpm
|
||||||
|
sudo systemctl restart php8.4-fpm
|
||||||
|
```
|
||||||
|
|
||||||
|
### Erreur 403 Forbidden
|
||||||
|
|
||||||
|
Problème de permissions sur les fichiers :
|
||||||
|
```bash
|
||||||
|
sudo chown -R www-data:www-data /var/www/Inventory/var/
|
||||||
|
sudo chmod -R 775 /var/www/Inventory/var/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Erreur API "No route found"
|
||||||
|
|
||||||
|
Le cache Symfony est probablement périmé :
|
||||||
|
```bash
|
||||||
|
php /var/www/Inventory/bin/console cache:clear --env=prod
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend ne se met pas à jour
|
||||||
|
|
||||||
|
Les fichiers statiques sont en cache. Rebuilder :
|
||||||
|
```bash
|
||||||
|
cd /var/www/Inventory/Inventory_frontend
|
||||||
|
rm -rf .output
|
||||||
|
npx nuxi generate
|
||||||
|
```
|
||||||
|
|
||||||
|
### L'API retourne 401 sur toutes les requêtes
|
||||||
|
|
||||||
|
La session PHP ne se crée pas correctement. Vérifier :
|
||||||
|
```bash
|
||||||
|
# Vérifier que le dossier de sessions existe et est accessible
|
||||||
|
ls -la /var/lib/php/sessions/
|
||||||
|
# Ou vérifier les logs Symfony
|
||||||
|
tail -f /var/www/Inventory/var/log/prod.log
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commandes utiles en production
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Logs Nginx
|
# Logs Nginx
|
||||||
tail -f /var/log/nginx/inventory-error.log
|
tail -f /var/log/nginx/inventory-error.log
|
||||||
|
tail -f /var/log/nginx/inventory-access.log
|
||||||
|
|
||||||
# Logs Symfony
|
# Logs Symfony
|
||||||
tail -f /var/www/Inventory/var/log/prod.log
|
tail -f /var/www/Inventory/var/log/prod.log
|
||||||
@@ -274,55 +301,12 @@ php /var/www/Inventory/bin/console cache:clear --env=prod
|
|||||||
# Rebuild frontend
|
# Rebuild frontend
|
||||||
cd /var/www/Inventory/Inventory_frontend && npx nuxi generate
|
cd /var/www/Inventory/Inventory_frontend && npx nuxi generate
|
||||||
|
|
||||||
# Status PHP-FPM
|
# Status des services
|
||||||
systemctl status php8.4-fpm
|
systemctl status php8.4-fpm
|
||||||
|
systemctl status nginx
|
||||||
|
systemctl status postgresql
|
||||||
|
|
||||||
# Reload Nginx
|
# Redémarrer les services
|
||||||
|
sudo systemctl restart php8.4-fpm
|
||||||
sudo systemctl reload nginx
|
sudo systemctl reload nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Backup base de données
|
|
||||||
|
|
||||||
### Export
|
|
||||||
```bash
|
|
||||||
pg_dump -U ferme_user -h 127.0.0.1 -d inventory --no-owner --no-acl --inserts --column-inserts --clean --if-exists > backup_inventory_$(date +%Y%m%d).sql
|
|
||||||
```
|
|
||||||
|
|
||||||
### Import
|
|
||||||
```bash
|
|
||||||
psql -U ferme_user -h 127.0.0.1 -d inventory -f backup_inventory_YYYYMMDD.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Erreur 502 Bad Gateway
|
|
||||||
```bash
|
|
||||||
# Vérifier PHP-FPM
|
|
||||||
systemctl status php8.4-fpm
|
|
||||||
sudo systemctl restart php8.4-fpm
|
|
||||||
```
|
|
||||||
|
|
||||||
### Erreur 403 Forbidden
|
|
||||||
```bash
|
|
||||||
# Vérifier les permissions
|
|
||||||
sudo chown -R www-data:www-data /var/www/Inventory/var/
|
|
||||||
sudo chmod -R 775 /var/www/Inventory/var/
|
|
||||||
```
|
|
||||||
|
|
||||||
### Erreur API "No route found"
|
|
||||||
```bash
|
|
||||||
# Vider le cache
|
|
||||||
php /var/www/Inventory/bin/console cache:clear --env=prod
|
|
||||||
```
|
|
||||||
|
|
||||||
### Frontend ne se met pas à jour
|
|
||||||
```bash
|
|
||||||
# Rebuild
|
|
||||||
cd /var/www/Inventory/Inventory_frontend
|
|
||||||
rm -rf .output
|
|
||||||
npx nuxi generate
|
|
||||||
```
|
|
||||||
|
|||||||
Submodule Inventory_frontend updated: 592beb0fa7...165e0a6341
269
README.md
269
README.md
@@ -2,52 +2,87 @@
|
|||||||
|
|
||||||
Application de gestion d'inventaire industriel pour **Malio**. Gestion complète du parc machines, des pièces, composants, produits, fournisseurs et documents associés, avec traçabilité et contrôle d'accès par rôles.
|
Application de gestion d'inventaire industriel pour **Malio**. Gestion complète du parc machines, des pièces, composants, produits, fournisseurs et documents associés, avec traçabilité et contrôle d'accès par rôles.
|
||||||
|
|
||||||
|
## C'est quoi ce projet ?
|
||||||
|
|
||||||
|
Inventory est une application web qui permet de gérer un parc de machines industrielles. Concrètement, elle permet de :
|
||||||
|
|
||||||
|
- **Cataloguer** les machines d'une usine, site par site
|
||||||
|
- **Décomposer** chaque machine en composants, pièces et produits (structure arborescente)
|
||||||
|
- **Suivre** les fournisseurs/constructeurs de chaque élément
|
||||||
|
- **Stocker** les documents techniques (PDF, images, fiches techniques)
|
||||||
|
- **Tracer** toutes les modifications (qui a changé quoi, quand) via un journal d'audit
|
||||||
|
- **Commenter** les fiches pour collaborer entre équipes
|
||||||
|
- **Gérer les accès** avec un système de rôles (admin, gestionnaire, lecteur)
|
||||||
|
|
||||||
|
L'application se compose de deux parties :
|
||||||
|
- Un **backend** (API REST) qui gère les données, la sécurité et la logique métier
|
||||||
|
- Un **frontend** (interface web) qui affiche les données et permet l'interaction utilisateur
|
||||||
|
|
||||||
## Stack technique
|
## Stack technique
|
||||||
|
|
||||||
| Couche | Technologie | Version |
|
| Couche | Technologie | Version | Rôle |
|
||||||
|--------|-------------|---------|
|
|--------|-------------|---------|------|
|
||||||
| Backend | Symfony + API Platform | 8.0 / 4.2 |
|
| Backend | Symfony + API Platform | 8.0 / 4.2 | API REST, logique métier, sécurité |
|
||||||
| PHP | PHP | >= 8.4 |
|
| PHP | PHP | >= 8.4 | Langage backend |
|
||||||
| Base de données | PostgreSQL | 16 |
|
| Base de données | PostgreSQL | 16 | Stockage des données |
|
||||||
| Frontend | Nuxt (SPA, SSR off) | 4 |
|
| Frontend | Nuxt (SPA, SSR off) | 4 | Framework web (rendu côté client) |
|
||||||
| UI | Vue 3 Composition API + TypeScript | 3.5 / 5.7 |
|
| UI | Vue 3 Composition API + TypeScript | 3.5 / 5.7 | Composants d'interface |
|
||||||
| CSS | TailwindCSS + DaisyUI | 4 / 5 |
|
| CSS | TailwindCSS + DaisyUI | 4 / 5 | Mise en page et composants visuels |
|
||||||
| Conteneurs | Docker Compose | |
|
| Conteneurs | Docker Compose | | Environnement de développement |
|
||||||
|
|
||||||
## Prérequis
|
## Prérequis
|
||||||
|
|
||||||
- **Docker** et **Docker Compose**
|
- **Docker** et **Docker Compose** (pour lancer le projet sans rien installer)
|
||||||
- **Node.js** >= 20 (via nvm)
|
- **Node.js** >= 20 (via [nvm](https://github.com/nvm-sh/nvm))
|
||||||
- **make**
|
- **make** (normalement déjà installé sur Linux/macOS)
|
||||||
|
|
||||||
### Installation de l'environnement
|
### Guides d'installation de l'environnement
|
||||||
|
|
||||||
| OS | Documentation |
|
| OS | Documentation |
|
||||||
|----|---------------|
|
|----|---------------|
|
||||||
| Windows | [WSL2 + Ubuntu + Docker](https://wiki.malio.fr/bookstack/books/environnement-de-dev/chapter/windows) |
|
| Windows | [WSL2 + Ubuntu + Docker](https://wiki.malio.fr/bookstack/books/environnement-de-dev/chapter/windows) |
|
||||||
| Linux | [Docker + nvm](https://wiki.malio.fr/bookstack/books/environnement-de-dev/chapter/linux) |
|
| Linux | [Docker + nvm](https://wiki.malio.fr/bookstack/books/environnement-de-dev/chapter/linux) |
|
||||||
|
|
||||||
## Installation
|
## Installation rapide
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# 1. Cloner le projet avec le frontend (submodule)
|
||||||
git clone --recurse-submodules <url-du-repo>
|
git clone --recurse-submodules <url-du-repo>
|
||||||
cd Inventory
|
cd Inventory
|
||||||
|
|
||||||
|
# 2. Démarrer les conteneurs Docker (PHP, PostgreSQL, Adminer)
|
||||||
make start
|
make start
|
||||||
|
|
||||||
|
# 3. Installer les dépendances et builder le projet
|
||||||
make install
|
make install
|
||||||
```
|
```
|
||||||
|
|
||||||
> Si `make start` échoue sur le port de la BDD, modifier `POSTGRES_PORT` dans `docker/.env.docker.local`.
|
> Si `make start` échoue sur le port de la BDD, modifier `POSTGRES_PORT` dans `docker/.env.docker.local`.
|
||||||
|
|
||||||
|
### Que fait `make install` ?
|
||||||
|
|
||||||
|
1. Installe les dépendances PHP (via Composer)
|
||||||
|
2. Installe les dépendances Node.js (via npm)
|
||||||
|
3. Build le frontend Nuxt
|
||||||
|
|
||||||
|
### Premier lancement
|
||||||
|
|
||||||
|
Une fois l'installation terminée, tu peux :
|
||||||
|
|
||||||
|
1. Charger des données de test : `make fixtures-load`
|
||||||
|
2. Lancer le frontend en mode dev : `make dev-nuxt`
|
||||||
|
3. Ouvrir l'application : http://localhost:3001
|
||||||
|
|
||||||
## URLs locales
|
## URLs locales
|
||||||
|
|
||||||
| Service | URL |
|
| Service | URL | Description |
|
||||||
|---------|-----|
|
|---------|-----|-------------|
|
||||||
| API Symfony | http://localhost:8081/api |
|
| API Symfony | http://localhost:8081/api | Documentation interactive de l'API (Swagger) |
|
||||||
| Frontend Nuxt | http://localhost:3001 |
|
| Frontend Nuxt | http://localhost:3001 | L'application web |
|
||||||
| Adminer (BDD) | http://localhost:5050 |
|
| Adminer (BDD) | http://localhost:5050 | Interface web pour explorer la base de données |
|
||||||
| PostgreSQL | `localhost:5433` (user: root, pass: root, db: inventory) |
|
| PostgreSQL | `localhost:5433` | Connexion directe (user: root, pass: root, db: inventory) |
|
||||||
|
|
||||||
## Commandes
|
## Commandes utiles
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
@@ -56,26 +91,28 @@ make install
|
|||||||
| `make start` | Démarrer les conteneurs |
|
| `make start` | Démarrer les conteneurs |
|
||||||
| `make stop` | Arrêter les conteneurs |
|
| `make stop` | Arrêter les conteneurs |
|
||||||
| `make restart` | Redémarrer les conteneurs |
|
| `make restart` | Redémarrer les conteneurs |
|
||||||
| `make shell` | Shell bash dans le conteneur PHP |
|
| `make shell` | Ouvrir un terminal dans le conteneur PHP (pour lancer des commandes Symfony) |
|
||||||
| `make reset` | Reset complet (supprime volumes, réinstalle) |
|
| `make reset` | Reset complet (supprime les volumes, réinstalle tout) |
|
||||||
|
|
||||||
### Backend
|
### Backend
|
||||||
|
|
||||||
| Commande | Description |
|
| Commande | Description |
|
||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| `make test` | Lancer les tests PHPUnit |
|
| `make test` | Lancer les tests PHPUnit |
|
||||||
| `make php-cs-fixer-allow-risky` | Formatter le code PHP |
|
| `make test FILES=tests/Api/Entity/MachineTest.php` | Lancer un test spécifique |
|
||||||
| `make cache-clear` | Vider le cache Symfony |
|
| `make test-setup` | Créer/mettre à jour la base de test |
|
||||||
| `make db-reset` | Reset de la BDD (supprime les données) |
|
| `make php-cs-fixer-allow-risky` | Formatter le code PHP (indentation, espaces, etc.) |
|
||||||
| `make fixtures-load` | Charger les fixtures SQL |
|
| `make cache-clear` | Vider le cache Symfony (à faire si tu as des erreurs bizarres) |
|
||||||
| `make fixtures-dump` | Dumper la BDD dans fixtures/data.sql |
|
| `make db-reset` | Reset de la BDD (supprime toutes les données) |
|
||||||
|
| `make fixtures-load` | Charger les données de test |
|
||||||
|
| `make fixtures-dump` | Sauvegarder la BDD actuelle dans fixtures/data.sql |
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
|
|
||||||
| Commande | Description |
|
| Commande | Description |
|
||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| `make dev-nuxt` | Serveur de dev Nuxt |
|
| `make dev-nuxt` | Lancer le serveur de dev Nuxt (avec rechargement automatique) |
|
||||||
| `make build-nuxtJS` | Build de production |
|
| `make build-nuxtJS` | Builder le frontend pour la production |
|
||||||
|
|
||||||
### Release
|
### Release
|
||||||
|
|
||||||
@@ -85,79 +122,115 @@ make install
|
|||||||
|
|
||||||
Synchronise automatiquement la version dans `VERSION`, `api_platform.yaml` et `nuxt.config.ts`, crée le tag git et pousse les deux repos.
|
Synchronise automatiquement la version dans `VERSION`, `api_platform.yaml` et `nuxt.config.ts`, crée le tag git et pousse les deux repos.
|
||||||
|
|
||||||
## Architecture
|
## Architecture globale
|
||||||
|
|
||||||
|
### Comment ça marche ?
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────┐ HTTP (JSON) ┌──────────────────┐ SQL ┌────────────┐
|
||||||
|
│ Frontend │ ◄─────────────────► │ Backend │ ◄──────────► │ PostgreSQL │
|
||||||
|
│ (Nuxt/Vue) │ cookies session │ (Symfony/API) │ │ (BDD) │
|
||||||
|
│ localhost:3001 │ │ localhost:8081 │ │ port 5433 │
|
||||||
|
└──────────────────┘ └──────────────────┘ └────────────┘
|
||||||
|
│ │
|
||||||
|
Interface web API REST + logique
|
||||||
|
pour l'utilisateur métier + sécurité
|
||||||
|
```
|
||||||
|
|
||||||
|
1. L'utilisateur ouvre le navigateur sur `localhost:3001`
|
||||||
|
2. Le frontend (Vue/Nuxt) affiche l'interface
|
||||||
|
3. Quand l'utilisateur fait une action (créer une machine, etc.), le frontend envoie une requête HTTP à l'API backend
|
||||||
|
4. Le backend valide la requête, vérifie les permissions, exécute la logique métier
|
||||||
|
5. Le backend lit/écrit dans PostgreSQL et renvoie une réponse JSON
|
||||||
|
6. Le frontend met à jour l'interface avec les nouvelles données
|
||||||
|
|
||||||
### Structure du projet
|
### Structure du projet
|
||||||
|
|
||||||
```
|
```
|
||||||
Inventory/ # Backend Symfony (repo principal)
|
Inventory/ # Backend Symfony (repo principal)
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── Entity/ # 20 entités Doctrine (attributs PHP 8)
|
│ ├── Entity/ # Les "modèles" de données (Machine, Piece, etc.)
|
||||||
│ ├── Controller/ # 16 contrôleurs custom
|
│ ├── Controller/ # Les endpoints API personnalisés
|
||||||
│ ├── EventSubscriber/ # 9 subscribers (audit onFlush)
|
│ ├── EventSubscriber/ # Logique déclenchée automatiquement (audit, etc.)
|
||||||
│ ├── EventListener/ # Listeners documents (cleanup, compression)
|
│ ├── Command/ # Commandes CLI (lancer via php bin/console)
|
||||||
│ ├── Command/ # 3 commandes CLI
|
│ ├── Service/ # Services métier (stockage fichiers, PDF, etc.)
|
||||||
│ ├── Service/ # 3 services (stockage, conversion, PDF)
|
│ ├── State/ # Processeurs API Platform (hashage mot de passe, upload)
|
||||||
│ ├── State/ # 3 processeurs API Platform
|
│ ├── Repository/ # Requêtes BDD personnalisées
|
||||||
│ ├── Repository/ # 19 repositories Doctrine
|
│ ├── Security/ # Authentification par session
|
||||||
│ ├── Security/ # Authenticateur session
|
│ └── Serializer/ # Conversion entité ↔ JSON personnalisée
|
||||||
│ └── Serializer/ # Normalizer custom (Document)
|
├── config/ # Configuration Symfony (routes, sécurité, etc.)
|
||||||
├── config/ # Configuration Symfony
|
├── migrations/ # Scripts de modification de la BDD
|
||||||
├── migrations/ # 4 migrations Doctrine (SQL PostgreSQL)
|
|
||||||
├── fixtures/ # Données de test (SQL)
|
├── fixtures/ # Données de test (SQL)
|
||||||
|
├── tests/ # Tests automatisés (PHPUnit)
|
||||||
├── scripts/ # Utilitaires (release, migration, normalisation)
|
├── scripts/ # Utilitaires (release, migration, normalisation)
|
||||||
├── docker/ # Dockerfile + config Docker
|
├── docker/ # Dockerfile + config Docker
|
||||||
├── makefile # Commandes de dev
|
├── makefile # Commandes de dev raccourcies
|
||||||
├── VERSION # Version courante (semver)
|
├── VERSION # Version courante (ex: 1.8.1)
|
||||||
└── Inventory_frontend/ # Submodule git (repo séparé)
|
└── Inventory_frontend/ # Submodule git (frontend, repo séparé)
|
||||||
├── app/pages/ # 36 pages Nuxt (file-based routing)
|
├── app/pages/ # Les pages de l'app (1 fichier = 1 route URL)
|
||||||
├── app/components/ # 57 composants Vue
|
├── app/components/ # Composants Vue réutilisables
|
||||||
├── app/composables/ # 45 composables
|
├── app/composables/ # Logique métier partagée (appels API, états)
|
||||||
└── app/shared/ # Types, utils, validation
|
├── app/shared/ # Types TypeScript, utilitaires, validation
|
||||||
|
├── app/middleware/ # Vérification de session automatique
|
||||||
|
└── app/services/ # Couche service (wrappers API)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Entités principales
|
### Entités principales (les "tables" de la BDD)
|
||||||
|
|
||||||
| Entité | Description |
|
| Entité | Description | Exemple |
|
||||||
|--------|-------------|
|
|--------|-------------|---------|
|
||||||
| `Machine` | Machines du parc industriel |
|
| `Machine` | Machines du parc industriel | "CNC Mazak 01" |
|
||||||
| `Composant` | Composants rattachés aux machines |
|
| `Composant` | Composants fonctionnels d'une machine | "Broche principale" |
|
||||||
| `Piece` | Pièces détachées |
|
| `Piece` | Pièces détachées/de rechange | "Roulement SKF 6205" |
|
||||||
| `Product` | Produits (consommables, outillage) |
|
| `Product` | Produits fournisseur (consommables, outillage) | "Huile de coupe X" |
|
||||||
| `Site` | Sites physiques / usines |
|
| `Site` | Sites physiques / usines | "Usine de Strasbourg" |
|
||||||
| `Constructeur` | Fournisseurs / fabricants |
|
| `Constructeur` | Fournisseurs / fabricants | "SKF", "Mazak" |
|
||||||
| `TypeMachine` | Types de machines avec squelettes de structure |
|
| `ModelType` | Catégories avec squelettes de structure | "Type: Moteur électrique" |
|
||||||
| `ModelType` | Catégories (pièce, composant, produit) avec champs personnalisés |
|
| `CustomField` / `CustomFieldValue` | Champs personnalisés (dynamiques) | "Tension : 220V" |
|
||||||
| `CustomField` / `CustomFieldValue` | Champs personnalisés extensibles |
|
| `Document` | Documents uploadés (PDF, images, etc.) | "Fiche technique CNC.pdf" |
|
||||||
| `Document` | Documents uploadés (stockage fichier + compression PDF) |
|
| `AuditLog` | Journal d'audit (historique des modifications) | "Machine X modifiée par Jean" |
|
||||||
| `AuditLog` | Journal d'audit (diff + snapshot) |
|
| `Comment` | Commentaires / tickets sur les fiches | "Vérifier le roulement" |
|
||||||
| `Comment` | Commentaires / tickets sur les fiches |
|
| `Profile` | Comptes utilisateurs avec rôles | "admin@malio.fr (ADMIN)" |
|
||||||
| `Profile` | Utilisateurs avec rôles |
|
|
||||||
|
|
||||||
### Commandes Symfony
|
### Structure hiérarchique d'une machine
|
||||||
|
|
||||||
| Commande | Description |
|
Une machine peut contenir une arborescence de composants, pièces et produits :
|
||||||
|----------|-------------|
|
|
||||||
| `app:compress-pdf` | Compresser les PDFs existants (supporte `--dry-run`) |
|
```
|
||||||
| `app:migrate-documents-to-filesystem` | Migrer les documents Base64 vers le système de fichiers |
|
Machine "CNC Mazak 01"
|
||||||
| `app:init-profile-passwords` | Initialiser mots de passe et rôles en masse |
|
├── Composant "Broche principale"
|
||||||
|
│ ├── Pièce "Roulement avant"
|
||||||
|
│ │ └── Produit "Graisse SKF LGMT2"
|
||||||
|
│ └── Pièce "Joint d'étanchéité"
|
||||||
|
├── Composant "Système hydraulique"
|
||||||
|
│ ├── Pièce "Pompe HP"
|
||||||
|
│ └── Produit "Huile hydraulique ISO 46"
|
||||||
|
└── Produit "Filtre à air cabine"
|
||||||
|
```
|
||||||
|
|
||||||
### Rôles et permissions
|
### Rôles et permissions
|
||||||
|
|
||||||
```
|
```
|
||||||
ROLE_ADMIN → ROLE_GESTIONNAIRE → ROLE_VIEWER → ROLE_USER
|
ROLE_ADMIN → Tout faire + gérer les utilisateurs
|
||||||
|
↓ hérite de
|
||||||
|
ROLE_GESTIONNAIRE → Créer, modifier, supprimer les données
|
||||||
|
↓ hérite de
|
||||||
|
ROLE_VIEWER → Lecture seule sur toutes les données
|
||||||
|
↓ hérite de
|
||||||
|
ROLE_USER → Accès de base (rôle minimum)
|
||||||
```
|
```
|
||||||
|
|
||||||
- **ADMIN** : accès complet, gestion des profils
|
|
||||||
- **GESTIONNAIRE** : CRUD sur toutes les entités, résolution des commentaires
|
|
||||||
- **VIEWER** : lecture seule sur toutes les entités
|
|
||||||
- **USER** : accès de base
|
|
||||||
|
|
||||||
### Authentification
|
### Authentification
|
||||||
|
|
||||||
Authentification par **session (cookies)**, pas de JWT. Le profil actif est stocké en session côté serveur.
|
Authentification par **session (cookies)**, pas de JWT. Le profil actif est stocké en session côté serveur. Concrètement :
|
||||||
|
|
||||||
### Base de données
|
1. L'utilisateur choisit son profil sur la page de login
|
||||||
|
2. Il entre son mot de passe
|
||||||
|
3. Le backend crée une session et envoie un cookie au navigateur
|
||||||
|
4. À chaque requête suivante, le navigateur envoie automatiquement ce cookie
|
||||||
|
5. Le backend vérifie le cookie et identifie l'utilisateur
|
||||||
|
|
||||||
|
### Base de données — Points importants
|
||||||
|
|
||||||
PostgreSQL 16 avec les particularités suivantes :
|
PostgreSQL 16 avec les particularités suivantes :
|
||||||
- **IDs** : chaînes CUID (`'cl' + bin2hex(random_bytes(12))`), pas d'auto-increment
|
- **IDs** : chaînes CUID (`'cl' + bin2hex(random_bytes(12))`), pas d'auto-increment
|
||||||
@@ -171,7 +244,7 @@ PostgreSQL 16 avec les particularités suivantes :
|
|||||||
|---------|-------|------|------|
|
|---------|-------|------|------|
|
||||||
| `web` | PHP 8.4 + Apache + Node | 8081, 3001 | API Symfony + Nuxt dev |
|
| `web` | PHP 8.4 + Apache + Node | 8081, 3001 | API Symfony + Nuxt dev |
|
||||||
| `db` | PostgreSQL 16 Alpine | 5433 | Base de données |
|
| `db` | PostgreSQL 16 Alpine | 5433 | Base de données |
|
||||||
| `adminer` | Adminer | 5050 | Interface web BDD |
|
| `adminer` | Adminer | 5050 | Interface web pour explorer la BDD |
|
||||||
|
|
||||||
## Xdebug
|
## Xdebug
|
||||||
|
|
||||||
@@ -191,30 +264,42 @@ Configuration PhpStorm / VSCode :
|
|||||||
- `develop` : branche principale de dev (cible des PR)
|
- `develop` : branche principale de dev (cible des PR)
|
||||||
- `feat/xxx`, `fix/xxx`, `refactor/xxx` : branches de travail
|
- `feat/xxx`, `fix/xxx`, `refactor/xxx` : branches de travail
|
||||||
|
|
||||||
### Convention de commit
|
### Convention de commit (enforced par un hook)
|
||||||
|
|
||||||
```
|
```
|
||||||
<type>(<scope>) : <message>
|
<type>(<scope>) : <message>
|
||||||
```
|
```
|
||||||
|
|
||||||
**Espace obligatoire autour du `:`**. Types : `feat`, `fix`, `perf`, `refactor`, `chore`, `docs`, `test`, `style`, `build`, `ci`, `revert`, `wip`.
|
**Espace obligatoire autour du `:`**. Types autorisés (minuscules) :
|
||||||
|
`feat`, `fix`, `perf`, `refactor`, `chore`, `docs`, `test`, `style`, `build`, `ci`, `revert`, `wip`
|
||||||
|
|
||||||
|
Exemples :
|
||||||
|
```
|
||||||
|
feat(machines) : add clone functionality
|
||||||
|
fix(documents) : prevent duplicate upload
|
||||||
|
refactor(audit) : merge history controllers
|
||||||
|
chore(deps) : update composer packages
|
||||||
|
```
|
||||||
|
|
||||||
### Pre-commit hook
|
### Pre-commit hook
|
||||||
|
|
||||||
1. php-cs-fixer sur les fichiers PHP stagés
|
Le hook `pre-commit` s'exécute automatiquement avant chaque commit :
|
||||||
2. PHPUnit — bloque le commit si les tests échouent
|
1. **php-cs-fixer** — Formate automatiquement les fichiers PHP modifiés
|
||||||
|
2. **PHPUnit** — Lance les tests. Si un test échoue, le commit est bloqué
|
||||||
|
|
||||||
### Submodule frontend
|
### Submodule frontend
|
||||||
|
|
||||||
Le frontend est un **submodule git** dans `Inventory_frontend/`. Workflow :
|
Le frontend est un **submodule git** dans `Inventory_frontend/` (c'est un repo git séparé, inclus dans le repo principal). Workflow de commit :
|
||||||
|
|
||||||
1. Commiter dans `Inventory_frontend/` d'abord
|
1. Commiter dans `Inventory_frontend/` d'abord
|
||||||
2. Commiter dans le repo principal pour mettre à jour le pointeur
|
2. Commiter dans le repo principal pour mettre à jour le pointeur du submodule
|
||||||
3. Pousser les deux repos
|
3. Pousser les deux repos
|
||||||
|
|
||||||
## Documentation complémentaire
|
## Documentation détaillée
|
||||||
|
|
||||||
- [DEPLOY.md](DEPLOY.md) : guide de déploiement serveur (Nginx, PHP-FPM, PostgreSQL)
|
- **[docs/BACKEND.md](docs/BACKEND.md)** : guide complet du backend (entités, controllers, API, audit, tests)
|
||||||
- [RELEASE.md](RELEASE.md) : processus de release et versioning
|
- **[docs/FRONTEND.md](docs/FRONTEND.md)** : guide complet du frontend (pages, composables, composants, patterns)
|
||||||
- [CHANGELOG.md](CHANGELOG.md) : historique des versions
|
- **[DEPLOY.md](DEPLOY.md)** : guide de déploiement serveur (Nginx, PHP-FPM, PostgreSQL)
|
||||||
- [Frontend README](Inventory_frontend/README.md) : documentation du frontend Nuxt
|
- **[RELEASE.md](RELEASE.md)** : processus de release et versioning
|
||||||
|
- **[CHANGELOG.md](CHANGELOG.md)** : historique des versions
|
||||||
|
- **[Frontend README](Inventory_frontend/README.md)** : documentation du frontend Nuxt
|
||||||
|
|||||||
132
RELEASE.md
132
RELEASE.md
@@ -1,12 +1,18 @@
|
|||||||
# Guide de Release
|
# Guide de Release
|
||||||
|
|
||||||
## Versioning
|
## C'est quoi une release ?
|
||||||
|
|
||||||
Le projet utilise le [Semantic Versioning](https://semver.org/) (SemVer) : `MAJOR.MINOR.PATCH`
|
Une release c'est une **version officielle** de l'application. Chaque release a un numéro de version (ex: `1.8.1`) et est marquée par un **tag git**.
|
||||||
|
|
||||||
- **MAJOR** : Changements incompatibles avec les versions précédentes
|
## Versioning (Semantic Versioning)
|
||||||
- **MINOR** : Nouvelles fonctionnalités rétrocompatibles
|
|
||||||
- **PATCH** : Corrections de bugs rétrocompatibles
|
Le projet utilise le [Semantic Versioning](https://semver.org/) : `MAJOR.MINOR.PATCH`
|
||||||
|
|
||||||
|
| Type | Quand l'utiliser | Exemple |
|
||||||
|
|------|------------------|---------|
|
||||||
|
| **PATCH** | Correction de bug, pas de nouvelle fonctionnalité | `1.8.0` → `1.8.1` |
|
||||||
|
| **MINOR** | Nouvelle fonctionnalité, rétrocompatible | `1.8.1` → `1.9.0` |
|
||||||
|
| **MAJOR** | Changement majeur, potentiellement incompatible | `1.9.0` → `2.0.0` |
|
||||||
|
|
||||||
La version est centralisée dans le fichier `VERSION` à la racine du projet.
|
La version est centralisée dans le fichier `VERSION` à la racine du projet.
|
||||||
|
|
||||||
@@ -14,9 +20,9 @@ La version est centralisée dans le fichier `VERSION` à la racine du projet.
|
|||||||
|
|
||||||
### Prérequis
|
### Prérequis
|
||||||
|
|
||||||
- Tous les changements doivent être commités
|
- Tous les changements doivent être commités (pas de fichiers modifiés non commités)
|
||||||
- Les tests doivent passer
|
- Les tests doivent passer (`make test`)
|
||||||
- Être sur la branche à releaser (ex: `main`, `develop`)
|
- Être sur la branche à releaser (généralement `develop` ou `master`)
|
||||||
|
|
||||||
### Utilisation du script
|
### Utilisation du script
|
||||||
|
|
||||||
@@ -24,28 +30,38 @@ La version est centralisée dans le fichier `VERSION` à la racine du projet.
|
|||||||
# Afficher l'aide et la version actuelle
|
# Afficher l'aide et la version actuelle
|
||||||
./scripts/release.sh
|
./scripts/release.sh
|
||||||
|
|
||||||
# Bump patch : 1.0.0 → 1.0.1
|
# Bump patch : 1.8.1 → 1.8.2
|
||||||
./scripts/release.sh patch
|
./scripts/release.sh patch
|
||||||
|
|
||||||
# Bump minor : 1.0.0 → 1.1.0
|
# Bump minor : 1.8.1 → 1.9.0
|
||||||
./scripts/release.sh minor
|
./scripts/release.sh minor
|
||||||
|
|
||||||
# Bump major : 1.0.0 → 2.0.0
|
# Bump major : 1.8.1 → 2.0.0
|
||||||
./scripts/release.sh major
|
./scripts/release.sh major
|
||||||
|
|
||||||
# Version spécifique
|
# Version spécifique
|
||||||
./scripts/release.sh 2.0.0
|
./scripts/release.sh 2.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
Le script :
|
### Que fait le script ?
|
||||||
1. Met à jour le fichier `VERSION`
|
|
||||||
2. Met à jour `config/packages/api_platform.yaml`
|
1. Vérifie qu'il n'y a pas de changements non commités
|
||||||
3. Crée un commit `chore(release): vX.Y.Z`
|
2. Vérifie/commit le submodule frontend si nécessaire
|
||||||
4. Crée le tag `vX.Y.Z`
|
3. Met à jour le fichier `VERSION` avec le nouveau numéro
|
||||||
|
4. Met à jour `config/packages/api_platform.yaml` (version affichée dans l'API)
|
||||||
|
5. Crée un commit `chore(release) : vX.Y.Z`
|
||||||
|
6. Crée le tag git `vX.Y.Z`
|
||||||
|
7. Affiche les commandes pour pousser
|
||||||
|
|
||||||
### Pousser la release
|
### Pousser la release
|
||||||
|
|
||||||
|
Après avoir exécuté le script :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Pousser le frontend d'abord (si modifié)
|
||||||
|
cd Inventory_frontend && git push && git push --tags && cd ..
|
||||||
|
|
||||||
|
# Pousser le backend
|
||||||
git push && git push --tags
|
git push && git push --tags
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -54,12 +70,21 @@ git push && git push --tags
|
|||||||
1. Aller sur le dépôt Gitea
|
1. Aller sur le dépôt Gitea
|
||||||
2. **Releases** > **New Release**
|
2. **Releases** > **New Release**
|
||||||
3. Sélectionner le tag `vX.Y.Z`
|
3. Sélectionner le tag `vX.Y.Z`
|
||||||
4. Titre : `v1.0.0` (ou avec un nom descriptif)
|
4. Titre : `vX.Y.Z` (ou avec un nom descriptif)
|
||||||
5. Description : résumé des changements (voir section Notes de release)
|
5. Description : résumé des changements (copier depuis CHANGELOG.md)
|
||||||
|
|
||||||
|
## Fichiers impactés par le versioning
|
||||||
|
|
||||||
|
| Fichier | Rôle |
|
||||||
|
|---------|------|
|
||||||
|
| `VERSION` | Source unique de vérité |
|
||||||
|
| `config/packages/api_platform.yaml` | Version affichée dans la doc API (Swagger) |
|
||||||
|
| `Inventory_frontend/nuxt.config.ts` | Lit `VERSION` au build pour l'afficher dans le footer |
|
||||||
|
| Footer de l'app | Affiche `v{{ appVersion }}` |
|
||||||
|
|
||||||
## Notes de release
|
## Notes de release
|
||||||
|
|
||||||
Template pour les notes de release :
|
Template pour les notes de release (à copier dans Gitea) :
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
## Nouveautés
|
## Nouveautés
|
||||||
@@ -73,66 +98,25 @@ Template pour les notes de release :
|
|||||||
## Changements
|
## Changements
|
||||||
- Refactoring de Z
|
- Refactoring de Z
|
||||||
- Mise à jour des dépendances
|
- Mise à jour des dépendances
|
||||||
|
|
||||||
|
## Migration requise
|
||||||
|
\`\`\`bash
|
||||||
|
docker compose exec web php bin/console doctrine:migrations:migrate
|
||||||
|
\`\`\`
|
||||||
```
|
```
|
||||||
|
|
||||||
## Fichiers impactés par le versioning
|
## Déploiement après une release
|
||||||
|
|
||||||
| Fichier | Usage |
|
Voir [DEPLOY.md](DEPLOY.md) pour les instructions de mise à jour en production.
|
||||||
|---------|-------|
|
|
||||||
| `VERSION` | Source unique de vérité |
|
|
||||||
| `config/packages/api_platform.yaml` | Version affichée dans l'API |
|
|
||||||
| `Inventory_frontend/nuxt.config.ts` | Lit VERSION au build |
|
|
||||||
| Footer de l'app | Affiche `v{{ appVersion }}` |
|
|
||||||
|
|
||||||
## Déploiement en production
|
En résumé :
|
||||||
|
|
||||||
### 1. Base de données
|
|
||||||
|
|
||||||
Dump de la base locale :
|
|
||||||
```bash
|
|
||||||
pg_dump -h localhost -p 5433 -U root -d inventory > backup_v1.0.0.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
Import en production :
|
|
||||||
```bash
|
|
||||||
psql -h <PROD_HOST> -U <PROD_USER> -d inventory < backup_v1.0.0.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Variables d'environnement production
|
|
||||||
|
|
||||||
Créer un fichier `.env.local` en production avec :
|
|
||||||
|
|
||||||
```env
|
|
||||||
APP_ENV=prod
|
|
||||||
APP_SECRET=<générer avec: openssl rand -hex 32>
|
|
||||||
DATABASE_URL="postgresql://user:password@host:5432/inventory?serverVersion=16"
|
|
||||||
CORS_ALLOW_ORIGIN='^https://votre-domaine\.com$'
|
|
||||||
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
|
|
||||||
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
|
|
||||||
JWT_PASSPHRASE=<votre-passphrase>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Build production
|
|
||||||
|
|
||||||
Backend :
|
|
||||||
```bash
|
```bash
|
||||||
|
# Sur le serveur de production
|
||||||
|
cd /var/www/Inventory
|
||||||
|
git pull
|
||||||
|
git submodule update --init --recursive
|
||||||
composer install --no-dev --optimize-autoloader
|
composer install --no-dev --optimize-autoloader
|
||||||
php bin/console cache:clear --env=prod
|
|
||||||
php bin/console doctrine:migrations:migrate --no-interaction
|
php bin/console doctrine:migrations:migrate --no-interaction
|
||||||
|
php bin/console cache:clear --env=prod
|
||||||
|
cd Inventory_frontend && npm install && npx nuxi generate
|
||||||
```
|
```
|
||||||
|
|
||||||
Frontend :
|
|
||||||
```bash
|
|
||||||
cd Inventory_frontend
|
|
||||||
NUXT_PUBLIC_API_BASE_URL=https://api.votre-domaine.com yarn build
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Checklist avant mise en prod
|
|
||||||
|
|
||||||
- [ ] Tests passent
|
|
||||||
- [ ] Migrations DB testées
|
|
||||||
- [ ] Variables d'environnement configurées
|
|
||||||
- [ ] Clés JWT générées
|
|
||||||
- [ ] CORS configuré
|
|
||||||
- [ ] SSL/HTTPS actif
|
|
||||||
- [ ] Backup de la DB prod existante (si upgrade)
|
|
||||||
|
|||||||
846
docs/BACKEND.md
Normal file
846
docs/BACKEND.md
Normal file
@@ -0,0 +1,846 @@
|
|||||||
|
# Guide Backend — Inventory
|
||||||
|
|
||||||
|
Guide complet du backend Symfony pour comprendre comment tout fonctionne, même si tu débutes.
|
||||||
|
|
||||||
|
## Table des matières
|
||||||
|
|
||||||
|
1. [Vue d'ensemble](#vue-densemble)
|
||||||
|
2. [Comment fonctionne une API REST](#comment-fonctionne-une-api-rest)
|
||||||
|
3. [Symfony + API Platform — les bases](#symfony--api-platform--les-bases)
|
||||||
|
4. [Les Entités (les modèles de données)](#les-entités)
|
||||||
|
5. [Les Controllers (les endpoints personnalisés)](#les-controllers)
|
||||||
|
6. [Le système d'audit](#le-système-daudit)
|
||||||
|
7. [L'authentification par session](#lauthentification-par-session)
|
||||||
|
8. [Les services](#les-services)
|
||||||
|
9. [Les migrations de base de données](#les-migrations)
|
||||||
|
10. [Les tests](#les-tests)
|
||||||
|
11. [Flux complet d'une requête](#flux-complet-dune-requête)
|
||||||
|
12. [Commandes Symfony utiles](#commandes-symfony-utiles)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vue d'ensemble
|
||||||
|
|
||||||
|
Le backend est une **API REST** construite avec :
|
||||||
|
|
||||||
|
- **Symfony 8** : le framework PHP (gère le routing, la sécurité, la config, etc.)
|
||||||
|
- **API Platform 4.2** : une surcouche qui génère automatiquement les endpoints CRUD à partir des entités
|
||||||
|
- **Doctrine ORM** : fait le lien entre les objets PHP et les tables PostgreSQL
|
||||||
|
- **PostgreSQL 16** : la base de données relationnelle
|
||||||
|
|
||||||
|
### Le principe
|
||||||
|
|
||||||
|
Au lieu d'écrire manuellement chaque endpoint (GET /machines, POST /machines, etc.), **API Platform** les génère automatiquement à partir des entités PHP. Tu déclares tes champs, tes relations, tes règles de sécurité directement sur la classe PHP, et API Platform fait le reste.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Comment fonctionne une API REST
|
||||||
|
|
||||||
|
### C'est quoi une API REST ?
|
||||||
|
|
||||||
|
Une API REST c'est un serveur qui répond à des requêtes HTTP (comme un site web, mais au lieu de renvoyer du HTML, il renvoie du JSON).
|
||||||
|
|
||||||
|
### Les verbes HTTP
|
||||||
|
|
||||||
|
| Verbe | Action | Exemple |
|
||||||
|
|-------|--------|---------|
|
||||||
|
| `GET` | Lire des données | `GET /api/machines` → liste toutes les machines |
|
||||||
|
| `POST` | Créer une donnée | `POST /api/machines` + body JSON → crée une machine |
|
||||||
|
| `PUT` | Remplacer une donnée | `PUT /api/machines/123` + body JSON → remplace la machine 123 |
|
||||||
|
| `PATCH` | Modifier partiellement | `PATCH /api/machines/123` + body JSON → modifie certains champs |
|
||||||
|
| `DELETE` | Supprimer | `DELETE /api/machines/123` → supprime la machine 123 |
|
||||||
|
|
||||||
|
### Les codes de réponse HTTP
|
||||||
|
|
||||||
|
| Code | Signification | Quand |
|
||||||
|
|------|---------------|-------|
|
||||||
|
| `200` | OK | Requête réussie |
|
||||||
|
| `201` | Created | Ressource créée avec succès (POST) |
|
||||||
|
| `204` | No Content | Suppression réussie (DELETE) |
|
||||||
|
| `400` | Bad Request | Données invalides envoyées |
|
||||||
|
| `401` | Unauthorized | Pas connecté / session expirée |
|
||||||
|
| `403` | Forbidden | Connecté mais pas les permissions |
|
||||||
|
| `404` | Not Found | La ressource n'existe pas |
|
||||||
|
| `409` | Conflict | Doublon (ex: nom déjà pris) |
|
||||||
|
| `500` | Server Error | Bug côté serveur |
|
||||||
|
|
||||||
|
### Le format JSON-LD
|
||||||
|
|
||||||
|
L'API utilise **JSON-LD** (JSON Linked Data), une extension de JSON qui ajoute des métadonnées :
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"@context": "/api/contexts/Machine",
|
||||||
|
"@id": "/api/machines/cl1a2b3c4d5e6f7g8h9i0j1k",
|
||||||
|
"@type": "Machine",
|
||||||
|
"id": "cl1a2b3c4d5e6f7g8h9i0j1k",
|
||||||
|
"name": "CNC Mazak 01",
|
||||||
|
"reference": "CNM-001",
|
||||||
|
"prix": "50000.00",
|
||||||
|
"site": "/api/sites/cl9z8y7x6w5v4u3t2s1r0q",
|
||||||
|
"createdAt": "2026-01-15T10:30:00+00:00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Points importants :
|
||||||
|
- `@id` est l'**IRI** (Internationalized Resource Identifier) : c'est l'identifiant unique de la ressource dans l'API
|
||||||
|
- Les relations utilisent des IRIs : `"site": "/api/sites/cl9z8..."` au lieu d'un simple ID
|
||||||
|
- Les collections retournent un format hydra avec pagination :
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"@context": "/api/contexts/Machine",
|
||||||
|
"@id": "/api/machines",
|
||||||
|
"@type": "hydra:Collection",
|
||||||
|
"hydra:totalItems": 42,
|
||||||
|
"hydra:member": [
|
||||||
|
{ "@id": "/api/machines/cl...", "name": "CNC 01", ... },
|
||||||
|
{ "@id": "/api/machines/cl...", "name": "Tour 02", ... }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Symfony + API Platform — les bases
|
||||||
|
|
||||||
|
### La structure des fichiers
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── Entity/ # Les classes PHP qui représentent les tables de la BDD
|
||||||
|
├── Controller/ # Les endpoints HTTP personnalisés (quand API Platform ne suffit pas)
|
||||||
|
├── EventSubscriber/ # Du code qui s'exécute automatiquement quand quelque chose se passe
|
||||||
|
├── Repository/ # Les requêtes SQL personnalisées
|
||||||
|
├── Service/ # La logique métier réutilisable
|
||||||
|
├── State/ # Les processeurs API Platform (interceptent le flux CRUD)
|
||||||
|
├── Security/ # L'authentification
|
||||||
|
├── Serializer/ # Personnalisation de la conversion entité ↔ JSON
|
||||||
|
├── Command/ # Commandes CLI (php bin/console app:xxx)
|
||||||
|
├── Enum/ # Les énumérations PHP (ex: catégories)
|
||||||
|
└── OpenApi/ # Personnalisation de la doc Swagger
|
||||||
|
```
|
||||||
|
|
||||||
|
### Comment Symfony traite une requête
|
||||||
|
|
||||||
|
```
|
||||||
|
Requête HTTP
|
||||||
|
↓
|
||||||
|
Symfony Router (quel code doit répondre ?)
|
||||||
|
↓
|
||||||
|
Sécurité (l'utilisateur a-t-il le droit ?)
|
||||||
|
↓
|
||||||
|
Controller ou API Platform (traitement)
|
||||||
|
↓
|
||||||
|
Doctrine ORM (lecture/écriture en BDD)
|
||||||
|
↓
|
||||||
|
Serializer (conversion entité → JSON)
|
||||||
|
↓
|
||||||
|
Réponse HTTP (JSON envoyé au frontend)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Les attributs PHP 8
|
||||||
|
|
||||||
|
Le projet utilise les **attributs PHP 8** (les `#[...]`) au lieu des annotations (les `@...`). C'est la syntaxe moderne de PHP :
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Attribut PHP 8 (ce qu'on utilise) ✅
|
||||||
|
#[ORM\Column(type: 'string', length: 255)]
|
||||||
|
private string $name;
|
||||||
|
|
||||||
|
// Annotation (ancien style, on ne l'utilise pas) ❌
|
||||||
|
/** @ORM\Column(type="string", length=255) */
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Les Entités
|
||||||
|
|
||||||
|
Les entités sont les classes PHP qui représentent les tables de la base de données. Chaque propriété de la classe correspond à une colonne.
|
||||||
|
|
||||||
|
### Anatomie d'une entité
|
||||||
|
|
||||||
|
Prenons un exemple simplifié :
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
// src/Entity/Machine.php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: MachineRepository::class)] // ← Lié à une table en BDD
|
||||||
|
#[ORM\HasLifecycleCallbacks] // ← Active les hooks PrePersist/PreUpdate
|
||||||
|
#[ApiResource( // ← Génère les endpoints API
|
||||||
|
operations: [
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"), // GET /api/machines
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"), // GET /api/machines/{id}
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"), // POST /api/machines
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"), // PATCH /api/machines/{id}
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"), // DELETE /api/machines/{id}
|
||||||
|
],
|
||||||
|
normalizationContext: ['groups' => ['machine:read']], // ← Quels champs exposer en lecture
|
||||||
|
denormalizationContext: ['groups' => ['machine:write']], // ← Quels champs accepter en écriture
|
||||||
|
paginationItemsPerPage: 30, // ← 30 résultats par page
|
||||||
|
)]
|
||||||
|
class Machine
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\Column(type: 'string', length: 36)]
|
||||||
|
#[Groups(['machine:read'])] // ← Exposé en lecture uniquement
|
||||||
|
private string $id;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'string', length: 255, unique: true)]
|
||||||
|
#[Groups(['machine:read', 'machine:write'])] // ← Exposé en lecture ET écriture
|
||||||
|
private string $name;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'decimal', precision: 10, scale: 2, nullable: true)]
|
||||||
|
#[Groups(['machine:read', 'machine:write'])]
|
||||||
|
private ?string $prix = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(targetEntity: Site::class)] // ← Relation : chaque machine appartient à un site
|
||||||
|
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] // ← Obligatoire, supprimé en cascade
|
||||||
|
#[Groups(['machine:read', 'machine:write'])]
|
||||||
|
private Site $site;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'datetime_immutable')]
|
||||||
|
#[Groups(['machine:read'])] // ← Lecture seule (pas dans machine:write)
|
||||||
|
private \DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
|
// ... getters et setters
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Décryptage des attributs importants
|
||||||
|
|
||||||
|
| Attribut | Signification |
|
||||||
|
|----------|--------------|
|
||||||
|
| `#[ORM\Entity]` | Cette classe est stockée en BDD |
|
||||||
|
| `#[ORM\Column]` | Cette propriété est une colonne |
|
||||||
|
| `#[ORM\Id]` | C'est la clé primaire |
|
||||||
|
| `#[ORM\ManyToOne]` | Relation N→1 (plusieurs machines → un site) |
|
||||||
|
| `#[ORM\OneToMany]` | Relation 1→N (un site → plusieurs machines) |
|
||||||
|
| `#[ORM\ManyToMany]` | Relation N→N (machines ↔ constructeurs) |
|
||||||
|
| `#[ApiResource]` | API Platform génère les endpoints CRUD |
|
||||||
|
| `#[Groups]` | Contrôle quels champs sont visibles/modifiables |
|
||||||
|
| `security: "is_granted('ROLE_X')"` | Qui a le droit d'utiliser cet endpoint |
|
||||||
|
|
||||||
|
### Le trait CuidEntityTrait
|
||||||
|
|
||||||
|
Toutes les entités utilisent un trait partagé qui génère les IDs et gère les timestamps :
|
||||||
|
|
||||||
|
```php
|
||||||
|
// src/Entity/Trait/CuidEntityTrait.php
|
||||||
|
trait CuidEntityTrait
|
||||||
|
{
|
||||||
|
#[ORM\PrePersist] // ← S'exécute automatiquement AVANT l'insertion en BDD
|
||||||
|
public function generateId(): void
|
||||||
|
{
|
||||||
|
if (!isset($this->id)) {
|
||||||
|
$this->id = 'cl' . bin2hex(random_bytes(12)); // ← Génère un ID unique de 26 chars
|
||||||
|
}
|
||||||
|
$this->createdAt = new \DateTimeImmutable();
|
||||||
|
$this->updatedAt = new \DateTimeImmutable();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ORM\PreUpdate] // ← S'exécute automatiquement AVANT une mise à jour
|
||||||
|
public function updateTimestamp(): void
|
||||||
|
{
|
||||||
|
$this->updatedAt = new \DateTimeImmutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Les entités du projet
|
||||||
|
|
||||||
|
#### Entités "catalogue" (les éléments qu'on gère)
|
||||||
|
|
||||||
|
| Entité | Table | Champs clés | Relations |
|
||||||
|
|--------|-------|-------------|-----------|
|
||||||
|
| **Machine** | `machine` | name, reference, prix | → Site, ↔ Constructeur, → Documents |
|
||||||
|
| **Composant** | `composant` | name, reference, description, prix, structure (JSON) | → ModelType, → Product, ↔ Constructeur |
|
||||||
|
| **Piece** | `piece` | name, reference, description, prix, productIds (JSON) | → ModelType, → Product, ↔ Constructeur |
|
||||||
|
| **Product** | `product` | name, reference, supplierPrice | → ModelType, ↔ Constructeur |
|
||||||
|
|
||||||
|
#### Entités de classification
|
||||||
|
|
||||||
|
| Entité | Table | Champs clés | Rôle |
|
||||||
|
|--------|-------|-------------|------|
|
||||||
|
| **Site** | `site` | name, contactName, contactPhone, contactAddress | Regrouper les machines par lieu |
|
||||||
|
| **Constructeur** | `constructeur` | name, email, phone | Fournisseurs/fabricants partagés |
|
||||||
|
| **ModelType** | `model_type` | name, code, category (enum), skeletons (JSON) | Catégoriser composants/pièces/produits |
|
||||||
|
|
||||||
|
#### Entités de liaison hiérarchique (structure machine)
|
||||||
|
|
||||||
|
| Entité | Rôle | Relations |
|
||||||
|
|--------|------|-----------|
|
||||||
|
| **MachineComponentLink** | Lie un composant à une machine | → Machine, → Composant, → parent (self) |
|
||||||
|
| **MachinePieceLink** | Lie une pièce à une machine | → Machine, → Piece, → parent composant |
|
||||||
|
| **MachineProductLink** | Lie un produit à une machine | → Machine, → Product, → parent (flexible) |
|
||||||
|
|
||||||
|
Ces entités permettent la **structure arborescente** : un composant peut contenir des pièces, qui contiennent des produits.
|
||||||
|
|
||||||
|
#### Entités de métadonnées
|
||||||
|
|
||||||
|
| Entité | Rôle |
|
||||||
|
|--------|------|
|
||||||
|
| **CustomField** | Définition d'un champ personnalisé (nom, type, options) |
|
||||||
|
| **CustomFieldValue** | Valeur d'un champ personnalisé pour une entité donnée |
|
||||||
|
| **Document** | Fichier uploadé (PDF, image) rattaché à une entité |
|
||||||
|
| **AuditLog** | Entrée du journal d'audit (diff + snapshot) |
|
||||||
|
| **Comment** | Commentaire/ticket sur une fiche |
|
||||||
|
| **Profile** | Compte utilisateur (email, rôle, mot de passe hashé) |
|
||||||
|
|
||||||
|
### Les relations entre entités (schéma simplifié)
|
||||||
|
|
||||||
|
```
|
||||||
|
Site ──1:N──► Machine ──1:N──► MachineComponentLink ──► Composant
|
||||||
|
│ │
|
||||||
|
│ └──1:N──► MachinePieceLink ──► Piece
|
||||||
|
│ │
|
||||||
|
│ └──1:N──► MachineProductLink ──► Product
|
||||||
|
│
|
||||||
|
└──N:N──► Constructeur (via table de jointure)
|
||||||
|
|
||||||
|
ModelType ──1:N──► Composant / Piece / Product
|
||||||
|
│
|
||||||
|
└──► CustomField ──1:N──► CustomFieldValue
|
||||||
|
|
||||||
|
Machine / Composant / Piece / Product ──1:N──► Document
|
||||||
|
──1:N──► CustomFieldValue
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Les Controllers
|
||||||
|
|
||||||
|
API Platform génère automatiquement les endpoints CRUD standard. Les controllers personnalisés gèrent les cas plus complexes.
|
||||||
|
|
||||||
|
### Liste des controllers
|
||||||
|
|
||||||
|
#### Authentification (3 controllers)
|
||||||
|
|
||||||
|
**SessionProfileController** (`/api/session/profile`) — Login/Logout
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/session/profile → Se connecter (payload: { profileId, password })
|
||||||
|
GET /api/session/profile → Récupérer le profil connecté
|
||||||
|
DELETE /api/session/profile → Se déconnecter
|
||||||
|
```
|
||||||
|
|
||||||
|
**SessionProfilesController** (`/api/session/profiles`) — Liste des profils
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/session/profiles → Liste tous les profils actifs (page de login)
|
||||||
|
```
|
||||||
|
|
||||||
|
**AdminProfileController** (`/api/admin/profiles`) — Administration des utilisateurs
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/admin/profiles → Liste tous les profils (ADMIN only)
|
||||||
|
POST /api/admin/profiles → Créer un profil
|
||||||
|
PUT /api/admin/profiles/{id}/role → Changer le rôle d'un profil
|
||||||
|
PUT /api/admin/profiles/{id}/password → Réinitialiser un mot de passe
|
||||||
|
PUT /api/admin/profiles/{id}/deactivate → Désactiver un profil
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Données et logique métier
|
||||||
|
|
||||||
|
**MachineStructureController** — Structure hiérarchique des machines
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/machines/{id}/structure → Récupérer l'arborescence complète
|
||||||
|
PATCH /api/machines/{id}/structure → Modifier l'arborescence
|
||||||
|
POST /api/machines/{id}/clone → Cloner une machine avec toute sa structure
|
||||||
|
```
|
||||||
|
|
||||||
|
**MachineCustomFieldsController** — Champs personnalisés machines
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/machines/{id}/custom-fields/init → Initialiser les champs personnalisés manquants
|
||||||
|
```
|
||||||
|
|
||||||
|
**EntityHistoryController** — Historique d'audit par entité
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/{entityType}/{id}/history → 200 derniers événements d'audit
|
||||||
|
```
|
||||||
|
|
||||||
|
**ActivityLogController** — Journal d'activité global
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/activity-log → Liste paginée avec filtres (entityType, action)
|
||||||
|
```
|
||||||
|
|
||||||
|
**CommentController** — Commentaires/tickets
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/comments → Créer un commentaire
|
||||||
|
PATCH /api/comments/{id}/resolve → Résoudre un commentaire
|
||||||
|
GET /api/comments/unresolved-count → Nombre de commentaires non résolus
|
||||||
|
```
|
||||||
|
|
||||||
|
**CustomFieldValueController** — Valeurs de champs personnalisés
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/custom-field-values → Créer/mettre à jour une valeur (upsert)
|
||||||
|
DELETE /api/custom-field-values/{id} → Supprimer une valeur
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fichiers
|
||||||
|
|
||||||
|
**DocumentQueryController** — Requêter les documents par entité
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/documents/by-site/{id} → Documents d'un site
|
||||||
|
GET /api/documents/by-machine/{id} → Documents d'une machine
|
||||||
|
GET /api/documents/by-composant/{id} → Documents d'un composant
|
||||||
|
GET /api/documents/by-piece/{id} → Documents d'une pièce
|
||||||
|
GET /api/documents/by-product/{id} → Documents d'un produit
|
||||||
|
```
|
||||||
|
|
||||||
|
**DocumentServeController** — Servir les fichiers
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/documents/{id}/file → Afficher le fichier (inline)
|
||||||
|
GET /api/documents/{id}/download → Télécharger le fichier (attachment)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Monitoring
|
||||||
|
|
||||||
|
**HealthCheckController** — Vérification de santé
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/health → Version, latence BDD, mémoire, version PHP
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exemple de controller commenté
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
// src/Controller/CommentController.php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Comment;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
class CommentController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/api/comments', methods: ['POST'])] // ← Définit l'URL et le verbe HTTP
|
||||||
|
public function create(
|
||||||
|
Request $request, // ← La requête HTTP entrante
|
||||||
|
EntityManagerInterface $em, // ← Pour écrire en BDD (injecté automatiquement)
|
||||||
|
): JsonResponse {
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER'); // ← Vérifie que l'utilisateur est connecté
|
||||||
|
|
||||||
|
$data = json_decode($request->getContent(), true); // ← Parse le body JSON
|
||||||
|
|
||||||
|
$comment = new Comment();
|
||||||
|
$comment->setContent($data['content']);
|
||||||
|
$comment->setEntityType($data['entityType']);
|
||||||
|
$comment->setEntityId($data['entityId']);
|
||||||
|
// ... autres champs
|
||||||
|
|
||||||
|
$em->persist($comment); // ← Dit à Doctrine "je veux sauvegarder ça"
|
||||||
|
$em->flush(); // ← Exécute réellement le INSERT SQL
|
||||||
|
|
||||||
|
return $this->json($comment, 201); // ← Renvoie le commentaire créé avec le code 201
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Le système d'audit
|
||||||
|
|
||||||
|
Chaque modification sur les entités principales est automatiquement enregistrée dans un journal d'audit. C'est un des points forts de l'application.
|
||||||
|
|
||||||
|
### Comment ça marche ?
|
||||||
|
|
||||||
|
Les **Event Subscribers** de Doctrine interceptent les opérations de base de données **avant** qu'elles soient exécutées (événement `onFlush`).
|
||||||
|
|
||||||
|
```
|
||||||
|
L'utilisateur modifie une machine
|
||||||
|
↓
|
||||||
|
Doctrine détecte le changement
|
||||||
|
↓
|
||||||
|
onFlush se déclenche
|
||||||
|
↓
|
||||||
|
Le subscriber calcule le diff (ancien → nouveau)
|
||||||
|
↓
|
||||||
|
Le subscriber crée un AuditLog avec :
|
||||||
|
- entityType : "machine"
|
||||||
|
- entityId : "cl1a2b3c..."
|
||||||
|
- action : "update"
|
||||||
|
- diff : { "name": { "from": "CNC 01", "to": "CNC 02" } }
|
||||||
|
- snapshot : { état complet de la machine }
|
||||||
|
- actorProfileId : "cl9z8y7x..." (qui a fait la modif)
|
||||||
|
↓
|
||||||
|
Les deux (machine + audit log) sont sauvegardés en même temps
|
||||||
|
```
|
||||||
|
|
||||||
|
### Le diff
|
||||||
|
|
||||||
|
Le diff capture exactement ce qui a changé :
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": { "from": "CNC Mazak 01", "to": "CNC Mazak 02" },
|
||||||
|
"prix": { "from": "45000.00", "to": "50000.00" },
|
||||||
|
"constructeurIds": {
|
||||||
|
"from": ["cl111...", "cl222..."],
|
||||||
|
"to": ["cl111...", "cl333..."]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Le snapshot
|
||||||
|
|
||||||
|
Le snapshot capture l'état complet de l'entité au moment de la modification :
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "cl1a2b3c...",
|
||||||
|
"name": "CNC Mazak 02",
|
||||||
|
"reference": "CNM-001",
|
||||||
|
"prix": "50000.00",
|
||||||
|
"siteId": "cl9z8y7x...",
|
||||||
|
"constructeurIds": ["cl111...", "cl333..."]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Les subscribers d'audit
|
||||||
|
|
||||||
|
| Subscriber | Entité | Type |
|
||||||
|
|------------|--------|------|
|
||||||
|
| MachineAuditSubscriber | Machine | Complex (avec constructeurs + custom fields) |
|
||||||
|
| ComposantAuditSubscriber | Composant | Complex |
|
||||||
|
| PieceAuditSubscriber | Piece | Complex |
|
||||||
|
| ProductAuditSubscriber | Product | Complex |
|
||||||
|
| ConstructeurAuditSubscriber | Constructeur | Simple |
|
||||||
|
| DocumentAuditSubscriber | Document | Simple |
|
||||||
|
| ModelTypeAuditSubscriber | ModelType | Simple |
|
||||||
|
|
||||||
|
**Simple** = suit seulement les champs de l'entité
|
||||||
|
**Complex** = suit aussi les relations ManyToMany (constructeurs) et les champs personnalisés
|
||||||
|
|
||||||
|
### AbstractAuditSubscriber
|
||||||
|
|
||||||
|
La classe de base qui contient toute la logique partagée :
|
||||||
|
|
||||||
|
```php
|
||||||
|
abstract class AbstractAuditSubscriber implements EventSubscriber
|
||||||
|
{
|
||||||
|
// Méthode à implémenter par chaque subscriber
|
||||||
|
abstract protected function getEntityClass(): string; // Ex: Machine::class
|
||||||
|
abstract protected function getEntityType(): string; // Ex: 'machine'
|
||||||
|
abstract protected function buildSnapshot($entity): array; // Construit le snapshot
|
||||||
|
|
||||||
|
// Deux chemins d'exécution :
|
||||||
|
// 1. onFlushSimple() : pour les entités sans collections ManyToMany
|
||||||
|
// 2. onFlushComplex() : pour les entités avec constructeurs (détecte les ajouts/suppressions)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Autres subscribers
|
||||||
|
|
||||||
|
| Subscriber | Rôle |
|
||||||
|
|------------|------|
|
||||||
|
| **PieceProductSyncSubscriber** | Synchronise le champ `productIds` sur Piece quand un Product est lié/délié |
|
||||||
|
| **UniqueConstraintSubscriber** | Capture les erreurs de doublon PostgreSQL et renvoie un message clair |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## L'authentification par session
|
||||||
|
|
||||||
|
### Le flux complet
|
||||||
|
|
||||||
|
```
|
||||||
|
1. GET /api/session/profiles
|
||||||
|
→ Retourne la liste des profils actifs (nom, prénom, email, hasPassword)
|
||||||
|
→ Le frontend affiche la page de login avec les profils disponibles
|
||||||
|
|
||||||
|
2. POST /api/session/profile
|
||||||
|
Body: { "profileId": "cl...", "password": "secret" }
|
||||||
|
→ Le backend vérifie le mot de passe
|
||||||
|
→ Si OK : stocke profileId dans la session PHP, retourne le profil
|
||||||
|
→ Si KO : retourne 401
|
||||||
|
|
||||||
|
3. GET /api/session/profile (à chaque chargement de page)
|
||||||
|
→ Le navigateur envoie le cookie de session automatiquement
|
||||||
|
→ Le backend retrouve le profil via la session
|
||||||
|
→ Retourne le profil connecté ou 401
|
||||||
|
|
||||||
|
4. DELETE /api/session/profile
|
||||||
|
→ Supprime le profileId de la session
|
||||||
|
→ L'utilisateur est déconnecté
|
||||||
|
```
|
||||||
|
|
||||||
|
### La sécurité sur les endpoints
|
||||||
|
|
||||||
|
Chaque endpoint API Platform a une règle de sécurité :
|
||||||
|
|
||||||
|
```php
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')") // Lecture → minimum ROLE_VIEWER
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')") // Création → minimum ROLE_GESTIONNAIRE
|
||||||
|
```
|
||||||
|
|
||||||
|
Les controllers personnalisés utilisent :
|
||||||
|
```php
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
```
|
||||||
|
|
||||||
|
### La hiérarchie des rôles
|
||||||
|
|
||||||
|
Grâce à la hiérarchie, un ADMIN a automatiquement tous les rôles inférieurs :
|
||||||
|
|
||||||
|
```
|
||||||
|
ROLE_ADMIN ─── a aussi ──► ROLE_GESTIONNAIRE ──► ROLE_VIEWER ──► ROLE_USER
|
||||||
|
```
|
||||||
|
|
||||||
|
Donc `is_granted('ROLE_VIEWER')` accepte aussi les GESTIONNAIRES et les ADMINS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Les services
|
||||||
|
|
||||||
|
### DocumentStorageService
|
||||||
|
|
||||||
|
Gère le stockage des fichiers sur le système de fichiers :
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Stocker un fichier uploadé
|
||||||
|
$path = $storageService->store($uploadedFile, $entityType, $entityId);
|
||||||
|
|
||||||
|
// Supprimer un fichier
|
||||||
|
$storageService->delete($path);
|
||||||
|
```
|
||||||
|
|
||||||
|
Les fichiers sont stockés dans `var/documents/{entityType}/{entityId}/{filename}`.
|
||||||
|
|
||||||
|
### PdfCompressorService
|
||||||
|
|
||||||
|
Compresse les fichiers PDF via Ghostscript pour réduire leur taille :
|
||||||
|
|
||||||
|
```php
|
||||||
|
$compressorService->compress($filePath);
|
||||||
|
```
|
||||||
|
|
||||||
|
### ModelTypeCategoryConversionService
|
||||||
|
|
||||||
|
Permet de convertir la catégorie d'un ModelType (ex: transformer un type "composant" en type "pièce").
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Les migrations
|
||||||
|
|
||||||
|
Les migrations sont des scripts SQL qui modifient la structure de la base de données. Elles sont dans le dossier `migrations/`.
|
||||||
|
|
||||||
|
### Principe
|
||||||
|
|
||||||
|
Quand tu ajoutes un champ à une entité, il faut créer une migration pour mettre à jour la BDD :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Générer une migration à partir des changements détectés
|
||||||
|
make shell
|
||||||
|
php bin/console doctrine:migrations:diff
|
||||||
|
|
||||||
|
# Appliquer les migrations
|
||||||
|
php bin/console doctrine:migrations:migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Particularités PostgreSQL
|
||||||
|
|
||||||
|
Les migrations utilisent du **SQL brut** avec des gardes pour l'idempotence :
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- On peut relancer la migration sans erreur
|
||||||
|
ALTER TABLE machine ADD COLUMN IF NOT EXISTS description TEXT;
|
||||||
|
DROP INDEX IF EXISTS idx_machine_name;
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_machine_name ON machine (name);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Attention aux noms de colonnes** : PostgreSQL stocke tout en **minuscules**. Donc `typePieceId` en PHP devient `typepieceid` en SQL. Toujours utiliser des noms lowercase dans le SQL brut.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Les tests
|
||||||
|
|
||||||
|
### Stack de test
|
||||||
|
|
||||||
|
- **PHPUnit 12** : framework de test PHP
|
||||||
|
- **API Platform Test** : utilitaires pour tester des endpoints API
|
||||||
|
- **DAMA DoctrineTestBundle** : wrappe chaque test dans une transaction avec rollback automatique (pas besoin de nettoyer la BDD entre les tests)
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
tests/
|
||||||
|
├── AbstractApiTestCase.php # Classe de base avec helpers
|
||||||
|
└── Api/
|
||||||
|
└── Entity/
|
||||||
|
├── MachineTest.php # Tests des endpoints machine
|
||||||
|
├── SiteTest.php # Tests des endpoints site
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exemple de test
|
||||||
|
|
||||||
|
```php
|
||||||
|
class MachineTest extends AbstractApiTestCase
|
||||||
|
{
|
||||||
|
public function testCreateMachine(): void
|
||||||
|
{
|
||||||
|
// Créer un client HTTP connecté en tant que gestionnaire
|
||||||
|
$client = $this->createGestionnaireClient();
|
||||||
|
|
||||||
|
// Créer un site (prérequis)
|
||||||
|
$site = $this->createSite();
|
||||||
|
|
||||||
|
// Envoyer une requête POST pour créer une machine
|
||||||
|
$client->request('POST', '/api/machines', [
|
||||||
|
'json' => [
|
||||||
|
'name' => 'Machine Test',
|
||||||
|
'reference' => 'MT-001',
|
||||||
|
'site' => '/api/sites/' . $site->getId(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Vérifier que la réponse est 201 Created
|
||||||
|
$this->assertResponseStatusCodeSame(201);
|
||||||
|
|
||||||
|
// Vérifier le contenu de la réponse
|
||||||
|
$this->assertJsonContains([
|
||||||
|
'name' => 'Machine Test',
|
||||||
|
'reference' => 'MT-001',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Helpers disponibles dans AbstractApiTestCase
|
||||||
|
|
||||||
|
| Méthode | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `createViewerClient()` | Client HTTP connecté avec ROLE_VIEWER |
|
||||||
|
| `createGestionnaireClient()` | Client HTTP connecté avec ROLE_GESTIONNAIRE |
|
||||||
|
| `createAdminClient()` | Client HTTP connecté avec ROLE_ADMIN |
|
||||||
|
| `createProfile()` | Crée un profil utilisateur en BDD |
|
||||||
|
| `createSite()` | Crée un site en BDD |
|
||||||
|
| `createMachine()` | Crée une machine en BDD |
|
||||||
|
|
||||||
|
### Lancer les tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test # Tous les tests
|
||||||
|
make test FILES=tests/Api/Entity/MachineTest.php # Un fichier
|
||||||
|
make test-setup # (Re)créer la BDD de test
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Flux complet d'une requête
|
||||||
|
|
||||||
|
### Exemple : créer une machine
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Le frontend envoie :
|
||||||
|
POST /api/machines
|
||||||
|
Content-Type: application/ld+json
|
||||||
|
Cookie: PHPSESSID=abc123
|
||||||
|
{
|
||||||
|
"name": "CNC Mazak 01",
|
||||||
|
"reference": "CNM-001",
|
||||||
|
"prix": "50000.00",
|
||||||
|
"site": "/api/sites/cl9z8y7x..."
|
||||||
|
}
|
||||||
|
|
||||||
|
2. Symfony reçoit la requête
|
||||||
|
→ Le routeur identifie : c'est un endpoint API Platform (POST sur Machine)
|
||||||
|
|
||||||
|
3. Sécurité
|
||||||
|
→ Vérifie le cookie de session → retrouve le profil connecté
|
||||||
|
→ Vérifie is_granted('ROLE_GESTIONNAIRE') → OK
|
||||||
|
|
||||||
|
4. Désérialisation (JSON → objet PHP)
|
||||||
|
→ API Platform convertit le JSON en objet Machine
|
||||||
|
→ Le champ "site" (IRI) est résolu en objet Site
|
||||||
|
→ Seuls les champs du groupe 'machine:write' sont acceptés
|
||||||
|
|
||||||
|
5. Validation
|
||||||
|
→ Vérifie les contraintes (name non vide, site existe, etc.)
|
||||||
|
|
||||||
|
6. Persistence (objet PHP → BDD)
|
||||||
|
→ Doctrine déclenche PrePersist (CuidEntityTrait)
|
||||||
|
→ Génère l'ID : "cl" + 24 hex chars aléatoires
|
||||||
|
→ Set createdAt et updatedAt
|
||||||
|
→ Doctrine détecte l'INSERT à faire
|
||||||
|
|
||||||
|
7. Audit (onFlush)
|
||||||
|
→ MachineAuditSubscriber détecte la nouvelle machine
|
||||||
|
→ Crée un AuditLog avec action='create', diff, snapshot
|
||||||
|
→ L'AuditLog est aussi ajouté à la transaction
|
||||||
|
|
||||||
|
8. Flush
|
||||||
|
→ Doctrine exécute les requêtes SQL :
|
||||||
|
INSERT INTO machine (id, name, reference, ...) VALUES (...)
|
||||||
|
INSERT INTO audit_log (id, entity_type, entity_id, action, diff, snapshot, ...) VALUES (...)
|
||||||
|
|
||||||
|
9. Sérialisation (objet PHP → JSON)
|
||||||
|
→ API Platform convertit la Machine en JSON-LD
|
||||||
|
→ Seuls les champs du groupe 'machine:read' sont inclus
|
||||||
|
|
||||||
|
10. Réponse
|
||||||
|
HTTP/1.1 201 Created
|
||||||
|
{
|
||||||
|
"@context": "/api/contexts/Machine",
|
||||||
|
"@id": "/api/machines/cl1a2b3c...",
|
||||||
|
"@type": "Machine",
|
||||||
|
"id": "cl1a2b3c...",
|
||||||
|
"name": "CNC Mazak 01",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commandes Symfony utiles
|
||||||
|
|
||||||
|
Lancer ces commandes dans le conteneur Docker (`make shell` pour y entrer) :
|
||||||
|
|
||||||
|
| Commande | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `php bin/console debug:router` | Voir toutes les routes disponibles |
|
||||||
|
| `php bin/console debug:config api_platform` | Voir la config API Platform |
|
||||||
|
| `php bin/console doctrine:schema:validate` | Vérifier que les entités sont synchronisées avec la BDD |
|
||||||
|
| `php bin/console doctrine:migrations:diff` | Générer une migration à partir des changements |
|
||||||
|
| `php bin/console doctrine:migrations:migrate` | Appliquer les migrations |
|
||||||
|
| `php bin/console cache:clear` | Vider le cache (résout beaucoup de problèmes) |
|
||||||
|
| `php bin/console app:compress-pdf` | Compresser les PDFs existants |
|
||||||
|
| `php bin/console app:create-profile` | Créer un profil utilisateur |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Résumé des points clés pour un débutant
|
||||||
|
|
||||||
|
1. **API Platform génère les endpoints CRUD automatiquement** à partir des entités — tu n'as pas besoin d'écrire de controllers pour les opérations standard
|
||||||
|
2. **Les attributs PHP 8** (`#[...]`) remplacent les annotations et configurent tout : BDD, API, sérialisation, sécurité
|
||||||
|
3. **Les groupes de sérialisation** (`machine:read`, `machine:write`) contrôlent quels champs sont visibles/modifiables
|
||||||
|
4. **L'audit est automatique** : chaque modification est tracée sans rien avoir à faire manuellement
|
||||||
|
5. **L'authentification est par session (cookies)**, pas par tokens JWT
|
||||||
|
6. **Les IDs sont des CUID** (chaînes aléatoires), pas des auto-increment
|
||||||
|
7. **PostgreSQL stocke les noms en minuscules** : attention dans le SQL brut
|
||||||
|
8. **Les tests utilisent des transactions** : chaque test est isolé et la BDD est nettoyée automatiquement
|
||||||
1052
docs/FRONTEND.md
Normal file
1052
docs/FRONTEND.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user