# Phase 1 — Fondations : Scaffolding du projet CVP > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Initialiser le monorepo CVP avec un frontend Next.js 14 et un backend FastAPI, configurer le tooling (lint, format, tests), créer la structure de dossiers, et produire un commit initial propre. **Architecture:** Monorepo avec deux sous-projets : `frontend/` (Next.js 14 App Router, TypeScript strict, Tailwind 3, shadcn/ui) et `backend/` (FastAPI, SQLAlchemy 2, Alembic, Python 3.11). Docker Compose pour PostgreSQL 16 et Ollama. Les deux sous-projets sont indépendants et communiquent via REST API. **Tech Stack:** Next.js 14, React 18, TypeScript, Tailwind CSS 3, shadcn/ui, next-intl, FastAPI, SQLAlchemy 2, Alembic, Pydantic 2, PostgreSQL 16, Python 3.11 --- ## Prérequis - Python 3.11 doit etre installé (`sudo dnf install -y python3.11 python3.11-pip python3.11-devel`) - Node.js 22+ et npm 10+ sont déjà disponibles - PostgreSQL 16 sera lancé via Docker Compose --- ## Task 1 : Fichiers racine du monorepo **Files:** - Create: `.gitignore` - Create: `.env.example` - Create: `docker-compose.yml` - [ ] **Step 1: Créer le `.gitignore`** ```gitignore # Dependencies node_modules/ __pycache__/ *.pyc .venv/ venv/ # Environment .env .env.local .env.*.local # Build .next/ out/ dist/ build/ *.egg-info/ # IDE .vscode/ .idea/ *.swp *.swo # OS .DS_Store Thumbs.db # Testing coverage/ htmlcov/ .pytest_cache/ .coverage # Database *.db *.sqlite3 # Generated *.pdf *.docx ``` - [ ] **Step 2: Créer le `.env.example`** ```env # PostgreSQL POSTGRES_HOST=localhost POSTGRES_PORT=5432 POSTGRES_DB=cvp POSTGRES_USER=cvp POSTGRES_PASSWORD=cvp_dev_password # France Travail API FRANCE_TRAVAIL_CLIENT_ID= FRANCE_TRAVAIL_CLIENT_SECRET= # Adzuna API ADZUNA_APP_ID= ADZUNA_APP_KEY= # Ollama OLLAMA_BASE_URL=http://localhost:11434 OLLAMA_MODEL=phi3:mini # Backend BACKEND_HOST=0.0.0.0 BACKEND_PORT=8000 # Frontend NEXT_PUBLIC_API_URL=http://localhost:8000 ``` - [ ] **Step 3: Créer le `docker-compose.yml`** ```yaml services: postgres: image: postgres:16-alpine environment: POSTGRES_DB: cvp POSTGRES_USER: cvp POSTGRES_PASSWORD: cvp_dev_password ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data ollama: image: ollama/ollama:latest ports: - "11434:11434" volumes: - ollama_data:/root/.ollama deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] volumes: postgres_data: ollama_data: ``` --- ## Task 2 : Initialiser le frontend Next.js **Files:** - Create: `frontend/` (via `create-next-app`) - Modify: `frontend/tsconfig.json` - Modify: `frontend/package.json` - [ ] **Step 1: Créer le projet Next.js 14** ```bash cd /home/localadm/cvp npx create-next-app@14 frontend \ --typescript \ --tailwind \ --eslint \ --app \ --src-dir \ --import-alias "@/*" \ --no-turbopack ``` - [ ] **Step 2: Vérifier que TypeScript strict est activé** Dans `frontend/tsconfig.json`, s'assurer que `"strict": true` est présent dans `compilerOptions`. - [ ] **Step 3: Vérifier que l'app démarre** ```bash cd /home/localadm/cvp/frontend npm run dev & sleep 5 curl -s http://localhost:3000 | head -20 kill %1 ``` Expected: réponse HTML valide. --- ## Task 3 : Configurer Tailwind, shadcn/ui et next-intl dans le frontend **Files:** - Modify: `frontend/tailwind.config.ts` (thème CVP) - Modify: `frontend/src/app/globals.css` - Create: `frontend/components.json` (via shadcn init) - Create: `frontend/src/messages/fr.json` - Create: `frontend/src/messages/en.json` - Create: `frontend/src/i18n/request.ts` - Create: `frontend/src/i18n/routing.ts` - Modify: `frontend/next.config.js` (plugin next-intl) - Modify: `frontend/package.json` (ajout next-intl) - [ ] **Step 1: Installer et configurer shadcn/ui** ```bash cd /home/localadm/cvp/frontend npx shadcn@latest init -d ``` - [ ] **Step 2: Configurer le thème Tailwind CVP** Modifier `frontend/tailwind.config.ts` pour ajouter les couleurs du design system : - Primaire : indigo (#6366F1) - Secondaire : cyan (#06B6D4) - Accent : orange (#F59E0B) - Succès : vert (#10B981) - Danger : rouge (#EF4444) - Fond dark : #0F172A / #1E293B - Polices : Inter (UI) + JetBrains Mono (code) - `borderRadius` : `xl` par défaut - [ ] **Step 3: Installer et configurer next-intl** ```bash cd /home/localadm/cvp/frontend npm install next-intl ``` Créer `src/messages/fr.json` : ```json { "common": { "appName": "CVP", "loading": "Chargement...", "error": "Une erreur est survenue", "save": "Enregistrer", "cancel": "Annuler", "delete": "Supprimer", "edit": "Modifier", "back": "Retour" }, "nav": { "dashboard": "Tableau de bord", "offres": "Offres", "profil": "Profil", "cv": "CV", "candidatures": "Candidatures", "parametres": "Parametres" } } ``` Créer `src/messages/en.json` avec les traductions anglaises correspondantes. Créer `src/i18n/routing.ts` : ```typescript import { defineRouting } from "next-intl/routing"; export const routing = defineRouting({ locales: ["fr", "en"], defaultLocale: "fr", }); ``` Créer `src/i18n/request.ts` : ```typescript import { getRequestConfig } from "next-intl/server"; import { routing } from "./routing"; export default getRequestConfig(async ({ requestLocale }) => { let locale = await requestLocale; if (!locale || !routing.locales.includes(locale as "fr" | "en")) { locale = routing.defaultLocale; } return { locale, messages: (await import(`../messages/${locale}.json`)).default, }; }); ``` Mettre a jour `next.config.js` avec le plugin `createNextIntlPlugin`. - [ ] **Step 4: Restructurer le App Router pour i18n** Déplacer les pages sous `src/app/[locale]/` et créer le layout racine avec `NextIntlClientProvider`. Créer le middleware `src/middleware.ts` pour la redirection de locale. --- ## Task 4 : Créer la structure de dossiers frontend **Files:** - Create: `frontend/src/components/ui/.gitkeep` - Create: `frontend/src/components/layout/.gitkeep` - Create: `frontend/src/components/offres/.gitkeep` - Create: `frontend/src/components/cv/.gitkeep` - Create: `frontend/src/components/profil/.gitkeep` - Create: `frontend/src/lib/.gitkeep` - Create: `frontend/src/hooks/.gitkeep` - Create: `frontend/src/types/.gitkeep` - Create: `frontend/src/app/[locale]/dashboard/page.tsx` - Create: `frontend/src/app/[locale]/offres/page.tsx` - Create: `frontend/src/app/[locale]/profil/page.tsx` - Create: `frontend/src/app/[locale]/cv/page.tsx` - Create: `frontend/src/app/[locale]/candidatures/page.tsx` - Create: `frontend/src/app/[locale]/parametres/page.tsx` - [ ] **Step 1: Créer les dossiers de composants** Créer les sous-dossiers dans `components/` avec des `.gitkeep` pour les dossiers vides. - [ ] **Step 2: Créer les pages placeholder** Chaque page sous `src/app/[locale]/` est un simple composant avec le titre de la section : ```tsx // src/app/[locale]/dashboard/page.tsx export default function DashboardPage() { return (

