Setup complet de l'infrastructure projet : - Frontend Nuxt 4 (SSR, TypeScript, i18n, Pinia, TailwindCSS) - Backend Laravel 12 API-only avec middleware X-API-Key et CORS - Design tokens (sky-dark, sky-accent, sky-text) et polices (Merriweather, Inter) - Documentation planning et implementation artifacts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
422 lines
14 KiB
Markdown
422 lines
14 KiB
Markdown
# Story 1.1: Initialisation du monorepo et infrastructure
|
|
|
|
Status: review
|
|
|
|
## Story
|
|
|
|
As a développeur,
|
|
I want un projet monorepo Nuxt 4 + Laravel 12 initialisé avec les configurations de base,
|
|
so that le développement peut commencer sur des fondations solides.
|
|
|
|
## Acceptance Criteria
|
|
|
|
1. **Given** un nouveau repository Git **When** le projet est initialisé **Then** la structure monorepo `frontend/` (Nuxt 4) + `api/` (Laravel 12) est en place
|
|
2. **And** Nuxt 4 est configuré avec SSR activé, TypeScript, et les modules `@nuxtjs/i18n`, `@nuxtjs/tailwindcss`, `@pinia/nuxt`, `nuxt/image`, `@nuxtjs/sitemap`
|
|
3. **And** Laravel 12 est configuré en mode API-only avec CORS autorisant le domaine frontend
|
|
4. **And** le middleware API Key (`X-API-Key`) est en place sur les routes API
|
|
5. **And** les fichiers `.env.example` existent pour frontend et backend
|
|
6. **And** TailwindCSS est configuré avec les design tokens (`sky-dark`, `sky-accent` #fa784f, `sky-text`)
|
|
7. **And** les polices sont définies (serif narrateur + sans-serif UI)
|
|
8. **And** le `.gitignore` est approprié pour les deux applications
|
|
|
|
## Tasks / Subtasks
|
|
|
|
- [x] **Task 1: Initialisation structure monorepo** (AC: #1)
|
|
- [x] Créer le dossier racine `skycel/` avec README.md
|
|
- [x] Configurer `.gitignore` global (node_modules, vendor, .env, etc.)
|
|
- [x] Initialiser Git repository
|
|
|
|
- [x] **Task 2: Setup Frontend Nuxt 4** (AC: #1, #2)
|
|
- [x] Exécuter `npx nuxi@latest init frontend`
|
|
- [x] Confirmer structure Nuxt 4 avec dossier `app/`
|
|
- [x] Activer TypeScript (déjà par défaut dans Nuxt 4)
|
|
- [x] Installer modules: `@nuxtjs/i18n`, `@nuxtjs/tailwindcss`, `@pinia/nuxt`, `@nuxt/image`, `@nuxtjs/sitemap`
|
|
- [x] Configurer `nuxt.config.ts` avec SSR activé
|
|
- [x] Créer `frontend/.env.example`
|
|
|
|
- [x] **Task 3: Setup Backend Laravel 12** (AC: #1, #3, #4)
|
|
- [x] Exécuter `composer create-project laravel/laravel api`
|
|
- [x] Configurer en mode API-only (supprimer views Blade inutiles)
|
|
- [x] Configurer CORS dans `config/cors.php` pour autoriser le domaine frontend
|
|
- [x] Créer middleware `VerifyApiKey` pour vérifier header `X-API-Key`
|
|
- [x] Enregistrer le middleware sur les routes API
|
|
- [x] Créer `api/.env.example`
|
|
|
|
- [x] **Task 4: Configuration TailwindCSS avec design tokens** (AC: #6)
|
|
- [x] Configurer `tailwind.config.js` avec thème custom
|
|
- [x] Définir tokens couleurs: `sky-dark` (noir→bleu), `sky-accent` (#fa784f), `sky-text` (blanc cassé)
|
|
- [x] Définir variantes hover/focus pour l'accent
|
|
- [x] Configurer purge pour production
|
|
|
|
- [x] **Task 5: Configuration des polices** (AC: #7)
|
|
- [x] Choisir police serif élégante pour narrateur/PNJ (ex: Merriweather, Lora, Playfair Display)
|
|
- [x] Choisir police sans-serif moderne pour UI (ex: Inter, Open Sans, Nunito)
|
|
- [x] Configurer les polices dans `tailwind.config.js` (fontFamily)
|
|
- [x] Importer les polices via Google Fonts ou fichiers locaux
|
|
|
|
- [x] **Task 6: Fichiers .env.example** (AC: #5)
|
|
- [x] `frontend/.env.example` avec: `NUXT_PUBLIC_API_URL`, `NUXT_PUBLIC_API_KEY`
|
|
- [x] `api/.env.example` avec: `APP_KEY`, `DB_*`, `API_KEY`, `CORS_ALLOWED_ORIGINS`
|
|
|
|
- [x] **Task 7: Validation finale** (AC: tous)
|
|
- [x] `cd frontend && npm run dev` fonctionne
|
|
- [x] `cd api && php artisan serve` fonctionne
|
|
- [x] Requête API avec header `X-API-Key` valide retourne 200
|
|
- [x] Requête API sans header retourne 401
|
|
- [x] Structure des dossiers conforme
|
|
|
|
## Dev Notes
|
|
|
|
### Architecture Monorepo
|
|
|
|
```
|
|
skycel/
|
|
├── frontend/ # Application Nuxt 4
|
|
│ ├── app/ # Code applicatif (structure Nuxt 4)
|
|
│ │ ├── pages/
|
|
│ │ ├── components/
|
|
│ │ ├── composables/
|
|
│ │ ├── stores/
|
|
│ │ ├── layouts/
|
|
│ │ ├── plugins/
|
|
│ │ ├── assets/
|
|
│ │ └── app.vue
|
|
│ ├── server/ # Server routes/API Nuxt (si besoin)
|
|
│ ├── public/
|
|
│ ├── i18n/
|
|
│ ├── nuxt.config.ts
|
|
│ ├── tailwind.config.js
|
|
│ ├── .env.example
|
|
│ └── package.json
|
|
├── api/ # Backend Laravel 12
|
|
│ ├── app/
|
|
│ │ ├── Http/
|
|
│ │ │ ├── Controllers/
|
|
│ │ │ ├── Middleware/
|
|
│ │ │ │ └── VerifyApiKey.php # CRÉER
|
|
│ │ │ ├── Requests/
|
|
│ │ │ └── Resources/
|
|
│ │ └── Models/
|
|
│ ├── database/
|
|
│ ├── routes/
|
|
│ │ └── api.php
|
|
│ ├── config/
|
|
│ │ └── cors.php # CONFIGURER
|
|
│ ├── bootstrap/
|
|
│ │ └── app.php # Enregistrer middleware
|
|
│ ├── .env.example
|
|
│ └── composer.json
|
|
├── docs/ # Documentation projet (existe déjà)
|
|
├── .gitignore
|
|
└── README.md
|
|
```
|
|
|
|
### Commandes d'initialisation
|
|
|
|
```bash
|
|
# Frontend Nuxt 4
|
|
npx nuxi@latest init frontend
|
|
cd frontend
|
|
npm install @nuxtjs/i18n @nuxtjs/tailwindcss @pinia/nuxt @nuxt/image @nuxtjs/sitemap pinia-plugin-persistedstate
|
|
|
|
# Backend Laravel 12
|
|
composer create-project laravel/laravel api
|
|
cd api
|
|
# Pas de packages supplémentaires pour cette story
|
|
```
|
|
|
|
### Configuration nuxt.config.ts
|
|
|
|
```typescript
|
|
// frontend/nuxt.config.ts
|
|
export default defineNuxtConfig({
|
|
devtools: { enabled: true },
|
|
|
|
// SSR activé (défaut)
|
|
ssr: true,
|
|
|
|
// Structure Nuxt 4
|
|
future: {
|
|
compatibilityVersion: 4,
|
|
},
|
|
|
|
modules: [
|
|
'@nuxtjs/i18n',
|
|
'@nuxtjs/tailwindcss',
|
|
'@pinia/nuxt',
|
|
'@nuxt/image',
|
|
'@nuxtjs/sitemap',
|
|
],
|
|
|
|
// i18n sera configuré en Story 1.3
|
|
i18n: {
|
|
locales: ['fr', 'en'],
|
|
defaultLocale: 'fr',
|
|
strategy: 'prefix_except_default',
|
|
},
|
|
|
|
// Transitions de page (configuré en Story 1.4)
|
|
app: {
|
|
pageTransition: { name: 'page', mode: 'out-in' },
|
|
},
|
|
|
|
runtimeConfig: {
|
|
public: {
|
|
apiUrl: process.env.NUXT_PUBLIC_API_URL || 'http://localhost:8000/api',
|
|
apiKey: process.env.NUXT_PUBLIC_API_KEY || '',
|
|
},
|
|
},
|
|
})
|
|
```
|
|
|
|
### Design Tokens TailwindCSS
|
|
|
|
```javascript
|
|
// frontend/tailwind.config.js
|
|
/** @type {import('tailwindcss').Config} */
|
|
module.exports = {
|
|
content: [
|
|
'./app/**/*.{vue,js,ts}',
|
|
'./components/**/*.{vue,js,ts}',
|
|
'./layouts/**/*.{vue,js,ts}',
|
|
'./pages/**/*.{vue,js,ts}',
|
|
],
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
'sky-dark': {
|
|
DEFAULT: '#0a0e1a', // Noir tirant vers le bleu
|
|
50: '#1a1f2e',
|
|
100: '#151a28',
|
|
200: '#10141f',
|
|
300: '#0c1019',
|
|
400: '#080c14',
|
|
500: '#0a0e1a', // Base
|
|
600: '#060810',
|
|
700: '#04060c',
|
|
800: '#020408',
|
|
900: '#010204',
|
|
},
|
|
'sky-accent': {
|
|
DEFAULT: '#fa784f', // Orange chaud
|
|
hover: '#fb8c68',
|
|
active: '#f96436',
|
|
50: '#fff4f0',
|
|
100: '#ffe8e0',
|
|
200: '#ffd1c1',
|
|
300: '#ffb9a2',
|
|
400: '#fca283',
|
|
500: '#fa784f', // Base
|
|
600: '#e86940',
|
|
700: '#d65a31',
|
|
800: '#c44b22',
|
|
900: '#b23c13',
|
|
},
|
|
'sky-text': {
|
|
DEFAULT: '#f5f0e6', // Blanc cassé tirant vers jaune
|
|
muted: '#b8b3a8',
|
|
50: '#fdfcfa',
|
|
100: '#fbf9f5',
|
|
200: '#f7f3eb',
|
|
300: '#f5f0e6', // Base
|
|
400: '#e8e3d9',
|
|
500: '#dbd6cc',
|
|
600: '#cec9bf',
|
|
700: '#c1bcb2',
|
|
800: '#b4afa5',
|
|
900: '#a7a298',
|
|
},
|
|
},
|
|
fontFamily: {
|
|
// Police narrative (serif) - pour narrateur, PNJ, dialogues
|
|
'narrative': ['Merriweather', 'Georgia', 'serif'],
|
|
// Police UI (sans-serif) - pour interface, boutons, labels
|
|
'ui': ['Inter', 'system-ui', 'sans-serif'],
|
|
},
|
|
},
|
|
},
|
|
plugins: [],
|
|
}
|
|
```
|
|
|
|
### Middleware Laravel VerifyApiKey
|
|
|
|
```php
|
|
<?php
|
|
// api/app/Http/Middleware/VerifyApiKey.php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class VerifyApiKey
|
|
{
|
|
public function handle(Request $request, Closure $next): Response
|
|
{
|
|
$apiKey = $request->header('X-API-Key');
|
|
|
|
if (!$apiKey || $apiKey !== config('app.api_key')) {
|
|
return response()->json([
|
|
'error' => [
|
|
'code' => 'INVALID_API_KEY',
|
|
'message' => 'Invalid or missing API key',
|
|
]
|
|
], 401);
|
|
}
|
|
|
|
return $next($request);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Configuration CORS Laravel
|
|
|
|
```php
|
|
<?php
|
|
// api/config/cors.php
|
|
|
|
return [
|
|
'paths' => ['api/*'],
|
|
'allowed_methods' => ['*'],
|
|
'allowed_origins' => explode(',', env('CORS_ALLOWED_ORIGINS', 'http://localhost:3000')),
|
|
'allowed_origins_patterns' => [],
|
|
'allowed_headers' => ['*'],
|
|
'exposed_headers' => [],
|
|
'max_age' => 0,
|
|
'supports_credentials' => false,
|
|
];
|
|
```
|
|
|
|
### Variables d'environnement
|
|
|
|
**frontend/.env.example:**
|
|
```env
|
|
# API Configuration
|
|
NUXT_PUBLIC_API_URL=http://localhost:8000/api
|
|
NUXT_PUBLIC_API_KEY=your-api-key-here
|
|
|
|
# Site URL (for sitemap, SEO)
|
|
NUXT_PUBLIC_SITE_URL=http://localhost:3000
|
|
```
|
|
|
|
**api/.env.example:**
|
|
```env
|
|
APP_NAME=Skycel
|
|
APP_ENV=local
|
|
APP_KEY=
|
|
APP_DEBUG=true
|
|
APP_URL=http://localhost:8000
|
|
|
|
# Database
|
|
DB_CONNECTION=mysql
|
|
DB_HOST=127.0.0.1
|
|
DB_PORT=3306
|
|
DB_DATABASE=skycel
|
|
DB_USERNAME=root
|
|
DB_PASSWORD=
|
|
|
|
# API Security
|
|
API_KEY=your-api-key-here
|
|
|
|
# CORS
|
|
CORS_ALLOWED_ORIGINS=http://localhost:3000
|
|
```
|
|
|
|
### Project Structure Notes
|
|
|
|
- **Nuxt 4** utilise la nouvelle structure `app/` (pas `src/`)
|
|
- Les composants dans `app/components/` sont auto-importés
|
|
- Les composables dans `app/composables/` sont auto-importés
|
|
- Les stores Pinia dans `app/stores/` sont accessibles via auto-import
|
|
- Composants client-only: utiliser le suffixe `.client.vue` (pas de SSR)
|
|
|
|
### References
|
|
|
|
- [Source: docs/planning-artifacts/architecture.md#Starter-Template-Evaluation]
|
|
- [Source: docs/planning-artifacts/architecture.md#Structure-Monorepo]
|
|
- [Source: docs/planning-artifacts/architecture.md#Authentication-&-Security]
|
|
- [Source: docs/planning-artifacts/ux-design-specification.md#Color-System]
|
|
- [Source: docs/planning-artifacts/ux-design-specification.md#Typography-System]
|
|
- [Source: docs/planning-artifacts/epics.md#Story-1.1]
|
|
|
|
### Technical Requirements
|
|
|
|
| Requirement | Value | Source |
|
|
|-------------|-------|--------|
|
|
| Nuxt version | 4.x (latest) | Architecture |
|
|
| Laravel version | 12.x | Architecture |
|
|
| Node.js | 18+ | Nuxt 4 requirement |
|
|
| PHP | 8.2+ | Laravel 12 requirement |
|
|
| TypeScript | Enabled | Architecture |
|
|
| SSR | Enabled | Architecture, NFR5 |
|
|
|
|
### Libraries to Install
|
|
|
|
**Frontend (npm):**
|
|
| Package | Version | Purpose |
|
|
|---------|---------|---------|
|
|
| @nuxtjs/i18n | 8.x | Internationalisation |
|
|
| @nuxtjs/tailwindcss | 6.x | Styling |
|
|
| @pinia/nuxt | 0.5.x | State management |
|
|
| @nuxt/image | 1.x | Image optimization |
|
|
| @nuxtjs/sitemap | 5.x | SEO sitemap |
|
|
| pinia-plugin-persistedstate | 3.x | LocalStorage persistence |
|
|
|
|
**Backend (composer):**
|
|
- Aucun package supplémentaire pour cette story (Laravel de base suffit)
|
|
|
|
## Dev Agent Record
|
|
|
|
### Agent Model Used
|
|
|
|
Claude Opus 4.5 (claude-opus-4-5-20251101)
|
|
|
|
### Debug Log References
|
|
|
|
- TailwindCSS: Le module `@nuxtjs/tailwindcss` v6.14 provoquait une erreur PostCSS `Cannot use 'import.meta' outside a module` sur Node.js 18. Résolu en remplaçant le module par une configuration PostCSS directe dans `nuxt.config.ts`.
|
|
- PHP: Le PHP en PATH (8.0.3) est incompatible avec Laravel 12. Utilisation de PHP 8.2.29 disponible dans Laragon pour la création du projet et l'exécution.
|
|
- pinia-plugin-persistedstate: La v4 requiert pinia 3+, incompatible avec @pinia/nuxt 0.9.0 (pinia 2). Downgrade vers v3.2.
|
|
|
|
### Completion Notes List
|
|
|
|
- Structure monorepo `frontend/` + `api/` créée et fonctionnelle
|
|
- Nuxt 4 (3.17.5) configuré avec SSR, TypeScript, i18n, Pinia, @nuxt/image, sitemap
|
|
- TailwindCSS v3 configuré via PostCSS avec design tokens (sky-dark, sky-accent, sky-text)
|
|
- Polices Merriweather (narrative) et Inter (UI) importées via Google Fonts
|
|
- Laravel 12.50 installé en mode API-only avec CORS et middleware VerifyApiKey
|
|
- Middleware API Key vérifié : 401 sans clé, 200 avec clé valide
|
|
- Fichiers .env.example créés pour frontend et backend
|
|
|
|
### Change Log
|
|
| Date | Change | Author |
|
|
|------|--------|--------|
|
|
| 2026-02-03 | Story créée avec contexte complet | SM Agent |
|
|
| 2026-02-05 | Implémentation complète de toutes les tâches (Tasks 1-7) | Dev Agent (Claude Opus 4.5) |
|
|
|
|
### File List
|
|
|
|
**Nouveaux fichiers :**
|
|
- `README.md` - Documentation racine du monorepo
|
|
- `.gitignore` - Gitignore global
|
|
- `frontend/package.json` - Dependencies Nuxt 4
|
|
- `frontend/nuxt.config.ts` - Configuration Nuxt 4 avec SSR, modules, PostCSS
|
|
- `frontend/tsconfig.json` - Config TypeScript
|
|
- `frontend/tailwind.config.js` - Design tokens TailwindCSS
|
|
- `frontend/app/app.vue` - Composant racine Vue
|
|
- `frontend/app/pages/index.vue` - Page d'accueil placeholder
|
|
- `frontend/app/assets/css/main.css` - CSS global avec import polices
|
|
- `frontend/.env.example` - Variables d'environnement frontend
|
|
- `api/` - Projet Laravel 12 complet (via composer create-project)
|
|
- `api/app/Http/Middleware/VerifyApiKey.php` - Middleware authentification API Key
|
|
- `api/config/cors.php` - Configuration CORS
|
|
- `api/routes/api.php` - Routes API avec endpoint /health
|
|
|
|
**Fichiers modifiés :**
|
|
- `api/bootstrap/app.php` - Routing API-only, enregistrement middleware VerifyApiKey
|
|
- `api/config/app.php` - Ajout config api_key
|
|
- `api/.env.example` - Ajout APP_NAME=Skycel, DB config MySQL, API_KEY, CORS_ALLOWED_ORIGINS
|
|
- `api/.env` - Mêmes ajouts que .env.example avec valeurs dev
|
|
- `api/routes/web.php` - Vidé (mode API-only)
|