diff --git a/.gitignore b/.gitignore index 075a0c2..b543d46 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,18 @@ ###> docker ### docker/.env.docker.local ###< docker ### + +###> lexik/jwt-authentication-bundle ### +/config/jwt/*.pem +###< lexik/jwt-authentication-bundle ### + +###> migration archives ### +/_archives/ +###< migration archives ### + +###> frontend ### +/frontend/node_modules/ +/frontend/.nuxt/ +/frontend/.output/ +/frontend/dist/ +###< frontend ### diff --git a/CARNET_DE_BORD.md b/CARNET_DE_BORD.md new file mode 100644 index 0000000..fedc799 --- /dev/null +++ b/CARNET_DE_BORD.md @@ -0,0 +1,355 @@ +# 📔 Carnet de Bord - Migration Inventory → Symfony + +**Projet** : Migration backend NestJS/Prisma → Symfony/API Platform +**DĂ©but** : 2026-01-10 +**Objectif** : Migrer vers Symfony + JWT + API Platform propre et maintenable + +--- + +## 🎯 Contexte + +- **Situation initiale** : + - `Inventory_backend/` : NestJS + Prisma (fonctionnel, ~11k lignes) + - `Inventory_frontend/` : Nuxt 3 (fonctionnel, 105 fichiers) + - Base de donnĂ©es PostgreSQL avec donnĂ©es en production + +- **Objectif** : + - Backend Symfony 8 + API Platform + JWT + - Garder les donnĂ©es existantes (migration Prisma → Doctrine) + - Frontend Nuxt connectĂ© au nouveau backend + - Docker : 2 backends en parallĂšle pendant transition + +--- + +## ✅ Phase 1 : PrĂ©paration (TERMINÉE - 10/01/2026) + +### Ce qui a Ă©tĂ© fait + +#### 1. Docker & Infrastructure ✅ +- **pgAdmin ajoutĂ©** au docker-compose.yml + - Port : 5050 + - Login : admin@admin.com / admin + - Container : `pgadmin-inventory` + - Volume persistant : `pgadmin_data` + - **Serveur PostgreSQL prĂ©-configurĂ©** : + - Fichier `docker/pgadmin/servers.json` montĂ© automatiquement + - Fichier `docker/pgadmin/pgpass` pour authentification sans mot de passe + - Connexion automatique Ă  `db:5432/inventory` au dĂ©marrage + - Nom du serveur : "Inventory PostgreSQL" + +#### 2. Bundles Symfony installĂ©s ✅ +```bash +# Versions installĂ©es +- lexik/jwt-authentication-bundle: v3.2.0 +- vich/uploader-bundle: v2.9.1 +- symfony/uid: 8.0.* +``` + +#### 3. JWT Configuration ✅ +- **ClĂ©s RSA gĂ©nĂ©rĂ©es** : `config/jwt/private.pem` + `public.pem` +- **security.yaml configurĂ©** : + - Firewall `login` : pattern `^/api/login_check` avec `json_login` + - Firewall `api` : pattern `^/api` avec `jwt` authenticator + - Provider : `app_user_provider` (entitĂ© Profile via email) + - Password hasher : bcrypt auto + +#### 4. EntitĂ© Profile créée ✅ +**Fichier** : `src/Entity/Profile.php` + +**CaractĂ©ristiques** : +- ImplĂ©mente `UserInterface` + `PasswordAuthenticatedUserInterface` +- Champs : + - `id` : string (30 chars, CUID-compatible pour Prisma) + - `email` : string unique (username pour JWT) + - `password` : string (hashed) + - `roles` : array JSON (ROLE_USER par dĂ©faut) + - `firstName`, `lastName` : string + - `isActive` : boolean + - `createdAt`, `updatedAt` : DateTimeImmutable +- Repository : `ProfileRepository` avec `PasswordUpgraderInterface` +- API Platform : endpoints CRUD auto-gĂ©nĂ©rĂ©s + +#### 5. Base de DonnĂ©es ✅ +- **Migration créée** : `Version20260110175413` +- **Table** : `profiles` créée avec succĂšs +- **Utilisateur test créé** : + ``` + Email: admin@admin.com + Password: admin123 + Roles: ['ROLE_USER', 'ROLE_ADMIN'] + ``` + +#### 6. API Platform ✅ +- **Endpoint racine** : http://localhost:8081/api/ +- **RĂ©ponse** : + ```json + { + "@context": "/api/contexts/Entrypoint", + "@id": "/api/", + "@type": "Entrypoint", + "profile": "/api/profiles" + } + ``` +- **OpenAPI Docs** : ConfigurĂ©es (Ă  tester) + +#### 7. Configuration Apache ✅ +- **VirtualHost** : `docker/php/config/vhost.conf` +- **DocumentRoot** : `/var/www/html/public` +- **AllowOverride** : All (pour `.htaccess`) +- **Port** : 8081 (Apache) → accessible depuis l'hĂŽte + +#### 8. Routing Symfony ✅ +- **Routes dĂ©finies** : + - `/api/login_check` : Login JWT + - `/api/test` : Test endpoint (TestController) + - `/api/*` : API Platform auto-routes +- **VĂ©rification** : + ```bash + php bin/console debug:router api_test + php bin/console router:match /api/test --method=GET + # ✅ Route found and matches + ``` + +#### 9. .htaccess créé ✅ +**Fichier** : `public/.htaccess` + +**Contenu** : Symfony standard avec mod_rewrite + +--- + +### ⚠ ProblĂšmes identifiĂ©s + +#### 1. Routes inaccessibles via Apache (404) + +**SymptĂŽme** : +```bash +curl http://localhost:8081/api/test +# → 404 Not Found +``` + +**Tests effectuĂ©s** : +- ✅ Route existe : `php bin/console debug:router api_test` +- ✅ Route match : `php bin/console router:match /api/test` +- ✅ Symfony fonctionne : `curl http://localhost:8081/api/` → JSON OK +- ✅ PHP built-in server OK : + ```bash + php -S localhost:9000 + curl http://localhost:9000/api/test + # → {"status":"ok","message":"Test endpoint works!"} + ``` +- ❌ Apache 404 : Depuis l'hĂŽte via port 8081 + +**Diagnostic** : +- Le problĂšme est **Apache-spĂ©cifique** +- Symfony/PHP fonctionnent correctement +- Le `.htaccess` n'est probablement **PAS lu par Apache** +- HypothĂšses : + 1. `AllowOverride All` non pris en compte + 2. `mod_rewrite` mal configurĂ© + 3. Ordre des directives Apache incorrect + 4. ProblĂšme de permissions sur `.htaccess` + +**Actions Ă  faire** : +- [ ] VĂ©rifier permissions `.htaccess` dans container +- [ ] Tester `apache2ctl configtest` +- [ ] Activer logs de rewrite : `LogLevel alert rewrite:trace3` +- [ ] Tester FallbackResource dans vhost au lieu de `.htaccess` + +#### 2. JWT Login non testĂ© + +**Raison** : BloquĂ© par problĂšme #1 (routes inaccessibles) + +**Actions Ă  faire** : +- [ ] RĂ©soudre problĂšme Apache +- [ ] Tester `POST /api/login_check` avec credentials +- [ ] VĂ©rifier gĂ©nĂ©ration du token JWT +- [ ] Tester route protĂ©gĂ©e avec token + +--- + +## 📝 Configuration Actuelle + +### Docker Compose + +```yaml +services: + web: + ports: + - "8081:80" # Symfony API + - "3001:3000" # (prĂ©vu pour Nuxt) + + db: + ports: + - "5433:5432" # PostgreSQL + + pgadmin: + ports: + - "5050:80" # pgAdmin +``` + +### Variables d'Environnement + +**Fichier** : `docker/.env.docker.local` + +```env +# PostgreSQL +POSTGRES_DB=inventory +POSTGRES_USER=root +POSTGRES_PASSWORD=root +POSTGRES_PORT=5433 + +# pgAdmin +PGADMIN_EMAIL=admin@admin.com +PGADMIN_PASSWORD=admin +PGADMIN_PORT=5050 + +# Symfony +APP_ENV=dev +APP_SECRET=changeme_super_secret_key_123456789 +JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem +JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem +JWT_PASSPHRASE=your_jwt_passphrase_change_me + +# NestJS (pour futur parallĂšle) +NESTJS_PORT=3000 +SESSION_SECRET=changeme_session_secret +CORS_ORIGIN=http://localhost:3001 +``` + +--- + +## 🚧 Phase 2 : Debugging & Tests (EN COURS) + +### Objectifs +- [x] RĂ©soudre problĂšme Apache `.htaccess` +- [ ] Tester authentification JWT complĂšte +- [ ] CrĂ©er endpoint de test public fonctionnel +- [ ] Documenter la solution Apache + +### Prochaines Ă©tapes +1. **Fix Apache** : Logs de debug + test FallbackResource +2. **Test JWT** : Login + gĂ©nĂ©ration token + route protĂ©gĂ©e +3. **Documentation** : Documenter la config Apache qui fonctionne + +--- + +## 📊 MĂ©triques + +### Temps passĂ© +- **Phase 1** : ~3h (exploration + setup + debugging) +- **ProblĂšme Apache** : ~1h30 (en cours) + +### Fichiers créés/modifiĂ©s + +**Nouveaux fichiers** : +- `src/Entity/Profile.php` +- `src/Repository/ProfileRepository.php` +- `src/Controller/TestController.php` +- `public/.htaccess` +- `config/routes/routing.controllers.yaml` +- `create_test_user.php` (script utilitaire) +- `migrations/Version20260110175413.php` +- `docker/pgadmin/servers.json` (config serveur PostgreSQL) +- `docker/pgadmin/pgpass` (credentials PostgreSQL) +- `CARNET_DE_BORD.md` (ce fichier) + +**Fichiers modifiĂ©s** : +- `docker-compose.yml` (+ pgAdmin) +- `docker/.env.docker.local` (+ variables Symfony/JWT/pgAdmin) +- `docker/php/config/vhost.conf` (DocumentRoot → public/) +- `config/packages/security.yaml` (JWT firewalls) +- `config/routes.yaml` (+ api_login_check) +- `composer.json` (+ lexik JWT, vich uploader) + +--- + +## 🎓 Leçons Apprises + +### 1. Symfony 8 + API Platform +- **Attributs PHP 8** : `use Symfony\Component\Routing\Attribute\Route;` (pas `Annotation`) +- **Routes controllers** : NĂ©cessite `config/routes/routing.controllers.yaml` avec `type: attribute` +- **API Platform** : Auto-gĂ©nĂšre les endpoints CRUD avec `#[ApiResource]` + +### 2. JWT Authentication +- **3 composants** : + 1. Firewall `login` : `json_login` intercepte `/api/login_check` + 2. Firewall `api` : `jwt` vĂ©rifie le token sur `/api/*` + 3. Access control : `PUBLIC_ACCESS` vs `IS_AUTHENTICATED_FULLY` +- **username_path** : Permet de mapper `email` au lieu de `username` +- **Provider** : Doit ĂȘtre dĂ©fini dans le firewall `login` + +### 3. Doctrine Migrations +- **ID Prisma CUID** : Garder en `string(30)` pour compatibilitĂ© +- **Lifecycle callbacks** : `#[ORM\PrePersist]` pour `createdAt`/`updatedAt` +- **UserInterface** : NĂ©cessite `getUserIdentifier()`, `getRoles()`, `eraseCredentials()` + +### 4. Docker & Apache +- **`.htaccess` vs VirtualHost** : Le vhost peut override le `.htaccess` +- **AllowOverride All** : Indispensable pour que `.htaccess` fonctionne +- **FallbackResource** : Alternative au mod_rewrite dans `.htaccess` +- **Debugging** : Tester avec PHP built-in server pour isoler le problĂšme + +--- + +## 📚 Ressources Utiles + +### AccĂšs aux Services + +``` +🌐 pgAdmin: http://localhost:5050 + └─ Login: admin@admin.com / admin + └─ Serveur: "Inventory PostgreSQL" (prĂ©-configurĂ©) + └─ Database: inventory + └─ Note: Le serveur PostgreSQL est automatiquement connectĂ© au dĂ©marrage + +🌐 API Platform: http://localhost:8081/api/ + └─ Docs: http://localhost:8081/api/docs (Ă  venir) + +đŸ—„ïž PostgreSQL: localhost:5433 + └─ User: root / root + └─ Database: inventory +``` + +### Commandes frĂ©quentes + +```bash +# Symfony +make shell # Entrer dans le container +php bin/console cache:clear # Clear cache +php bin/console debug:router # Lister routes +php bin/console debug:firewall # Lister firewalls +php bin/console doctrine:migrations:migrate # ExĂ©cuter migrations + +# Docker +make start # DĂ©marrer containers +make stop # ArrĂȘter containers +docker logs -f php-inventory-apache # Logs Apache +docker logs -f pgadmin-inventory # Logs pgAdmin +docker exec php-inventory-apache bash # Shell root + +# Tests API +curl http://localhost:8081/api/ # Test API Platform +curl -X POST http://localhost:8081/api/login_check \ + -H "Content-Type: application/json" \ + -d '{"email":"admin@admin.com","password":"admin123"}' +``` + +### Documentation +- [Lexik JWT Bundle](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/2.x/Resources/doc/index.rst) +- [API Platform Security](https://api-platform.com/docs/core/security/) +- [Symfony Security](https://symfony.com/doc/current/security.html) + +--- + +## 🔄 Historique des Changements + +### 2026-01-10 +- ✅ CrĂ©ation projet migration +- ✅ Phase 1 complĂšte (pgAdmin, JWT, Profile, migrations) +- ⚠ ProblĂšme Apache identifiĂ© (routes 404) +- 📝 Carnet de bord créé + +--- + +**DerniĂšre mise Ă  jour** : 2026-01-10 19:00 +**Statut** : Phase 2 - Debugging Apache en cours diff --git a/MIGRATION_PLAN.md b/MIGRATION_PLAN.md new file mode 100644 index 0000000..34b4211 --- /dev/null +++ b/MIGRATION_PLAN.md @@ -0,0 +1,1412 @@ +# Plan de Migration : NestJS/Prisma → Symfony/API Platform + +**Date de crĂ©ation**: 2026-01-10 +**DerniĂšre mise Ă  jour**: 2026-01-10 20:30 +**Objectif**: Migrer le backend Inventory (NestJS + Prisma) vers Symfony 8 + API Platform avec JWT auth, tout en conservant les donnĂ©es existantes et en minimisant les disruptions. + +--- + +## 📊 État des Lieux - 2026-01-10 20:30 + +### ✅ Phase 1 : PrĂ©paration (TERMINÉE) +- ✅ Bundles Symfony installĂ©s (JWT, VichUploader, Uid) +- ✅ JWT configurĂ© avec clĂ©s RSA +- ✅ EntitĂ© Profile créée (UserInterface + JWT-ready) +- ✅ pgAdmin configurĂ© et connectĂ© Ă  PostgreSQL +- ✅ Docker fonctionnel (web:8081, db:5433, pgadmin:5050) +- ✅ Authentification JWT 100% opĂ©rationnelle +- ✅ API Platform configurĂ©e avec prĂ©fixe `/api` + +### ✅ Phase 2 : Tests & Validation (TERMINÉE) +- ✅ Route `/api/test` fonctionnelle +- ✅ Login `/api/login_check` gĂ©nĂšre token JWT +- ✅ Routes protĂ©gĂ©es validĂ©es avec Bearer token +- ✅ API Platform retourne JSON-LD correctement + +### 📩 DonnĂ©es Ă  Migrer (Base: `inventory-data`) + +**Total: 673 lignes rĂ©parties sur 22 tables** + +| Table | Lignes | PrioritĂ© | Commentaire | +|-------|--------|----------|-------------| +| `sites` | 3 | ⚠ Critique | Point d'entrĂ©e, relations cascades | +| `machines` | 3 | ⚠ Critique | EntitĂ© centrale | +| `type_machines` | 3 | ⚠ Critique | Configuration machines | +| `ModelType` | 71 | 🔮 Haute | Templates composants/piĂšces/produits | +| `composants` | 23 | 🔮 Haute | - | +| `pieces` | 82 | 🔮 Haute | Plus grande table mĂ©tier | +| `products` | 2 | 🟡 Moyenne | - | +| `constructeurs` | 20 | 🟡 Moyenne | - | +| `custom_fields` | 95 | 🔮 Haute | Configuration champs custom | +| `custom_field_values` | 203 | 🔮 Haute | Plus grande table (valeurs) | +| `documents` | 14 | 🟡 Moyenne | Fichiers uploadĂ©s | +| `machine_component_links` | 25 | 🔮 Haute | Relations machines ↔ composants | +| `machine_piece_links` | 99 | 🔮 Haute | Relations machines ↔ piĂšces | +| `machine_product_links` | 0 | 🟱 Basse | Table vide | +| `type_machine_*_requirements` (×3) | 22 | 🟡 Moyenne | Config requirements types | +| `_*Constructeurs` (×4) | 14 | 🟡 Moyenne | Tables de liaison ManyToMany | +| `profiles` | 2 | 🟱 Basse | Utilisateurs (dĂ©jĂ  migrĂ©s) | + +**ComplexitĂ©s identifiĂ©es** : +- ✅ IDs CUID Prisma → garder en `string(30)` pour compatibilitĂ© +- ⚠ Champs JSON complexes (components, criticalParts, machinePieces, specifications) +- ⚠ Relations polymorphiques (Document → Machine/Composant/Piece/Product/Site) +- ⚠ Tables de liaison ManyToMany (4 tables `_*Constructeurs`) +- ⚠ System de custom fields dynamiques (95 dĂ©finitions, 203 valeurs) + +### đŸ—ïž Structure Actuelle des Projets + +``` +/home/r-dev/Inventory/ +├── Inventory_backend/ # NestJS + Prisma (11k lignes) +│ ├── src/ # 18 modules NestJS +│ ├── prisma/schema.prisma # SchĂ©ma DB source +│ └── package.json +├── Inventory_frontend/ # Nuxt 3 (105 fichiers) +│ ├── app/ +│ │ ├── composables/ +│ │ └── pages/ +│ └── package.json +├── src/ # ⚡ Nouveau: Symfony backend +│ ├── Entity/Profile.php # 1/16 entitĂ©s créées +│ └── Repository/ +├── config/ # Configuration Symfony +├── migrations/ # Migrations Doctrine +└── docker-compose.yml # Web + DB + pgAdmin +``` + +### 🎯 Prochaines Étapes (Phase 3) + +#### Étape 3.1 : RĂ©organisation du Projet +- [ ] DĂ©placer `Inventory_frontend/` vers `frontend/` +- [ ] Archiver `Inventory_backend/` vers `_archives/backend_nestjs/` +- [ ] CrĂ©er structure finale unifiĂ©e +- [ ] Mettre Ă  jour .gitignore + +#### Étape 3.2 : CrĂ©ation des 15 EntitĂ©s Manquantes +- [ ] Site (prioritĂ© 1) +- [ ] TypeMachine (prioritĂ© 1) +- [ ] Machine (prioritĂ© 1) +- [ ] ModelType (prioritĂ© 2) +- [ ] Composant, Piece, Product (prioritĂ© 2) +- [ ] Constructeur (prioritĂ© 3) +- [ ] Document (prioritĂ© 3) +- [ ] CustomField, CustomFieldValue (prioritĂ© 3) +- [ ] MachineComponentLink, MachinePieceLink, MachineProductLink (prioritĂ© 3) +- [ ] TypeMachine*Requirement (×3) (prioritĂ© 4) + +#### Étape 3.3 : Migration des DonnĂ©es +- [ ] CrĂ©er backup de `inventory-data` +- [ ] GĂ©nĂ©rer migrations Doctrine +- [ ] Comparer schĂ©mas Prisma vs Doctrine +- [ ] Copier les donnĂ©es `inventory-data` → `inventory` +- [ ] Valider intĂ©gritĂ© (count + foreign keys) + +--- + +## Table des MatiĂšres +1. [Vue d'ensemble](#vue-densemble) +2. [Architecture Cible](#architecture-cible) +3. [Mapping Prisma → Doctrine](#mapping-prisma--doctrine) +4. [StratĂ©gie de Migration des DonnĂ©es](#stratĂ©gie-de-migration-des-donnĂ©es) +5. [Configuration Docker](#configuration-docker) +6. [Plan d'ExĂ©cution Phase par Phase](#plan-dexĂ©cution-phase-par-phase) +7. [Gestion des Risques](#gestion-des-risques) +8. [Checklist de Migration](#checklist-de-migration) + +--- + +## 1. Vue d'ensemble + +### Situation Actuelle +- **Backend NestJS**: Port 3000, Prisma ORM, PostgreSQL, Sessions cookie-based +- **Frontend Nuxt 3**: Port 3001, API calls vers localhost:3000 +- **Base de donnĂ©es**: PostgreSQL 16 sur port 5433, 28 modĂšles Prisma, donnĂ©es en production +- **Docker**: Container unique avec PHP 8.4 + Node 24.12, Apache, XDebug + +### Objectif Final +- **Backend Symfony 8**: Port 8081, Doctrine ORM, API Platform 4.2, JWT auth +- **Frontend Nuxt 3**: Port 3001, API calls vers localhost:8081 +- **Base de donnĂ©es**: MĂȘme PostgreSQL, migration Prisma → Doctrine +- **Docker**: 2 backends en parallĂšle pendant la transition (NestJS:3000 + Symfony:8081) + +### Pourquoi cette migration ? +- Code gĂ©nĂ©rĂ© par IA (ChatGPT) Ă  nettoyer et reprendre en main +- Symfony + API Platform = stack PHP standard, mature, bien documentĂ©e +- JWT auth plus adaptĂ© pour API REST moderne +- Meilleure intĂ©gration avec l'Ă©cosystĂšme PHP existant + +--- + +## 2. Architecture Cible + +### Stack Technique + +#### Backend Symfony +``` +Symfony 8.0 +├── API Platform 4.2 # REST API auto-documentĂ©e (OpenAPI) +├── Doctrine ORM 3.6 # ORM pour PostgreSQL +├── Lexik JWT Bundle # JWT authentication +├── Nelmio CORS Bundle # CORS pour frontend +├── VichUploader Bundle # Upload de documents +├── Symfony Validator # Validation des DTOs +└── Doctrine Migrations # Gestion des migrations DB +``` + +#### Structure de Projet +``` +src/ +├── Entity/ # 16 entitĂ©s Doctrine +│ ├── Site.php +│ ├── Machine.php +│ ├── Composant.php +│ ├── Piece.php +│ ├── Product.php +│ ├── TypeMachine.php +│ ├── ModelType.php +│ ├── Constructeur.php +│ ├── Profile.php +│ ├── Document.php +│ ├── CustomField.php +│ ├── CustomFieldValue.php +│ ├── MachineComponentLink.php +│ ├── MachinePieceLink.php +│ ├── MachineProductLink.php +│ └── TypeMachine*Requirement.php (×3) +├── Repository/ # Repositories Doctrine +├── Controller/ # Controllers custom si besoin +├── State/ # State providers/processors API Platform +├── Validator/ # Contraintes de validation custom +└── Service/ # Services mĂ©tier (Ă©quivalents NestJS services) +``` + +#### Configuration JWT + +**Flow d'authentification**: +``` +1. POST /api/login_check + Body: {"username": "...", "password": "..."} + Response: {"token": "eyJ0eXAiOiJKV1QiLCJhbGc..."} + +2. RequĂȘtes authentifiĂ©es + Header: Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc... + +3. Token refresh (optionnel) + POST /api/token/refresh + Body: {"refresh_token": "..."} +``` + +**Configuration sĂ©curitĂ©** (`config/packages/security.yaml`): +```yaml +security: + password_hashers: + App\Entity\Profile: + algorithm: auto + + providers: + app_user_provider: + entity: + class: App\Entity\Profile + property: email # À ajouter Ă  Profile + + firewalls: + login: + pattern: ^/api/login + stateless: true + json_login: + check_path: /api/login_check + success_handler: lexik_jwt_authentication.handler.authentication_success + failure_handler: lexik_jwt_authentication.handler.authentication_failure + + api: + pattern: ^/api + stateless: true + jwt: ~ + + access_control: + - { path: ^/api/login, roles: PUBLIC_ACCESS } + - { path: ^/api/docs, roles: PUBLIC_ACCESS } + - { path: ^/api, roles: IS_AUTHENTICATED_FULLY } +``` + +--- + +## 3. Mapping Prisma → Doctrine + +### 3.1 Types de DonnĂ©es + +| Prisma | Doctrine | Notes | +|--------|----------|-------| +| `String` | `string` | `@ORM\Column(type="string")` | +| `String @id @default(cuid())` | `Ulid` | `@ORM\Id` + `@ORM\GeneratedValue(strategy="CUSTOM")` | +| `Int` | `integer` | `@ORM\Column(type="integer")` | +| `Decimal @db.Decimal(10,2)` | `string` | `@ORM\Column(type="decimal", precision=10, scale=2)` | +| `Boolean` | `bool` | `@ORM\Column(type="boolean")` | +| `DateTime` | `\DateTimeImmutable` | `@ORM\Column(type="datetime_immutable")` | +| `Json` | `array` | `@ORM\Column(type="json")` | +| `String[]` | `array` | `@ORM\Column(type="json")` | +| `String?` | `?string` | `@ORM\Column(nullable=true)` | + +### 3.2 Relations + +| Prisma | Doctrine | Exemple | +|--------|----------|---------| +| `@relation(onDelete: Cascade)` | `@ORM\JoinColumn(onDelete="CASCADE")` | Machine → Site | +| `@relation(onDelete: SetNull)` | `@ORM\JoinColumn(nullable=true, onDelete="SET NULL")` | Composant → Product | +| `Model[]` | `@ORM\OneToMany` + `Collection` | Site → machines | +| `Model` | `@ORM\ManyToOne` | Machine → site | +| Pas de `@relation` | `@ORM\ManyToMany` | Machine ↔ Constructeurs | + +### 3.3 Contraintes et Index + +| Prisma | Doctrine | +|--------|----------| +| `@unique` | `@ORM\Column(unique=true)` | +| `@@unique([field1, field2])` | `@ORM\UniqueConstraint(columns=["field1", "field2"])` | +| `@@map("table_name")` | `@ORM\Table(name="table_name")` | +| `@db.VarChar(120)` | `@ORM\Column(type="string", length=120)` | +| `@default("")` | `#[ORM\Column(options: ['default' => ''])]` | + +### 3.4 Mapping DĂ©taillĂ© des 16 EntitĂ©s + +#### 1. Site +```php +#[ORM\Entity(repositoryClass: SiteRepository::class)] +#[ORM\Table(name: 'sites')] +#[ApiResource( + operations: [ + new Get(), + new GetCollection(), + new Post(), + new Put(), + new Delete() + ] +)] +class Site +{ + #[ORM\Id] + #[ORM\Column(type: 'ulid', unique: true)] + #[ORM\GeneratedValue(strategy: 'CUSTOM')] + #[ORM\CustomIdGenerator(class: UlidGenerator::class)] + private ?Ulid $id = null; + + #[ORM\Column(type: 'string', length: 255)] + #[Assert\NotBlank] + private string $name; + + #[ORM\Column(type: 'string', length: 255, options: ['default' => ''])] + private string $contactName = ''; + + #[ORM\Column(type: 'string', length: 20, options: ['default' => ''])] + private string $contactPhone = ''; + + #[ORM\Column(type: 'string', length: 500, options: ['default' => ''])] + private string $contactAddress = ''; + + #[ORM\Column(type: 'string', length: 10, options: ['default' => ''])] + private string $contactPostalCode = ''; + + #[ORM\Column(type: 'string', length: 100, options: ['default' => ''])] + private string $contactCity = ''; + + #[ORM\Column(type: 'datetime_immutable')] + private \DateTimeImmutable $createdAt; + + #[ORM\Column(type: 'datetime_immutable')] + private \DateTimeImmutable $updatedAt; + + #[ORM\OneToMany(mappedBy: 'site', targetEntity: Machine::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + private Collection $machines; + + #[ORM\OneToMany(mappedBy: 'site', targetEntity: Document::class, cascade: ['remove'], orphanRemoval: true)] + private Collection $documents; + + #[ORM\PrePersist] + public function setCreatedAtValue(): void + { + $this->createdAt = new \DateTimeImmutable(); + $this->updatedAt = new \DateTimeImmutable(); + } + + #[ORM\PreUpdate] + public function setUpdatedAtValue(): void + { + $this->updatedAt = new \DateTimeImmutable(); + } +} +``` + +#### 2. Machine +```php +#[ORM\Entity] +#[ORM\Table(name: 'machines')] +#[ApiResource] +class Machine +{ + #[ORM\Id] + #[ORM\Column(type: 'ulid')] + private ?Ulid $id = null; + + #[ORM\Column(type: 'string', length: 255, unique: true)] + private string $name; + + #[ORM\Column(type: 'string', length: 255, nullable: true)] + private ?string $reference = null; + + #[ORM\Column(type: 'decimal', precision: 10, scale: 2, nullable: true)] + private ?string $prix = null; + + #[ORM\ManyToOne(targetEntity: Site::class, inversedBy: 'machines')] + #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] + private Site $site; + + #[ORM\ManyToOne(targetEntity: TypeMachine::class, inversedBy: 'machines')] + #[ORM\JoinColumn(nullable: true)] + private ?TypeMachine $typeMachine = null; + + #[ORM\ManyToMany(targetEntity: Constructeur::class, inversedBy: 'machines')] + #[ORM\JoinTable(name: 'machine_constructeurs')] + private Collection $constructeurs; + + #[ORM\OneToMany(mappedBy: 'machine', targetEntity: MachineComponentLink::class, cascade: ['persist', 'remove'])] + private Collection $componentLinks; + + #[ORM\OneToMany(mappedBy: 'machine', targetEntity: MachinePieceLink::class, cascade: ['persist', 'remove'])] + private Collection $pieceLinks; + + #[ORM\OneToMany(mappedBy: 'machine', targetEntity: MachineProductLink::class, cascade: ['persist', 'remove'])] + private Collection $productLinks; + + #[ORM\OneToMany(mappedBy: 'machine', targetEntity: Document::class, cascade: ['remove'])] + private Collection $documents; + + #[ORM\OneToMany(mappedBy: 'machine', targetEntity: CustomFieldValue::class, cascade: ['persist', 'remove'])] + private Collection $customFieldValues; + + #[ORM\Column(type: 'datetime_immutable')] + private \DateTimeImmutable $createdAt; + + #[ORM\Column(type: 'datetime_immutable')] + private \DateTimeImmutable $updatedAt; +} +``` + +#### 3. TypeMachine (avec champs JSON) +```php +#[ORM\Entity] +#[ORM\Table(name: 'type_machines')] +#[ApiResource] +class TypeMachine +{ + #[ORM\Id] + #[ORM\Column(type: 'ulid')] + private ?Ulid $id = null; + + #[ORM\Column(type: 'string', length: 255, unique: true)] + private string $name; + + #[ORM\Column(type: 'text', nullable: true)] + private ?string $description = null; + + #[ORM\Column(type: 'string', length: 100, nullable: true)] + private ?string $category = null; + + #[ORM\Column(type: 'string', length: 100, nullable: true)] + private ?string $maintenanceFrequency = null; + + // Champs JSON (structure hiĂ©rarchique, specs techniques) + #[ORM\Column(type: 'json', nullable: true)] + private ?array $components = null; + + #[ORM\Column(type: 'json', nullable: true)] + private ?array $criticalParts = null; + + #[ORM\Column(type: 'json', nullable: true)] + private ?array $machinePieces = null; + + #[ORM\Column(type: 'json', nullable: true)] + private ?array $specifications = null; + + #[ORM\OneToMany(mappedBy: 'typeMachine', targetEntity: Machine::class)] + private Collection $machines; + + #[ORM\OneToMany(mappedBy: 'typeMachine', targetEntity: CustomField::class, cascade: ['remove'])] + private Collection $customFields; + + #[ORM\OneToMany(mappedBy: 'typeMachine', targetEntity: TypeMachineComponentRequirement::class, cascade: ['persist', 'remove'])] + private Collection $componentRequirements; + + #[ORM\OneToMany(mappedBy: 'typeMachine', targetEntity: TypeMachinePieceRequirement::class, cascade: ['persist', 'remove'])] + private Collection $pieceRequirements; + + #[ORM\OneToMany(mappedBy: 'typeMachine', targetEntity: TypeMachineProductRequirement::class, cascade: ['persist', 'remove'])] + private Collection $productRequirements; +} +``` + +#### 4. ModelType (avec Enum) +```php +enum ModelCategory: string +{ + case COMPONENT = 'COMPONENT'; + case PIECE = 'PIECE'; + case PRODUCT = 'PRODUCT'; +} + +#[ORM\Entity] +#[ORM\Table(name: 'model_types')] +#[ORM\UniqueConstraint(name: 'unique_category_name', columns: ['category', 'name'])] +#[ApiResource] +class ModelType +{ + #[ORM\Id] + #[ORM\Column(type: 'ulid')] + private ?Ulid $id = null; + + #[ORM\Column(type: 'string', length: 120)] + private string $name; + + #[ORM\Column(type: 'string', length: 60, unique: true)] + private string $code; + + #[ORM\Column(type: 'string', enumType: ModelCategory::class)] + private ModelCategory $category; + + #[ORM\Column(type: 'text', nullable: true)] + private ?string $notes = null; + + #[ORM\Column(type: 'text', nullable: true)] + private ?string $description = null; + + #[ORM\Column(type: 'json', nullable: true)] + private ?array $componentSkeleton = null; + + #[ORM\Column(type: 'json', nullable: true)] + private ?array $pieceSkeleton = null; + + #[ORM\Column(type: 'json', nullable: true)] + private ?array $productSkeleton = null; + + #[ORM\OneToMany(mappedBy: 'typeComposant', targetEntity: Composant::class)] + private Collection $composants; + + #[ORM\OneToMany(mappedBy: 'typePiece', targetEntity: Piece::class)] + private Collection $pieces; + + #[ORM\OneToMany(mappedBy: 'typeProduct', targetEntity: Product::class)] + private Collection $products; + + // Relations avec les requirements (×3) + // Relations avec les custom fields (×3) +} +``` + +#### 5. Document (Relations Polymorphiques) +```php +#[ORM\Entity] +#[ORM\Table(name: 'documents')] +#[ApiResource( + normalizationContext: ['groups' => ['document:read']], + denormalizationContext: ['groups' => ['document:write']] +)] +class Document +{ + #[ORM\Id] + #[ORM\Column(type: 'ulid')] + private ?Ulid $id = null; + + #[ORM\Column(type: 'string', length: 255)] + private string $name; + + #[ORM\Column(type: 'string', length: 255)] + private string $filename; + + #[ORM\Column(type: 'string', length: 500)] + private string $path; + + #[ORM\Column(type: 'string', length: 100)] + private string $mimeType; + + #[ORM\Column(type: 'integer')] + private int $size; + + // Relations polymorphiques (nullable) + #[ORM\ManyToOne(targetEntity: Machine::class, inversedBy: 'documents')] + #[ORM\JoinColumn(nullable: true, onDelete: 'CASCADE')] + private ?Machine $machine = null; + + #[ORM\ManyToOne(targetEntity: Composant::class, inversedBy: 'documents')] + #[ORM\JoinColumn(nullable: true, onDelete: 'CASCADE')] + private ?Composant $composant = null; + + #[ORM\ManyToOne(targetEntity: Piece::class, inversedBy: 'documents')] + #[ORM\JoinColumn(nullable: true, onDelete: 'CASCADE')] + private ?Piece $piece = null; + + #[ORM\ManyToOne(targetEntity: Product::class, inversedBy: 'documents')] + #[ORM\JoinColumn(nullable: true, onDelete: 'CASCADE')] + private ?Product $product = null; + + #[ORM\ManyToOne(targetEntity: Site::class, inversedBy: 'documents')] + #[ORM\JoinColumn(nullable: true, onDelete: 'CASCADE')] + private ?Site $site = null; +} +``` + +#### 6-16. Autres EntitĂ©s +Les autres entitĂ©s suivent le mĂȘme pattern : +- **Composant, Piece, Product** : Similaires Ă  Machine avec relations vers ModelType +- **Constructeur** : ManyToMany avec Machine/Composant/Piece/Product +- **Profile** : EntitĂ© utilisateur (Ă  enrichir avec email/password pour JWT) +- **CustomField, CustomFieldValue** : SystĂšme de champs dynamiques +- **MachineComponentLink, MachinePieceLink, MachineProductLink** : Tables de liaison avec overrides +- **TypeMachine*Requirement (×3)** : DĂ©finition des requirements (minCount, maxCount, label, etc.) + +--- + +## 4. StratĂ©gie de Migration des DonnĂ©es + +### 4.1 ProblĂšme : CUID vs ULID + +**Prisma utilise CUID** (`@default(cuid())`) : +- Format: `clabcd123xyz...` (25 caractĂšres) +- Exemple: `cldu8kzj30000vh8q9k0y8h7i` + +**Doctrine recommande ULID** : +- Format: `01ARZ3NDEKTSV4RRFFQ69G5FAV` (26 caractĂšres) +- Type Symfony : `Symfony\Component\Uid\Ulid` + +**Solutions possibles** : + +#### Option A : Garder les CUID comme chaĂźnes (RECOMMANDÉE) +```php +#[ORM\Id] +#[ORM\Column(type: 'string', length: 30)] +private string $id; +``` + +**Avantages** : +- ✅ Aucune modification des IDs existants +- ✅ Migration transparente, pas de mapping d'IDs +- ✅ Relations FK prĂ©servĂ©es +- ✅ Frontend continue Ă  utiliser les mĂȘmes IDs + +**InconvĂ©nients** : +- ❌ Pas de type Ulid natif Symfony +- ❌ Moins "moderne" + +#### Option B : Migrer CUID → ULID avec mapping +```php +#[ORM\Id] +#[ORM\Column(type: 'ulid')] +private Ulid $id; +``` + +**NĂ©cessite** : +1. CrĂ©er une table de mapping `cuid_to_ulid` +2. GĂ©nĂ©rer de nouveaux ULIDs pour chaque ligne +3. Mettre Ă  jour toutes les FK +4. Adapter le frontend + +**Risque** : ComplexitĂ© Ă©levĂ©e, risque de perte de donnĂ©es. + +### 4.2 Approche RecommandĂ©e : Migration en 3 Étapes + +#### Étape 1 : CrĂ©er le schĂ©ma Doctrine en parallĂšle (SAFE) + +1. GĂ©nĂ©rer les migrations Doctrine sans exĂ©cution : + ```bash + php bin/console doctrine:migrations:diff + ``` + +2. **NE PAS exĂ©cuter** la migration immĂ©diatement + +3. Analyser le SQL gĂ©nĂ©rĂ© pour dĂ©tecter les diffĂ©rences : + - Noms de colonnes (camelCase → snake_case) + - Types de donnĂ©es (Decimal en Prisma → string en Doctrine) + - Contraintes (vĂ©rifier les FK, indexes) + +#### Étape 2 : Script de Validation (Dry-run) + +CrĂ©er un script PHP qui : +1. Lit toutes les tables Prisma actuelles +2. Compare avec le schĂ©ma Doctrine gĂ©nĂ©rĂ© +3. GĂ©nĂšre un rapport de diffĂ©rences +4. Valide que toutes les donnĂ©es peuvent ĂȘtre mappĂ©es + +```php +// scripts/validate-migration.php +use Doctrine\ORM\EntityManagerInterface; + +class MigrationValidator +{ + public function validate(): array + { + $issues = []; + + // VĂ©rifier chaque table + foreach ($this->getTables() as $table) { + // Comparer schĂ©ma Prisma vs Doctrine + $schemaIssues = $this->compareSchemas($table); + + // VĂ©rifier les donnĂ©es peuvent ĂȘtre migrĂ©es + $dataIssues = $this->validateData($table); + + $issues = array_merge($issues, $schemaIssues, $dataIssues); + } + + return $issues; + } +} +``` + +#### Étape 3 : Migration rĂ©elle (avec Backup) + +```bash +# 1. BACKUP de la DB +pg_dump -U postgres -h localhost -p 5433 inventory_db > backup_$(date +%Y%m%d_%H%M%S).sql + +# 2. ExĂ©cuter les migrations Doctrine +php bin/console doctrine:migrations:migrate --no-interaction + +# 3. VĂ©rifier l'intĂ©gritĂ© +php bin/console doctrine:schema:validate + +# 4. Si erreur : ROLLBACK +psql -U postgres -h localhost -p 5433 inventory_db < backup_YYYYMMDD_HHMMSS.sql +``` + +### 4.3 Mapping des Noms de Colonnes + +Prisma → Doctrine naming strategy : + +| Prisma | Doctrine | Action | +|--------|----------|--------| +| `siteId` | `site_id` | Annotation `#[ORM\JoinColumn(name="siteId")]` ou laisser Doctrine snake_case | +| `createdAt` | `created_at` | Idem | +| `contactName` | `contact_name` | Idem | + +**Recommandation** : Utiliser `#[ORM\Column(name="xxx")]` pour garder les noms Prisma existants et Ă©viter les ALTER TABLE. + +### 4.4 Checklist de Migration DB + +- [ ] CrĂ©er backup complet de la DB +- [ ] GĂ©nĂ©rer migration Doctrine (`doctrine:migrations:diff`) +- [ ] Analyser SQL gĂ©nĂ©rĂ© manuellement +- [ ] Tester migration sur copie locale de la DB +- [ ] Valider que les donnĂ©es sont intactes aprĂšs migration +- [ ] CrĂ©er script de rollback +- [ ] Documenter les diffĂ©rences schĂ©ma Prisma vs Doctrine +- [ ] Tester toutes les requĂȘtes via Doctrine +- [ ] VĂ©rifier les performances (indexes, FK) + +--- + +## 5. Configuration Docker + +### 5.1 Setup : 2 Backends en ParallĂšle + +**Objectif** : Faire tourner NestJS (port 3000) ET Symfony (port 8081) simultanĂ©ment pendant la transition. + +#### Modification `docker-compose.yml` + +```yaml +services: + web: + container_name: php-${DOCKER_APP_NAME}-apache + build: + context: ./docker/php + dockerfile: Dockerfile + args: + DOCKER_PHP_VERSION: ${DOCKER_PHP_VERSION} + DOCKER_NODE_VERSION: ${DOCKER_NODE_VERSION} + CURRENT_UID: ${CURRENT_UID} + CURRENT_GID: ${CURRENT_GID} + environment: + PHP_IDE_CONFIG: serverName=${DOCKER_APP_NAME}-docker + XDEBUG_CLIENT_HOST: ${XDEBUG_CLIENT_HOST:-host.docker.internal} + XDEBUG_CONFIG: client_host=${XDEBUG_CLIENT_HOST:-host.docker.internal} client_port=9003 + DATABASE_URL: "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?serverVersion=16&charset=utf8" + # Variables pour NestJS + NESTJS_DATABASE_URL: "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public" + NESTJS_PORT: 3000 + NESTJS_CORS_ORIGIN: "http://localhost:3001" + # Variables pour Symfony + APP_ENV: dev + APP_SECRET: ${APP_SECRET} + JWT_SECRET_KEY: ${JWT_SECRET_KEY} + JWT_PUBLIC_KEY: ${JWT_PUBLIC_KEY} + JWT_PASSPHRASE: ${JWT_PASSPHRASE} + volumes: + - ./:/var/www/html + - ~/.cache:/var/www/.cache + - ~/.config:/var/www/.config + - ~/.composer:/var/www/.composer + - ./docker/php/config/php.ini:/usr/local/etc/php/php.ini + - ./docker/php/config/vhost.conf:/etc/apache2/sites-available/000-default.conf + - ./docker/php/config/docker-php-ext-xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini + - ./LOG:/var/www/html/LOG + - ./LOG/logs_apache:/var/log/apache2/ + extra_hosts: + - "host.docker.internal:host-gateway" + depends_on: + - db + ports: + - "8081:80" # Apache/Symfony + - "3000:3000" # NestJS backend (nouveau mapping) + - "3001:3001" # Nuxt frontend + restart: unless-stopped + + db: + image: postgres:16-alpine + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - pg_data:/var/lib/postgresql/data + ports: + - "${POSTGRES_PORT:-5433}:5432" + restart: unless-stopped + +volumes: + pg_data: +``` + +#### Configuration Apache VirtualHost + +```apache +# /docker/php/config/vhost.conf + + + ServerName localhost + DocumentRoot /var/www/html/public + + + AllowOverride All + Require all granted + FallbackResource /index.php + + + # Logs + ErrorLog /var/log/apache2/error.log + CustomLog /var/log/apache2/access.log combined + +``` + +#### Script de DĂ©marrage des 2 Backends + +CrĂ©er `/docker/start-services.sh` : + +```bash +#!/bin/bash + +# DĂ©marrer Apache (Symfony) +apache2-foreground & + +# Installer dĂ©pendances NestJS si besoin +cd /var/www/html/Inventory_backend +if [ ! -d "node_modules" ]; then + npm install +fi + +# DĂ©marrer NestJS +npm run start:dev & + +# Installer dĂ©pendances Nuxt si besoin +cd /var/www/html/Inventory_frontend +if [ ! -d "node_modules" ]; then + npm install +fi + +# DĂ©marrer Nuxt +npm run dev & + +# Garder le container actif +wait -n +``` + +Modifier `Dockerfile` pour utiliser ce script : + +```dockerfile +# À la fin du Dockerfile +COPY start-services.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/start-services.sh + +CMD ["start-services.sh"] +``` + +### 5.2 Variables d'Environnement + +CrĂ©er `/docker/.env.docker.local` (dĂ©jĂ  existant, Ă  complĂ©ter) : + +```env +# Existant +DOCKER_APP_NAME=inventory +DOCKER_PHP_VERSION=8.4.6 +DOCKER_NODE_VERSION=24.12.0 +CURRENT_UID=1000 +CURRENT_GID=1000 +POSTGRES_DB=inventory_db +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_PORT=5433 + +# Nouveau : Symfony +APP_ENV=dev +APP_SECRET=changeme_super_secret_key_123456789 +JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem +JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem +JWT_PASSPHRASE=your_jwt_passphrase + +# Nouveau : NestJS +NESTJS_PORT=3000 +SESSION_SECRET=changeme_session_secret +CORS_ORIGIN=http://localhost:3001 +``` + +### 5.3 GĂ©nĂ©ration des ClĂ©s JWT + +Ajouter au script de dĂ©marrage : + +```bash +# GĂ©nĂ©rer clĂ©s JWT si inexistantes +mkdir -p /var/www/html/config/jwt +if [ ! -f /var/www/html/config/jwt/private.pem ]; then + openssl genpkey -out /var/www/html/config/jwt/private.pem -aes256 -algorithm rsa -pkeyopt rsa_keybits:4096 -pass pass:${JWT_PASSPHRASE} + openssl pkey -in /var/www/html/config/jwt/private.pem -passin pass:${JWT_PASSPHRASE} -out /var/www/html/config/jwt/public.pem -pubout + chmod 600 /var/www/html/config/jwt/*.pem +fi +``` + +--- + +## 6. Plan d'ExĂ©cution Phase par Phase + +### Phase 1 : PrĂ©paration (Semaine 1) + +#### TĂąches +1. **Installer bundles Symfony** : + ```bash + composer require lexik/jwt-authentication-bundle + composer require vich/uploader-bundle + composer require symfony/uid + ``` + +2. **Configurer JWT** : + - GĂ©nĂ©rer clĂ©s RSA + - Configurer `lexik_jwt_authentication.yaml` + - Configurer `security.yaml` + +3. **CrĂ©er entitĂ© Profile enrichie** : + ```php + class Profile implements UserInterface + { + // Ajouter email, password, roles + } + ``` + +4. **Analyser schĂ©ma Prisma** : + - Documenter toutes les relations + - Identifier les champs JSON complexes + - Lister les contraintes de validation + +5. **Setup Docker** : + - Modifier `docker-compose.yml` + - CrĂ©er script de dĂ©marrage multi-services + - Tester les 2 backends en parallĂšle + +#### Livrables +- [ ] Symfony configurĂ© avec tous les bundles +- [ ] JWT fonctionnel avec endpoint `/api/login_check` +- [ ] Docker lance NestJS (3000) + Symfony (8081) + Nuxt (3001) +- [ ] Documentation schĂ©ma Prisma complĂšte + +--- + +### Phase 2 : CrĂ©ation des EntitĂ©s (Semaine 2-3) + +#### TĂąches +1. **CrĂ©er toutes les entitĂ©s Doctrine** (16 entitĂ©s) : + - Utiliser les attributs PHP 8 (`#[ORM\Entity]`) + - Mapper exactement le schĂ©ma Prisma + - Ajouter annotations API Platform + - ImplĂ©menter les lifecycle callbacks (`#[ORM\PrePersist]`, etc.) + +2. **GĂ©nĂ©rer repositories** : + ```bash + php bin/console make:entity --regenerate + ``` + +3. **CrĂ©er migration Doctrine** : + ```bash + php bin/console doctrine:migrations:diff + ``` + +4. **Analyser SQL gĂ©nĂ©rĂ©** : + - Comparer avec schĂ©ma Prisma actuel + - Identifier les diffĂ©rences + - PrĂ©parer script d'ajustement si besoin + +5. **Tester sur DB vide** : + ```bash + # CrĂ©er DB test + createdb inventory_test + # Appliquer migrations + php bin/console doctrine:migrations:migrate --env=test + # Valider schĂ©ma + php bin/console doctrine:schema:validate + ``` + +#### Livrables +- [ ] 16 entitĂ©s Doctrine complĂštes avec relations +- [ ] Migration Doctrine gĂ©nĂ©rĂ©e et validĂ©e +- [ ] Tests sur DB vide rĂ©ussis +- [ ] Documentation des diffĂ©rences Prisma/Doctrine + +--- + +### Phase 3 : Migration de la Base de DonnĂ©es (Semaine 4) + +#### TĂąches +1. **Backup complet** : + ```bash + pg_dump -U postgres -h localhost -p 5433 inventory_db > backup_avant_migration.sql + ``` + +2. **CrĂ©er script de validation** : + ```php + // scripts/validate-schema.php + // Comparer schĂ©ma Prisma vs Doctrine + ``` + +3. **ExĂ©cuter migration Doctrine** : + ```bash + php bin/console doctrine:migrations:migrate --no-interaction + ``` + +4. **Valider donnĂ©es migrĂ©es** : + - Compter les lignes par table (Prisma count vs Doctrine count) + - VĂ©rifier intĂ©gritĂ© rĂ©fĂ©rentielle + - Tester quelques requĂȘtes + +5. **Tests de rĂ©gression** : + - Lire toutes les entitĂ©s via Doctrine + - VĂ©rifier les relations + - Tester les champs JSON + +#### Livrables +- [ ] Base de donnĂ©es migrĂ©e avec succĂšs +- [ ] Backup de sĂ©curitĂ© créé +- [ ] Script de validation OK (0 diffĂ©rences) +- [ ] Tests de lecture/Ă©criture via Doctrine OK + +--- + +### Phase 4 : Logique MĂ©tier (Semaine 5-6) + +#### TĂąches +1. **Migrer services NestJS → Symfony** : + +**Mapping des services** : + +| NestJS Service | Symfony Service | ResponsabilitĂ©s | +|----------------|-----------------|-----------------| +| `SitesService` | `SiteManager` | CRUD sites | +| `MachinesService` | `MachineManager` | CRUD machines, reconfiguration | +| `ComposantsService` | `ComposantManager` | CRUD composants | +| `PiecesService` | `PieceManager` | CRUD pieces | +| `ProductsService` | `ProductManager` | CRUD products | +| `TypesService` | `TypeMachineManager` | Gestion types machines | +| `ModelTypeService` | `ModelTypeManager` | Gestion model types | +| `DocumentsService` | `DocumentManager` | Upload/download documents | +| `CustomFieldsService` | `CustomFieldManager` | Gestion custom fields | +| `ConstructeursService` | `ConstructeurManager` | Gestion constructeurs | +| `ProfilesService` | `ProfileManager` | Gestion profiles | +| `SessionService` | SupprimĂ© (JWT) | - | + +2. **ImplĂ©menter upload de documents** : + - Configurer VichUploaderBundle + - CrĂ©er endpoint `POST /api/documents` + - CrĂ©er endpoint `GET /api/documents/{id}/download` + +3. **ImplĂ©menter custom fields** : + - Service de gestion des dĂ©finitions + - Service de gestion des valeurs + - Validation dynamique + +4. **CrĂ©er State Processors API Platform** : + - Pour logique custom (ex: reconfiguration machine) + - Pour validation complexe + +5. **Tests unitaires** : + - Tester chaque service + - Mocker les repositories + - VĂ©rifier logique mĂ©tier + +#### Livrables +- [ ] Tous les services mĂ©tier migrĂ©s +- [ ] Upload/download documents fonctionnel +- [ ] Custom fields opĂ©rationnels +- [ ] Tests unitaires couvrant 80%+ du code + +--- + +### Phase 5 : API Platform (Semaine 7) + +#### TĂąches +1. **Configurer les endpoints API Platform** : + - VĂ©rifier routes auto-gĂ©nĂ©rĂ©es + - Customiser opĂ©rations si besoin + - Ajouter filtres de recherche + - Configurer pagination + +2. **SĂ©rialisation** : + - CrĂ©er groupes de normalisation + - GĂ©rer relations circulaires + - Optimiser avec `@Groups` + +3. **Validation** : + - Ajouter contraintes de validation + - CrĂ©er validators custom + - GĂ©rer messages d'erreur + +4. **Documentation API** : + - GĂ©nĂ©rer OpenAPI spec + - Tester via Swagger UI + - Documenter endpoints custom + +5. **Tests d'intĂ©gration** : + - Tester tous les endpoints CRUD + - VĂ©rifier codes de rĂ©ponse HTTP + - Valider structure JSON + +#### Livrables +- [ ] Tous les endpoints API fonctionnels +- [ ] Documentation OpenAPI complĂšte +- [ ] Tests d'intĂ©gration OK +- [ ] Swagger UI accessible sur `/api/docs` + +--- + +### Phase 6 : Adaptation Frontend (Semaine 8) + +#### TĂąches +1. **Modifier `useApi.js`** : + ```js + // Ancien + const baseURL = 'http://localhost:3000'; + + // Nouveau + const baseURL = import.meta.env.VITE_API_URL || 'http://localhost:8081/api'; + ``` + +2. **ImplĂ©menter gestion JWT** : + ```js + // composables/useAuth.js + export const useAuth = () => { + const token = ref(localStorage.getItem('jwt_token')); + + const login = async (username, password) => { + const response = await fetch('/api/login_check', { + method: 'POST', + body: JSON.stringify({ username, password }) + }); + const { token } = await response.json(); + localStorage.setItem('jwt_token', token); + token.value = token; + }; + + const logout = () => { + localStorage.removeItem('jwt_token'); + token.value = null; + }; + + return { token, login, logout }; + }; + ``` + +3. **Adapter les composables** : + - `useSites.js` → vĂ©rifier format rĂ©ponse + - `useMachines.js` → idem + - Tous les autres composables + +4. **Gestion des changements d'API** : + - Ajuster parsing des rĂ©ponses JSON:API vs JSON simple + - GĂ©rer les nouveaux formats d'erreur + - Adapter les IDs si changement + +5. **Tests fonctionnels** : + - Tester chaque page du frontend + - VĂ©rifier tous les flows utilisateur + - Tester upload/download documents + +#### Livrables +- [ ] Frontend connectĂ© au backend Symfony +- [ ] Authentification JWT fonctionnelle +- [ ] Tous les composables adaptĂ©s +- [ ] Tests fonctionnels rĂ©ussis + +--- + +### Phase 7 : Tests & DĂ©commissionnement NestJS (Semaine 9) + +#### TĂąches +1. **Tests de charge** : + - Comparer performances NestJS vs Symfony + - VĂ©rifier temps de rĂ©ponse + - Tester avec donnĂ©es rĂ©elles + +2. **Tests de rĂ©gression complĂšte** : + - Rejouer tous les scĂ©narios utilisateur + - Comparer rĂ©sultats NestJS vs Symfony + - Fixer les bugs + +3. **Documentation** : + - Documenter nouvelle architecture + - CrĂ©er guide migration pour Ă©quipe + - Documenter diffĂ©rences API + +4. **DĂ©commissionner NestJS** : + - ArrĂȘter le service NestJS + - Supprimer du docker-compose + - Archiver le code (ne pas supprimer) + +5. **Cleanup** : + - Supprimer dĂ©pendances NestJS inutilisĂ©es + - Nettoyer fichiers de config + - Optimiser Docker + +#### Livrables +- [ ] Tests de charge validĂ©s +- [ ] 0 bugs de rĂ©gression +- [ ] Documentation complĂšte +- [ ] NestJS arrĂȘtĂ©, Symfony en production +- [ ] Code NestJS archivĂ© + +--- + +## 7. Gestion des Risques + +### 7.1 Risques Techniques + +| Risque | ProbabilitĂ© | Impact | Mitigation | +|--------|-------------|--------|------------| +| **Perte de donnĂ©es pendant migration DB** | Faible | Critique | Backup complet avant migration, script de rollback, validation post-migration | +| **IncompatibilitĂ© schĂ©ma Prisma/Doctrine** | Moyenne | ÉlevĂ© | Validation schema dry-run, tests sur DB copie, garder noms de colonnes Prisma | +| **Performances Symfony < NestJS** | Faible | Moyen | Benchmarks, optimisation requĂȘtes Doctrine, cache API Platform | +| **Bugs lors migration logique mĂ©tier** | Moyenne | ÉlevĂ© | Tests unitaires + intĂ©gration, comparison NestJS/Symfony side-by-side | +| **JWT incompatible avec frontend** | Faible | Moyen | Tests d'auth dĂšs Phase 1, documentation claire | +| **Upload documents ne fonctionne pas** | Faible | Moyen | Tester VichUploader tĂŽt, fallback manuel si besoin | +| **Relations Doctrine mal configurĂ©es** | Moyenne | ÉlevĂ© | Tests avec donnĂ©es rĂ©elles, validation via `doctrine:schema:validate` | + +### 7.2 Risques Projet + +| Risque | ProbabilitĂ© | Impact | Mitigation | +|--------|-------------|--------|------------| +| **Timeline trop optimiste** | ÉlevĂ©e | Moyen | Buffer de 2 semaines, dĂ©coupage en phases indĂ©pendantes | +| **Manque de tests** | Moyenne | ÉlevĂ© | Écrire tests dĂšs Phase 2, coverage > 80% | +| **Documentation insuffisante** | Moyenne | Moyen | Documenter au fur et Ă  mesure, pas Ă  la fin | +| **RĂ©version nĂ©cessaire** | Faible | Critique | Garder NestJS actif en parallĂšle, backup DB, script de rollback | + +### 7.3 Plan de Rollback + +Si problĂšme critique : + +1. **ArrĂȘter Symfony** : + ```bash + docker-compose stop web + ``` + +2. **Restaurer DB depuis backup** : + ```bash + psql -U postgres -h localhost -p 5433 inventory_db < backup_avant_migration.sql + ``` + +3. **RedĂ©marrer NestJS** : + ```bash + cd Inventory_backend && npm run start:dev + ``` + +4. **Reconfigurer frontend** : + ```js + // Revert API URL + const baseURL = 'http://localhost:3000'; + ``` + +5. **Analyser l'erreur** : + - Lire logs Symfony (`var/log/dev.log`) + - Lire logs PostgreSQL + - Identifier root cause + +6. **Fix et retry** : + - Corriger le problĂšme + - Retester en local + - Relancer migration + +--- + +## 8. Checklist de Migration + +### Avant de Commencer +- [ ] Lire entiĂšrement ce document +- [ ] Comprendre schĂ©ma Prisma actuel +- [ ] Valider que Docker fonctionne +- [ ] CrĂ©er backup DB initial +- [ ] Setup environnement de dev local + +### Phase 1 : PrĂ©paration +- [ ] Installer bundles Symfony (JWT, VichUploader, Uid) +- [ ] Configurer JWT avec clĂ©s RSA +- [ ] Enrichir entitĂ© Profile (email, password, roles) +- [ ] Modifier docker-compose.yml pour 2 backends +- [ ] Tester dĂ©marrage des 2 backends en parallĂšle +- [ ] Documenter schĂ©ma Prisma + +### Phase 2 : EntitĂ©s +- [ ] CrĂ©er 16 entitĂ©s Doctrine +- [ ] Ajouter annotations API Platform +- [ ] GĂ©nĂ©rer migration Doctrine +- [ ] Analyser SQL gĂ©nĂ©rĂ© +- [ ] Tester migration sur DB vide +- [ ] Valider schĂ©ma Doctrine + +### Phase 3 : Migration DB +- [ ] CrĂ©er backup DB production +- [ ] CrĂ©er script de validation +- [ ] ExĂ©cuter migration Doctrine +- [ ] Valider count de lignes par table +- [ ] Tester requĂȘtes Doctrine +- [ ] VĂ©rifier intĂ©gritĂ© rĂ©fĂ©rentielle + +### Phase 4 : Services +- [ ] Migrer les 11 services mĂ©tier +- [ ] ImplĂ©menter upload documents +- [ ] ImplĂ©menter custom fields +- [ ] Écrire tests unitaires +- [ ] Valider logique mĂ©tier + +### Phase 5 : API +- [ ] Configurer endpoints API Platform +- [ ] ImplĂ©menter sĂ©rialisation +- [ ] Ajouter validation +- [ ] GĂ©nĂ©rer doc OpenAPI +- [ ] Tests d'intĂ©gration + +### Phase 6 : Frontend +- [ ] Modifier useApi.js (nouveau baseURL) +- [ ] ImplĂ©menter gestion JWT +- [ ] Adapter tous les composables +- [ ] Tester toutes les pages +- [ ] Valider flows utilisateur + +### Phase 7 : Finalisation +- [ ] Tests de charge +- [ ] Tests de rĂ©gression complĂšte +- [ ] Documenter nouvelle architecture +- [ ] ArrĂȘter NestJS +- [ ] Archiver code NestJS +- [ ] Cleanup Docker + +--- + +## Annexes + +### A. Commandes Utiles + +```bash +# Symfony +php bin/console doctrine:schema:validate +php bin/console doctrine:migrations:diff +php bin/console doctrine:migrations:migrate +php bin/console doctrine:query:sql "SELECT COUNT(*) FROM sites" +php bin/console debug:router +php bin/console cache:clear + +# Docker +docker-compose up -d --build +docker-compose logs -f web +docker-compose exec web bash +docker-compose down + +# Base de donnĂ©es +pg_dump -U postgres -h localhost -p 5433 inventory_db > backup.sql +psql -U postgres -h localhost -p 5433 inventory_db < backup.sql +psql -U postgres -h localhost -p 5433 inventory_db -c "SELECT COUNT(*) FROM sites" + +# Tests +php bin/phpunit +php vendor/bin/phpstan analyse src +``` + +### B. Structure Finale du Projet + +``` +/home/r-dev/Inventory/ +├── Inventory_backend/ # À archiver aprĂšs migration +├── Inventory_frontend/ # Frontend Nuxt (Ă  garder) +│ ├── app/ +│ │ ├── composables/ +│ │ │ ├── useApi.js # Modifier baseURL +│ │ │ ├── useAuth.js # Nouveau : JWT +│ │ │ └── ... +│ │ └── pages/ +├── src/ # Backend Symfony (nouveau) +│ ├── Entity/ +│ │ ├── Site.php +│ │ ├── Machine.php +│ │ └── ... (16 entitĂ©s) +│ ├── Repository/ +│ ├── Service/ +│ │ ├── SiteManager.php +│ │ ├── MachineManager.php +│ │ └── ... (11 services) +│ ├── Controller/ # Si besoin de custom controllers +│ └── State/ # State providers API Platform +├── config/ +│ ├── packages/ +│ │ ├── doctrine.yaml +│ │ ├── api_platform.yaml +│ │ ├── lexik_jwt_authentication.yaml +│ │ ├── security.yaml +│ │ └── nelmio_cors.yaml +│ └── jwt/ +│ ├── private.pem +│ └── public.pem +├── migrations/ # Migrations Doctrine +├── docker/ +│ ├── php/ +│ │ ├── Dockerfile +│ │ └── config/ +│ ├── start-services.sh # Nouveau : lance Symfony + Nuxt +│ ├── .env.docker +│ └── .env.docker.local +├── docker-compose.yml # ModifiĂ© : 2 backends +├── composer.json # Symfony dependencies +└── MIGRATION_PLAN.md # Ce document +``` + +### C. Ressources + +- [API Platform Docs](https://api-platform.com/docs/) +- [Doctrine ORM Docs](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/) +- [Lexik JWT Bundle](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/2.x/Resources/doc/index.rst) +- [Symfony Best Practices](https://symfony.com/doc/current/best_practices.html) +- [Prisma to Doctrine Migration Guide](https://symfony.com/doc/current/doctrine.html#creating-an-entity-class) + +--- + +## Conclusion + +Ce plan de migration dĂ©taille la stratĂ©gie pour passer de NestJS/Prisma Ă  Symfony/API Platform de maniĂšre progressive et sĂ©curisĂ©e. Les points clĂ©s : + +1. **Migration incrĂ©mentale** : Garder NestJS actif en parallĂšle +2. **SĂ©curitĂ© des donnĂ©es** : Backups, validation, rollback +3. **Modernisation** : JWT auth, API Platform, documentation OpenAPI +4. **Minimiser les risques** : Tests Ă  chaque phase, validation continue + +**DurĂ©e estimĂ©e** : 9 semaines (avec buffer) +**Effort requis** : 1 dĂ©veloppeur full-time + +**PrĂȘt Ă  dĂ©marrer ?** Validation de ce plan avant de commencer la Phase 1. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..2a141ca --- /dev/null +++ b/TODO.md @@ -0,0 +1,2 @@ +- Doc: ne pas oublier de mettre `make` dans la documentation. +- Note: le probleme d'IP sous WSL, a ajouter dans la doc.