246 lines
11 KiB
Markdown
246 lines
11 KiB
Markdown
# CV-Maker — Documentation technique
|
||
|
||
Application web de création de CV en temps réel : saisie **Markdown** dans un panneau latéral, **preview A4** instantanée, sauvegarde locale automatique et export **PDF** — le tout sans backend, 100 % côté navigateur.
|
||
|
||
## Sommaire
|
||
|
||
1. [Prérequis](#prérequis)
|
||
2. [Installation et lancement](#installation-et-lancement)
|
||
3. [Scripts](#scripts)
|
||
4. [Stack technique](#stack-technique)
|
||
5. [Structure du projet](#structure-du-projet)
|
||
6. [Modèle de données](#modèle-de-données)
|
||
7. [Fonctionnement clé](#fonctionnement-clé)
|
||
- [Rendu Markdown sécurisé](#rendu-markdown-sécurisé)
|
||
- [Persistance locale](#persistance-locale)
|
||
- [Gestion des expériences](#gestion-des-expériences)
|
||
- [Impression PDF](#impression-pdf)
|
||
- [PWA](#pwa)
|
||
8. [Conception détaillée](#conception-détaillée)
|
||
9. [Limitations connues](#limitations-connues)
|
||
|
||
---
|
||
|
||
## Prérequis
|
||
|
||
| Outil | Version minimale |
|
||
| --- | --- |
|
||
| Node.js | 20.9+ |
|
||
| pnpm | 12.x (`corepack enable` puis `pnpm -v` — figé via `packageManager`) |
|
||
|
||
## Installation et lancement
|
||
|
||
```bash
|
||
# 1. Installer les dépendances
|
||
pnpm install
|
||
|
||
# 2. Lancer en développement
|
||
pnpm dev # → http://localhost:3000
|
||
|
||
# 3. Construire + servir en production
|
||
pnpm build
|
||
pnpm start
|
||
```
|
||
|
||
## Scripts
|
||
|
||
| Script | Commande | Description |
|
||
| --- | --- | --- |
|
||
| `dev` | `pnpm dev` | Serveur de développement (Turbopack) |
|
||
| `dev:sw` | `concurrently …` | Dev + re-build du service worker en continu (`--watch`) |
|
||
| `build` | `next build && serwist build` | Build de production + génération du service worker |
|
||
| `start` | `pnpm start` | Lancement du build de production |
|
||
| `lint` | `pnpm lint` | Vérification ESLint (Next.js) |
|
||
| `typecheck` | `tsc --noEmit` | Validation TypeScript strict |
|
||
|
||
## Stack technique
|
||
|
||
| Brique | Choix | Rôle |
|
||
| --- | --- | --- |
|
||
| Framework | Next.js (App Router) | Routage, composants serveur/client, métadonnées |
|
||
| Langage | TypeScript (strict) | Typage du modèle de données |
|
||
| Styles | Tailwind CSS | Design tokens, responsive, print |
|
||
| Markdown | `marked` | Conversion Markdown → HTML |
|
||
| Sécurité | `dompurify` | Assainissement du HTML avant injection (anti-XSS) |
|
||
| Persistance | `localStorage` | Données locales, aucune transmission |
|
||
| PWA | `@serwist/next` + `@serwist/cli` | Service worker (mode configurator), manifest, installabilité |
|
||
| Icônes | `lucide-react` | Iconographie |
|
||
| Conteneurisation | Docker (multi-stage) | Image de production, distribution Docker Hub |
|
||
|
||
## Structure du projet
|
||
|
||
```
|
||
cv-maker/
|
||
├── app/
|
||
│ ├── layout.tsx # Layout racine (lang, meta, manifest)
|
||
│ ├── page.tsx # Éditeur principal
|
||
│ ├── globals.css # Tokens Tailwind + @media print
|
||
│ ├── documentation/
|
||
│ │ └── page.tsx # Page d'aide (statique)
|
||
│ ├── mentions-legales/
|
||
│ │ └── page.tsx # Page Mentions légales (statique)
|
||
│ └── sw.ts # Source du service worker (Serwist)
|
||
├── components/
|
||
│ ├── ui/ # Primitives réutilisables
|
||
│ │ ├── Button.tsx
|
||
│ │ ├── IconButton.tsx
|
||
│ │ ├── Textarea.tsx
|
||
│ │ ├── Tabs.tsx
|
||
│ │ └── Footer.tsx # Pied de page (lien Mentions légales)
|
||
│ └── cv/ # Composants métier du CV
|
||
│ ├── CvWorkspace.tsx # Assemblage 2 colonnes + toolbar
|
||
│ ├── CvToolbar.tsx # Barre d'app (PDF, JSON, liens)
|
||
│ ├── CvInputPanel.tsx # Panneau gauche (tabs + expériences)
|
||
│ ├── CvTabs.tsx # Onglets 1..6
|
||
│ ├── MarkdownTextarea.tsx # Textarea contrôlé
|
||
│ ├── ExperienceManager.tsx # Formulaire + liste expériences
|
||
│ ├── ExperienceItem.tsx # Ligne « n. titre [Modifier] [×] »
|
||
│ ├── CvPreview.tsx # Feuille A4
|
||
│ ├── CvHeader.tsx # Accroche (zone 1)
|
||
│ ├── CvLeftColumn.tsx # Sections 2→6 (1/3)
|
||
│ ├── CvSection.tsx # Section générique
|
||
│ ├── CvRightColumn.tsx # Expériences pro (2/3)
|
||
│ └── CvExperiencePreview.tsx
|
||
├── lib/
|
||
│ ├── types/cv.ts # Contracts du domaine
|
||
│ ├── markdown.ts # markdownToHtml (marked + DOMPurify)
|
||
│ ├── storage.ts # Adapter localStorage
|
||
│ └── hooks/
|
||
│ ├── useLocalStorage.ts # Persistance générique
|
||
│ └── useCVData.ts # Façade métier d'édition
|
||
├── public/
|
||
│ ├── manifest.webmanifest # Manifest PWA
|
||
│ └── icons/ # icônes 192 / 512
|
||
├── Dockerfile # Image multi-stage (deps/builder/runner)
|
||
├── docker-compose.yml # Service cv-maker, healthcheck
|
||
├── .dockerignore # Exclusions de contexte build
|
||
├── serwist.config.mjs # Config service worker (mode configurator)
|
||
├── next.config.ts # output: "standalone"
|
||
├── tsconfig.json # strict
|
||
├── GOAL.md # Cahier des charges source
|
||
└── README.md # Ce document
|
||
```
|
||
|
||
## Modèle de données
|
||
|
||
Contenu stocké dans **une seule clé localStorage** : `cv-maker:v1` (JSON).
|
||
|
||
```ts
|
||
interface CVCareerData {
|
||
version: 1;
|
||
accroche: string; // onglet 1 — message d'accroche
|
||
etatCivil: string; // onglet 2 — état civil + contacts + site + GitHub
|
||
formations: string; // onglet 3 — formations
|
||
competences: string; // onglet 4 — compétences techniques
|
||
softSkills: string; // onglet 5 — soft skills
|
||
centresInteret: string; // onglet 6 — centres d'intérêt
|
||
experiences: Experience[];
|
||
}
|
||
|
||
interface Experience {
|
||
id: string; // crypto.randomUUID() — identifiant stable
|
||
titre: string; // titre affiché (liste + section CV)
|
||
contenu: string; // corps en Markdown
|
||
createdAt: number;
|
||
updatedAt: number;
|
||
}
|
||
```
|
||
|
||
- Les 6 zones sont du **Markdown brut** (le HTML rendu n'est jamais stocké).
|
||
- Les expériences sont affichées **dans l'ordre de saisie** : la 1ʳᵉ ajoutée en haut de la section « Expériences professionnelles », la suivante en dessous, etc. (la 1ʳᵉ porte le numéro 1).
|
||
- Sauvegarde invalide/corrompue → état par défaut, sans crash.
|
||
|
||
## Fonctionnement clé
|
||
|
||
### Rendu Markdown sécurisé
|
||
|
||
`lib/markdown.ts` — toute injection HTML passe par ici :
|
||
|
||
```ts
|
||
const rawHtml = marked.parse(source, { async: false }) as string;
|
||
return DOMPurify.sanitize(rawHtml);
|
||
```
|
||
|
||
`marked` génère le HTML ; **`DOMPurify` neutralise** tout script/événement URL dangereuse avant le `dangerouslySetInnerHTML`. Aucun HTML brut saisi n'est injecté directement.
|
||
|
||
### Persistance locale
|
||
|
||
`useLocalStorage` synchronise l'état React avec localStorage **à chaque changement** (via `useEffect`) ; l'enregistrement est silencieux (aucun indicateur affiché). `useCVData` expose la façade d'édition (`setText`, `addExperience`, `updateExperience`, `removeExperience`, `resetCV`, `importCV`), et la barre d'application permet l'**export/import JSON** (`cv-maker.json`) pour partager un CV entre appareils.
|
||
|
||
### Gestion des expériences
|
||
|
||
- Bouton **+** : nouvelle expérience (formulaire titre + contenu).
|
||
- Liste numérotée avec **Modifier** / **×** (suppression confirmée).
|
||
- Identifiants UUID : jamais d'index comme identifiant (robustesse lors des suppressions).
|
||
|
||
### Impression PDF
|
||
|
||
- Bouton « Imprimer / PDF » → `window.print()`.
|
||
- CSS `@media print` dans `app/globals.css` : `@page { size: A4; margin: 0 }`, panneau de saisie et toolbar masqués, seule la feuille A4 est imprimée.
|
||
|
||
### PWA
|
||
|
||
- **Mode « configurator »** (`@serwist/next` + `@serwist/cli`, compatible Turbopack) : `serwist.config.mjs` décrit le service worker, compilé par `pnpm build` après Next.js (`next build && serwist build`).
|
||
- `public/sw.js` pré-cache les routes `/`, `/documentation`, `/mentions-legales` et tous les assets (787 kB, 20 URL).
|
||
- En développement le service worker est **désactivé** (`SerwistProvider disable`) ; utiliser `pnpm dev:sw` pour le reconstruire à chaud.
|
||
- `public/manifest.webmanifest` + icônes 192/512 → installable, fonctionne hors-ligne (les données du CV restent dans localStorage).
|
||
|
||
### Dockerisation
|
||
|
||
L'image est construite en **3 étapes** (`Dockerfile` multi-stage) :
|
||
|
||
| Étape | Image | Contenu |
|
||
| --- | --- | --- |
|
||
| `deps` | `node:20-alpine` | Installation déterministe des dépendances (`pnpm install --frozen-lockfile`, incl. `pnpm-workspace.yaml`) |
|
||
| `builder` | `node:20-alpine` | Compilation (`pnpm build`, sortie `standalone` + `sw.js`) |
|
||
| `runner` | `node:20-alpine` | Exécution uniquement (`node server.js`, image ≈ 60 Mo) |
|
||
|
||
**Build et lancement :**
|
||
|
||
```bash
|
||
docker build -t cv-maker:latest .
|
||
docker run --rm -p 3001:3000 cv-maker:latest # → http://localhost:3001
|
||
```
|
||
|
||
> Si le port 3000 est déjà occupé en local, mapper un autre port hôte (`-p 3001:3000`).
|
||
|
||
**Avec Docker Compose :**
|
||
|
||
```bash
|
||
docker compose up -d
|
||
docker compose down
|
||
```
|
||
|
||
**Publication sur Docker Hub :**
|
||
|
||
```bash
|
||
docker login
|
||
docker tag cv-maker:latest <mon-user-dockerhub>/cv-maker:latest
|
||
docker push <mon-user-dockerhub>/cv-maker:latest
|
||
```
|
||
|
||
Voir la [documentation détaillée Docker](./docs/conception/08-deploiement-docker.md) pour le contenu complet des fichiers (`Dockerfile`, `docker-compose.yml`, `.dockerignore`) et la recette de validation.
|
||
|
||
## Conception détaillée
|
||
|
||
Le dossier de conception complet (analyse fonctionnelle, architecture, modèle de données, UI, plan de réalisation pas-à-pas, sécurité/RGPD, plan de tests) se trouve dans :
|
||
|
||
```
|
||
docs/conception/
|
||
├── README.md → sommaire et ordre de lecture
|
||
├── 01-analyse-fonctionnelle.md
|
||
├── 02-architecture-technique.md
|
||
├── 03-modele-donnees.md
|
||
├── 04-specifications-ui.md
|
||
├── 05-etapes-realisation.md
|
||
├── 06-securite-rgpd.md
|
||
├── 07-plan-tests.md
|
||
└── 08-deploiement-docker.md
|
||
```
|
||
|
||
## Limitations connues
|
||
|
||
- **Desktop first** : tablette/mobile = niveau « utilisable », non optimisé.
|
||
- **One page A4 à l'impression** : feuille de preview à hauteur flexible ; un contenu très long peut générer plusieurs pages PDF.
|
||
- **Pas de tests automatisés** (v1) : recette manuelle documentée dans `docs/conception/07-plan-tests.md` ; tests Vitest prévus en v2.
|
||
- **Pas de drag & drop** de réorganisation des expériences (évolution prévue). |