🎉 Init monorepo Nuxt 4 + Laravel 12 (Story 1.1)
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>
This commit is contained in:
@@ -0,0 +1,421 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user