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

