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>
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
- Given une connexion MariaDB configurée dans Laravel When
php artisan migrateest exécuté Then la tabletranslationsest créée (id, lang, key_name, value, timestamps) avec index unique (lang, key_name) - And la table
projectsest créée (id, slug, title_key, description_key, short_description_key, image, url, github_url, date_completed, is_featured, display_order, timestamps) - And la table
skillsest créée (id, slug, name_key, description_key, icon, category, max_level, display_order) - And la table
skill_projectest créée (id, skill_id, project_id, level_before, level_after, level_description_key) avec foreign keys - And les Models Eloquent sont définis avec leurs relations (Project belongsToMany Skill, etc.)
- And des Seeders de base sont disponibles avec données de test en FR et EN
- And
php artisan db:seedfonctionne 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
skycelsi elle n'existe pas - Configurer
api/.envavec 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
- Créer migration
-
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
- Créer migration
-
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
- Créer migration
-
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
- Créer migration
-
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')
- Créer
-
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
- Créer
-
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
- Créer
-
Task 9: Seeders de base (AC: #6, #7)
- Créer
database/seeders/TranslationSeeder.phpavec traductions FR et EN de test - Créer
database/seeders/SkillSeeder.phpavec 8-10 compétences de test (Frontend, Backend, Tools) - Créer
database/seeders/ProjectSeeder.phpavec 3-4 projets de test - Créer
database/seeders/SkillProjectSeeder.phppour lier compétences et projets - Mettre à jour
DatabaseSeeder.phppour appeler les seeders dans l'ordre correct (translations → skills → projects → skill_project)
- Créer
-
Task 10: Validation finale (AC: tous)
php artisan migrate:freshfonctionne sans erreurphp artisan db:seedfonctionne 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()->skillsetSkill::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 :
- Skycel Portfolio (ce projet)
- Projet fictif e-commerce
- 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/.envavec variables DB_* configurées
Cette story PRÉPARE pour :
- Story 1.3 (i18n) : La table
translationssera 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/etapi/ - Laravel 12 configuré en mode API-only
- Fichier
.env.examplecréé avec variables DB_* - Middleware VerifyApiKey en place
Files created in Story 1.1:
api/.env.exampleavec 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écifieskill_project. Résolu en ajoutant le nom de table explicite dans les relationsbelongsToMany. - 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 --seedfonctionne 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É