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

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/ (via create-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 : xl par 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 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
# 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.