- 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
17 KiB
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
# 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
# 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
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/(viacreate-next-app) -
Modify:
frontend/tsconfig.json -
Modify:
frontend/package.json -
Step 1: Créer le projet Next.js 14
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
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
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:xlpar défaut -
Step 3: Installer et configurer next-intl
cd /home/localadm/cvp/frontend
npm install next-intl
Créer src/messages/fr.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 :
import { defineRouting } from "next-intl/routing";
export const routing = defineRouting({
locales: ["fr", "en"],
defaultLocale: "fr",
});
Créer src/i18n/request.ts :
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 :
// 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
cd /home/localadm/cvp/frontend
npm install -D prettier eslint-config-prettier
- Step 2: Créer
.prettierrc
{
"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 :
{
"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
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
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
[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
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
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 :
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 :
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
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 :
# 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
# 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
# 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 :
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
# 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
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
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
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
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
settingsdepuisapp.config - Utiliser
settings.database_urlcomme URL de connexion - Importer le
Basede 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
# 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
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.