Files
cvp/docs/superpowers/plans/2026-04-13-phase1-fondations.md
T
fzarifian f1d5882596 feat: initialiser le projet CVP (Next.js 14 + FastAPI + PostgreSQL)
- Frontend : Next.js 14 App Router, TypeScript strict, Tailwind 3, shadcn/ui, next-intl (fr/en)
- Backend : FastAPI, SQLAlchemy 2 async, Alembic, Pydantic 2, Python 3.11
- Infrastructure : Docker Compose (PostgreSQL 16 + Ollama)
- Tooling : ESLint + Prettier (frontend), Ruff (backend), pytest
- Structure complète des dossiers avec pages et routers placeholder
2026-04-13 15:28:01 +02:00

747 lines
17 KiB
Markdown

# 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 (
<main className="p-8">
<h1 className="text-3xl font-bold">Tableau de bord</h1>
</main>
);
}
```
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`.