Tableau de bord

); } ``` Meme pattern pour : `offres/page.tsx`, `profil/page.tsx`, `cv/page.tsx`, `candidatures/page.tsx`, `parametres/page.tsx`. --- ## Task 5 : Configurer ESLint et Prettier pour le frontend **Files:** - Modify: `frontend/.eslintrc.json` - Create: `frontend/.prettierrc` - Modify: `frontend/package.json` (scripts) - [ ] **Step 1: Installer Prettier** ```bash cd /home/localadm/cvp/frontend npm install -D prettier eslint-config-prettier ``` - [ ] **Step 2: Créer `.prettierrc`** ```json { "semi": true, "singleQuote": false, "tabWidth": 2, "trailingComma": "all", "printWidth": 100 } ``` - [ ] **Step 3: Configurer ESLint** Mettre a jour `.eslintrc.json` pour inclure `prettier` dans `extends` et ajouter les regles : `no-console: warn`, `@typescript-eslint/no-explicit-any: error`. - [ ] **Step 4: Ajouter les scripts npm** Dans `package.json`, ajouter : ```json { "scripts": { "lint": "next lint", "lint:fix": "next lint --fix", "format": "prettier --write \"src/**/*.{ts,tsx,json,css}\"", "format:check": "prettier --check \"src/**/*.{ts,tsx,json,css}\"", "type-check": "tsc --noEmit" } } ``` - [ ] **Step 5: Vérifier que lint passe** ```bash cd /home/localadm/cvp/frontend npm run lint npm run format:check npm run type-check ``` Expected: pas d'erreurs. --- ## Task 6 : Initialiser le backend Python **Files:** - Create: `backend/pyproject.toml` - Create: `backend/requirements.txt` - Create: `backend/app/__init__.py` - Create: `backend/app/main.py` - Create: `backend/app/config.py` - [ ] **Step 1: Installer Python 3.11 et créer le virtualenv** ```bash sudo dnf install -y python3.11 python3.11-pip python3.11-devel cd /home/localadm/cvp/backend python3.11 -m venv .venv source .venv/bin/activate ``` - [ ] **Step 2: Créer `pyproject.toml`** ```toml [project] name = "cvp-backend" version = "0.1.0" description = "CVP - Backend API pour la recherche d'emploi intelligente" requires-python = ">=3.11" [tool.pytest.ini_options] testpaths = ["tests"] asyncio_mode = "auto" [tool.ruff] target-version = "py311" line-length = 100 [tool.ruff.lint] select = ["E", "F", "W", "I", "N", "UP", "ANN", "B", "A", "SIM"] ignore = ["ANN101", "ANN102"] [tool.ruff.lint.isort] known-first-party = ["app"] ``` - [ ] **Step 3: Créer `requirements.txt`** ```txt fastapi==0.115.12 uvicorn[standard]==0.34.2 sqlalchemy[asyncio]==2.0.41 asyncpg==0.30.0 alembic==1.15.2 pydantic==2.11.3 pydantic-settings==2.9.1 python-dotenv==1.1.0 httpx==0.28.1 apscheduler==3.11.0 weasyprint==65.1 python-docx==1.1.2 ollama==0.4.8 # Dev pytest==8.3.5 pytest-asyncio==0.25.3 pytest-cov==6.1.1 ruff==0.11.8 httpx==0.28.1 ``` - [ ] **Step 4: Installer les dépendances** ```bash cd /home/localadm/cvp/backend source .venv/bin/activate pip install -r requirements.txt ``` - [ ] **Step 5: Créer le point d'entrée FastAPI** `backend/app/__init__.py` : fichier vide. `backend/app/config.py` : ```python from pydantic_settings import BaseSettings class Settings(BaseSettings): postgres_host: str = "localhost" postgres_port: int = 5432 postgres_db: str = "cvp" postgres_user: str = "cvp" postgres_password: str = "cvp_dev_password" france_travail_client_id: str = "" france_travail_client_secret: str = "" adzuna_app_id: str = "" adzuna_app_key: str = "" ollama_base_url: str = "http://localhost:11434" ollama_model: str = "phi3:mini" model_config = {"env_file": ".env", "env_file_encoding": "utf-8"} @property def database_url(self) -> str: return ( f"postgresql+asyncpg://{self.postgres_user}:{self.postgres_password}" f"@{self.postgres_host}:{self.postgres_port}/{self.postgres_db}" ) settings = Settings() ``` `backend/app/main.py` : ```python import logging from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = FastAPI( title="CVP API", description="API pour la recherche d'emploi intelligente", version="0.1.0", ) app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.get("/health") async def health_check() -> dict[str, str]: return {"status": "ok"} ``` - [ ] **Step 6: Vérifier que le backend démarre** ```bash cd /home/localadm/cvp/backend source .venv/bin/activate uvicorn app.main:app --host 0.0.0.0 --port 8000 & sleep 3 curl -s http://localhost:8000/health kill %1 ``` Expected: `{"status":"ok"}` --- ## Task 7 : Créer la structure de dossiers backend **Files:** - Create: `backend/app/api/__init__.py` - Create: `backend/app/api/routes/__init__.py` - Create: `backend/app/api/routes/offres.py` - Create: `backend/app/api/routes/profil.py` - Create: `backend/app/api/routes/cv.py` - Create: `backend/app/api/routes/candidatures.py` - Create: `backend/app/api/routes/parametres.py` - Create: `backend/app/api/deps.py` - Create: `backend/app/models/__init__.py` - Create: `backend/app/schemas/__init__.py` - Create: `backend/app/services/__init__.py` - Create: `backend/app/templates/.gitkeep` - Create: `backend/app/db/__init__.py` - Create: `backend/app/db/database.py` - Create: `backend/tests/__init__.py` - Create: `backend/tests/test_health.py` - [ ] **Step 1: Créer les packages Python** Créer tous les dossiers avec `__init__.py` vides pour : `api/`, `api/routes/`, `models/`, `schemas/`, `services/`, `db/`, `tests/`. - [ ] **Step 2: Créer les routers placeholder** Chaque fichier de route est un router minimal : ```python # backend/app/api/routes/offres.py from fastapi import APIRouter router = APIRouter(prefix="/offres", tags=["offres"]) ``` Meme pattern pour `profil.py`, `cv.py`, `candidatures.py`, `parametres.py`. - [ ] **Step 3: Créer le module database** ```python # backend/app/db/database.py from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from app.config import settings engine = create_async_engine(settings.database_url, echo=False) async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async def get_db() -> AsyncSession: async with async_session() as session: yield session ``` - [ ] **Step 4: Créer le module deps** ```python # backend/app/api/deps.py from typing import Annotated from fastapi import Depends from sqlalchemy.ext.asyncio import AsyncSession from app.db.database import get_db DbSession = Annotated[AsyncSession, Depends(get_db)] ``` - [ ] **Step 5: Enregistrer les routers dans main.py** Ajouter dans `app/main.py` : ```python from app.api.routes import offres, profil, cv, candidatures, parametres app.include_router(offres.router, prefix="/api") app.include_router(profil.router, prefix="/api") app.include_router(cv.router, prefix="/api") app.include_router(candidatures.router, prefix="/api") app.include_router(parametres.router, prefix="/api") ``` - [ ] **Step 6: Écrire le premier test** ```python # backend/tests/test_health.py from fastapi.testclient import TestClient from app.main import app client = TestClient(app) def test_health_check(): response = client.get("/health") assert response.status_code == 200 assert response.json() == {"status": "ok"} ``` - [ ] **Step 7: Lancer le test** ```bash cd /home/localadm/cvp/backend source .venv/bin/activate pytest tests/test_health.py -v ``` Expected: 1 test PASSED. --- ## Task 8 : Configurer Ruff (lint + format) pour le backend **Files:** - Modify: `backend/pyproject.toml` (déjà fait en Task 6) - [ ] **Step 1: Vérifier que ruff lint passe** ```bash cd /home/localadm/cvp/backend source .venv/bin/activate ruff check app/ tests/ ``` Expected: pas d'erreurs (corriger si nécessaire). - [ ] **Step 2: Vérifier que ruff format passe** ```bash ruff format --check app/ tests/ ``` Expected: pas de diff (corriger si nécessaire avec `ruff format app/ tests/`). --- ## Task 9 : Configurer Alembic pour les migrations **Files:** - Create: `backend/alembic.ini` - Create: `backend/app/db/migrations/env.py` - Create: `backend/app/db/migrations/versions/.gitkeep` - Create: `backend/app/db/migrations/script.py.mako` - [ ] **Step 1: Initialiser Alembic** ```bash cd /home/localadm/cvp/backend source .venv/bin/activate alembic init app/db/migrations ``` - [ ] **Step 2: Configurer `alembic.ini`** Mettre `sqlalchemy.url` a vide (sera fourni par `env.py` dynamiquement). - [ ] **Step 3: Configurer `env.py`** Modifier `app/db/migrations/env.py` pour : - Importer `settings` depuis `app.config` - Utiliser `settings.database_url` comme URL de connexion - Importer le `Base` de SQLAlchemy pour l'autogénération des migrations --- ## Task 10 : Générer le CLAUDE.md **Files:** - Create: `CLAUDE.md` - [ ] **Step 1: Créer le CLAUDE.md a la racine** Le fichier doit contenir : - Description du projet - Commandes utiles (dev, test, lint, format, build) - Conventions de codage (résumé du PROMPT.md) - Structure du projet - Regles impératives --- ## Task 11 : Commit initial - [ ] **Step 1: Vérifier que tout fonctionne** ```bash # Frontend cd /home/localadm/cvp/frontend npm run lint npm run type-check npm run build # Backend cd /home/localadm/cvp/backend source .venv/bin/activate ruff check app/ tests/ pytest tests/ -v ``` Expected: tout passe sans erreur. - [ ] **Step 2: Commit initial** ```bash cd /home/localadm/cvp git add . git commit -m "feat: initialiser le projet CVP (Next.js 14 + FastAPI + PostgreSQL)" ``` Note : ne PAS ajouter `Co-Authored-By`.