# Story 1.2: Base de données et migrations initiales Status: ready-for-dev ## 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 - [ ] **Task 1: Configuration connexion MariaDB** (AC: #1) - [ ] Vérifier que MariaDB est installé et accessible - [ ] Créer la base de données `skycel` si elle n'existe pas - [ ] Configurer `api/.env` avec les variables DB_* correctes - [ ] Tester la connexion avec `php artisan db:show` - [ ] **Task 2: Migration table translations** (AC: #1) - [ ] Créer migration `create_translations_table` - [ ] Colonnes: id, lang (VARCHAR 5), key_name (VARCHAR 255), value (TEXT), timestamps - [ ] Index unique composite sur (lang, key_name) - [ ] Index simple sur lang pour les requêtes par langue - [ ] **Task 3: Migration table projects** (AC: #2) - [ ] Créer migration `create_projects_table` - [ ] 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 - [ ] Index sur slug (unique) - [ ] Index sur display_order pour le tri - [ ] **Task 4: Migration table skills** (AC: #3) - [ ] Créer migration `create_skills_table` - [ ] 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 - [ ] Index sur slug (unique) - [ ] Index sur category pour le filtrage - [ ] **Task 5: Migration table pivot skill_project** (AC: #4) - [ ] Créer migration `create_skill_project_table` - [ ] Colonnes: id, skill_id (FK), project_id (FK), level_before (integer), level_after (integer), level_description_key (nullable), timestamps - [ ] Foreign key skill_id → skills.id avec ON DELETE CASCADE - [ ] Foreign key project_id → projects.id avec ON DELETE CASCADE - [ ] Index composite sur (skill_id, project_id) pour éviter les doublons - [ ] **Task 6: Model Translation** (AC: #5) - [ ] Créer `app/Models/Translation.php` - [ ] Propriétés fillable: lang, key_name, value - [ ] Scope `scopeForLang($query, $lang)` pour filtrer par langue - [ ] Méthode statique `getTranslation($key, $lang, $fallback = 'fr')` - [ ] **Task 7: Model Project avec relations** (AC: #5) - [ ] Créer `app/Models/Project.php` - [ ] Propriétés fillable: slug, title_key, description_key, short_description_key, image, url, github_url, date_completed, is_featured, display_order - [ ] Casts: date_completed → date, is_featured → boolean - [ ] Relation `skills()`: belongsToMany(Skill::class)->withPivot(['level_before', 'level_after', 'level_description_key'])->withTimestamps() - [ ] Scope `scopeFeatured($query)` pour les projets mis en avant - [ ] Scope `scopeOrdered($query)` pour le tri par display_order - [ ] **Task 8: Model Skill avec relations** (AC: #5) - [ ] Créer `app/Models/Skill.php` - [ ] Propriétés fillable: slug, name_key, description_key, icon, category, max_level, display_order - [ ] Relation `projects()`: belongsToMany(Project::class)->withPivot(['level_before', 'level_after', 'level_description_key'])->withTimestamps() - [ ] Scope `scopeByCategory($query, $category)` pour filtrer par catégorie - [ ] Scope `scopeOrdered($query)` pour le tri par display_order - [ ] **Task 9: Seeders de base** (AC: #6, #7) - [ ] Créer `database/seeders/TranslationSeeder.php` avec traductions FR et EN de test - [ ] Créer `database/seeders/SkillSeeder.php` avec 8-10 compétences de test (Frontend, Backend, Tools) - [ ] Créer `database/seeders/ProjectSeeder.php` avec 3-4 projets de test - [ ] Créer `database/seeders/SkillProjectSeeder.php` pour lier compétences et projets - [ ] Mettre à jour `DatabaseSeeder.php` pour appeler les seeders dans l'ordre correct (translations → skills → projects → skill_project) - [ ] **Task 10: Validation finale** (AC: tous) - [ ] `php artisan migrate:fresh` fonctionne sans erreur - [ ] `php artisan db:seed` fonctionne sans erreur - [ ] Vérifier en BDD que les tables sont créées avec les bons schémas - [ ] Vérifier que les relations fonctionnent: `Project::first()->skills` et `Skill::first()->projects` - [ ] Vérifier que les traductions fonctionnent: `Translation::getTranslation('project.skycel.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 {{agent_model_name_version}} ### Debug Log References ### Completion Notes List ### Change Log | Date | Change | Author | |------|--------|--------| | 2026-02-03 | Story créée avec contexte complet | SM Agent | ### File List