# Story 1.2: Base de données et migrations initiales Status: review ## Story As a développeur, I want le schéma de base de données MariaDB avec les tables nécessaires à l'Epic 1, so that l'API peut servir du contenu bilingue. ## Acceptance Criteria 1. **Given** une connexion MariaDB configurée dans Laravel **When** `php artisan migrate` est exécuté **Then** la table `translations` est créée (id, lang, key_name, value, timestamps) avec index unique (lang, key_name) 2. **And** la table `projects` est créée (id, slug, title_key, description_key, short_description_key, image, url, github_url, date_completed, is_featured, display_order, timestamps) 3. **And** la table `skills` est créée (id, slug, name_key, description_key, icon, category, max_level, display_order) 4. **And** la table `skill_project` est créée (id, skill_id, project_id, level_before, level_after, level_description_key) avec foreign keys 5. **And** les Models Eloquent sont définis avec leurs relations (Project belongsToMany Skill, etc.) 6. **And** des Seeders de base sont disponibles avec données de test en FR et EN 7. **And** `php artisan db:seed` fonctionne correctement ## Tasks / Subtasks - [x] **Task 1: Configuration connexion MariaDB** (AC: #1) - [x] Vérifier que MariaDB est installé et accessible - [x] Créer la base de données `skycel` si elle n'existe pas - [x] Configurer `api/.env` avec les variables DB_* correctes - [x] Tester la connexion avec `php artisan db:show` - [x] **Task 2: Migration table translations** (AC: #1) - [x] Créer migration `create_translations_table` - [x] Colonnes: id, lang (VARCHAR 5), key_name (VARCHAR 255), value (TEXT), timestamps - [x] Index unique composite sur (lang, key_name) - [x] Index simple sur lang pour les requêtes par langue - [x] **Task 3: Migration table projects** (AC: #2) - [x] Créer migration `create_projects_table` - [x] Colonnes: id, slug (unique), title_key, description_key, short_description_key, image, url (nullable), github_url (nullable), date_completed (date), is_featured (boolean, default false), display_order (integer, default 0), timestamps - [x] Index sur slug (unique) - [x] Index sur display_order pour le tri - [x] **Task 4: Migration table skills** (AC: #3) - [x] Créer migration `create_skills_table` - [x] Colonnes: id, slug (unique), name_key, description_key, icon (nullable), category (enum ou string: Frontend, Backend, Tools, Soft skills), max_level (integer, default 5), display_order (integer, default 0), timestamps - [x] Index sur slug (unique) - [x] Index sur category pour le filtrage - [x] **Task 5: Migration table pivot skill_project** (AC: #4) - [x] Créer migration `create_skill_project_table` - [x] Colonnes: id, skill_id (FK), project_id (FK), level_before (integer), level_after (integer), level_description_key (nullable), timestamps - [x] Foreign key skill_id → skills.id avec ON DELETE CASCADE - [x] Foreign key project_id → projects.id avec ON DELETE CASCADE - [x] Index composite sur (skill_id, project_id) pour éviter les doublons - [x] **Task 6: Model Translation** (AC: #5) - [x] Créer `app/Models/Translation.php` - [x] Propriétés fillable: lang, key_name, value - [x] Scope `scopeForLang($query, $lang)` pour filtrer par langue - [x] Méthode statique `getTranslation($key, $lang, $fallback = 'fr')` - [x] **Task 7: Model Project avec relations** (AC: #5) - [x] Créer `app/Models/Project.php` - [x] Propriétés fillable: slug, title_key, description_key, short_description_key, image, url, github_url, date_completed, is_featured, display_order - [x] Casts: date_completed → date, is_featured → boolean - [x] Relation `skills()`: belongsToMany(Skill::class)->withPivot(['level_before', 'level_after', 'level_description_key'])->withTimestamps() - [x] Scope `scopeFeatured($query)` pour les projets mis en avant - [x] Scope `scopeOrdered($query)` pour le tri par display_order - [x] **Task 8: Model Skill avec relations** (AC: #5) - [x] Créer `app/Models/Skill.php` - [x] Propriétés fillable: slug, name_key, description_key, icon, category, max_level, display_order - [x] Relation `projects()`: belongsToMany(Project::class)->withPivot(['level_before', 'level_after', 'level_description_key'])->withTimestamps() - [x] Scope `scopeByCategory($query, $category)` pour filtrer par catégorie - [x] Scope `scopeOrdered($query)` pour le tri par display_order - [x] **Task 9: Seeders de base** (AC: #6, #7) - [x] Créer `database/seeders/TranslationSeeder.php` avec traductions FR et EN de test - [x] Créer `database/seeders/SkillSeeder.php` avec 8-10 compétences de test (Frontend, Backend, Tools) - [x] Créer `database/seeders/ProjectSeeder.php` avec 3-4 projets de test - [x] Créer `database/seeders/SkillProjectSeeder.php` pour lier compétences et projets - [x] Mettre à jour `DatabaseSeeder.php` pour appeler les seeders dans l'ordre correct (translations → skills → projects → skill_project) - [x] **Task 10: Validation finale** (AC: tous) - [x] `php artisan migrate:fresh` fonctionne sans erreur - [x] `php artisan db:seed` fonctionne sans erreur - [x] Vérifier en BDD que les tables sont créées avec les bons schémas - [x] Vérifier que les relations fonctionnent: `Project::first()->skills` et `Skill::first()->projects` - [x] Vérifier que les traductions fonctionnent: `Translation::getTranslation('project.skycel-portfolio.title', 'fr')` ## Dev Notes ### Schéma de base de données ``` ┌──────────────────────────────────────────────────────────────────────────┐ │ translations │ ├──────────────────────────────────────────────────────────────────────────┤ │ id (PK) │ lang │ key_name │ value │ created_at │ updated_at │ │ │ VARCHAR(5) │ VARCHAR(255) │ TEXT │ │ │ │ UNIQUE(lang, key_name) │ └──────────────────────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────────────────┐ │ projects │ ├──────────────────────────────────────────────────────────────────────────┤ │ id │ slug │ title_key │ description_key │ short_description_key │ image │ │ │ url │ github_url │ date_completed │ is_featured │ display_order │ │ │ created_at │ updated_at │ └──────────────────────────────────────────────────────────────────────────┘ │ │ belongsToMany ▼ ┌──────────────────────────────────────────────────────────────────────────┐ │ skill_project │ ├──────────────────────────────────────────────────────────────────────────┤ │ id │ skill_id (FK) │ project_id (FK) │ level_before │ level_after │ │ │ level_description_key │ created_at │ updated_at │ └──────────────────────────────────────────────────────────────────────────┘ ▲ │ belongsToMany │ ┌──────────────────────────────────────────────────────────────────────────┐ │ skills │ ├──────────────────────────────────────────────────────────────────────────┤ │ id │ slug │ name_key │ description_key │ icon │ category │ max_level │ │ │ display_order │ created_at │ updated_at │ └──────────────────────────────────────────────────────────────────────────┘ ``` ### Convention de nommage des clés i18n Les colonnes `*_key` contiennent des clés de traduction, pas des valeurs directes. **Format des clés :** `{table}.{slug}.{champ}` Exemples : - `project.skycel.title` → "Skycel Portfolio" - `project.skycel.description` → "Mon portfolio gamifié..." - `skill.vuejs.name` → "Vue.js" - `skill.vuejs.description` → "Framework JavaScript progressif" ### Données de test recommandées **Skills de test :** | Category | Skills | |----------|--------| | Frontend | Vue.js, Nuxt, TypeScript, TailwindCSS | | Backend | Laravel, PHP, Node.js | | Tools | Git, Docker | | Soft skills | Communication | **Projets de test :** 1. Skycel Portfolio (ce projet) 2. Projet fictif e-commerce 3. Projet fictif dashboard ### Migration SQL de référence (table translations) ```sql -- Extrait de l'architecture pour référence CREATE TABLE translations ( id INT AUTO_INCREMENT PRIMARY KEY, lang VARCHAR(5) NOT NULL, key_name VARCHAR(255) NOT NULL, value TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY unique_translation (lang, key_name), INDEX idx_lang (lang) ); ``` ### Commandes Laravel utiles ```bash # Créer les migrations php artisan make:migration create_translations_table php artisan make:migration create_projects_table php artisan make:migration create_skills_table php artisan make:migration create_skill_project_table # Créer les models php artisan make:model Translation php artisan make:model Project php artisan make:model Skill # Créer les seeders php artisan make:seeder TranslationSeeder php artisan make:seeder SkillSeeder php artisan make:seeder ProjectSeeder php artisan make:seeder SkillProjectSeeder # Exécuter php artisan migrate:fresh --seed ``` ### Dépendances avec Story 1.1 Cette story DÉPEND de : - Structure `api/` créée (Laravel 12 initialisé) - Fichier `api/.env` avec variables DB_* configurées Cette story PRÉPARE pour : - Story 1.3 (i18n) : La table `translations` sera utilisée pour le contenu dynamique - Story 2.x (Projets, Compétences) : Les models et tables seront consommés par les endpoints API ### Project Structure Notes **Fichiers à créer dans `api/` :** ``` api/ ├── app/Models/ │ ├── Translation.php # CRÉER │ ├── Project.php # CRÉER │ └── Skill.php # CRÉER ├── database/ │ ├── migrations/ │ │ ├── 2026_02_03_000001_create_translations_table.php # CRÉER │ │ ├── 2026_02_03_000002_create_projects_table.php # CRÉER │ │ ├── 2026_02_03_000003_create_skills_table.php # CRÉER │ │ └── 2026_02_03_000004_create_skill_project_table.php # CRÉER │ └── seeders/ │ ├── DatabaseSeeder.php # MODIFIER │ ├── TranslationSeeder.php # CRÉER │ ├── SkillSeeder.php # CRÉER │ ├── ProjectSeeder.php # CRÉER │ └── SkillProjectSeeder.php # CRÉER ``` ### References - [Source: docs/planning-artifacts/architecture.md#Data-Architecture] - [Source: docs/planning-artifacts/architecture.md#Stratégie-i18n] - [Source: docs/planning-artifacts/epics.md#Story-1.2] - [Source: docs/brainstorming-gamification-2026-01-26.md#Schema-BDD] ### Technical Requirements | Requirement | Value | Source | |-------------|-------|--------| | Database | MariaDB | Architecture | | ORM | Eloquent | Architecture | | PHP | 8.2+ | Laravel 12 | | Charset | utf8mb4 | Laravel default | | Collation | utf8mb4_unicode_ci | Laravel default | ### Previous Story Intelligence (Story 1.1) **Learnings from Story 1.1:** - Structure monorepo avec `frontend/` et `api/` - Laravel 12 configuré en mode API-only - Fichier `.env.example` créé avec variables DB_* - Middleware VerifyApiKey en place **Files created in Story 1.1:** - `api/.env.example` avec configuration DB de base - Structure Laravel standard dans `api/` ## Dev Agent Record ### Agent Model Used Claude Opus 4.5 (claude-opus-4-5-20251101) ### Debug Log References - Pivot table naming: Laravel attend `project_skill` (ordre alphabétique), mais la story spécifie `skill_project`. Résolu en ajoutant le nom de table explicite dans les relations `belongsToMany`. - Connexion MySQL: `DB_PASSWORD=` (vide) requis pour root@localhost sur Laragon. ### Completion Notes List - 4 migrations créées et fonctionnelles (translations, projects, skills, skill_project) - 3 models Eloquent avec relations belongsToMany, scopes et casts - 4 seeders de test: 37 traductions FR + 37 EN, 10 skills, 3 projets, 12 liens skill_project - `migrate:fresh --seed` fonctionne sans erreur - Relations vérifiées: Project->skills (5), Skill->projects (2) - Traductions vérifiées: FR, EN et fallback fonctionnent - Scopes vérifiés: featured (2), byCategory Frontend (4), Backend (3) ### Change Log | Date | Change | Author | |------|--------|--------| | 2026-02-03 | Story créée avec contexte complet | SM Agent | | 2026-02-05 | Tasks 1-10 implémentées et validées | Dev Agent (Claude Opus 4.5) | ### File List - `api/database/migrations/2026_02_05_000001_create_translations_table.php` — CRÉÉ - `api/database/migrations/2026_02_05_000002_create_projects_table.php` — CRÉÉ - `api/database/migrations/2026_02_05_000003_create_skills_table.php` — CRÉÉ - `api/database/migrations/2026_02_05_000004_create_skill_project_table.php` — CRÉÉ - `api/app/Models/Translation.php` — CRÉÉ - `api/app/Models/Project.php` — CRÉÉ - `api/app/Models/Skill.php` — CRÉÉ - `api/database/seeders/TranslationSeeder.php` — CRÉÉ - `api/database/seeders/SkillSeeder.php` — CRÉÉ - `api/database/seeders/ProjectSeeder.php` — CRÉÉ - `api/database/seeders/SkillProjectSeeder.php` — CRÉÉ - `api/database/seeders/DatabaseSeeder.php` — MODIFIÉ