Files
Portfolio-Game/docs/implementation-artifacts/1-2-base-donnees-migrations-initiales.md
skycel bba6128236 🗃️ Add database schema, models & seeders (Story 1.2)
Migrations (translations, projects, skills, skill_project), Eloquent models
with belongsToMany relations, scopes, and test seeders (74 translations FR/EN,
10 skills, 3 projects, 12 skill-project links).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 15:11:21 +01:00

15 KiB

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

  • 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-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)

-- 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

# 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É