# 📄 Documentation Technique - Gestion Documents Légaux (CGU/Privacy)

**Version** : 1.0  
**Date** : 25 Novembre 2025  
**Auteur** : Équipe PtitsPas  
**Référence** : RGPD & Conformité juridique

---

## 📖 Table des matières

1. [Vue d'ensemble](#vue-densemble)
2. [Architecture](#architecture)
3. [Tables BDD](#tables-bdd)
4. [Service Documents Légaux](#service-documents-légaux)
5. [Workflow Upload & Activation](#workflow-upload--activation)
6. [Workflow Acceptation Utilisateur](#workflow-acceptation-utilisateur)
7. [APIs](#apis)
8. [Interface Admin](#interface-admin)
9. [Conformité RGPD](#conformité-rgpd)

---

## 🎯 Vue d'ensemble

### Problématique

Chaque collectivité déployant P'titsPas on-premise doit pouvoir :
1. ✅ **Personnaliser** les CGU et la Politique de confidentialité
2. ✅ **Versionner** les documents (traçabilité juridique)
3. ✅ **Tracer** qui a accepté quelle version (RGPD)
4. ✅ **Prouver** l'acceptation (IP, User-Agent, horodatage)
5. ✅ **Empêcher** le retour en arrière (sécurité juridique)

### Solution

- **Documents génériques v1** fournis par défaut (rédigés avec juriste)
- **Upload de nouvelles versions** par l'admin (PDF uniquement)
- **Versioning automatique** (incrémentation sans retour arrière)
- **Activation manuelle** (prévisualisation avant mise en prod)
- **Traçabilité complète** (hash SHA-256, IP, User-Agent)

---

## 🏗️ Architecture

### Flux de données

```
┌─────────────────────────────────────────────────────────┐
│                    Workflow Documents                   │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  1. UPLOAD (Admin)                                      │
│     Admin ──▶ API ──▶ File System ──▶ BDD             │
│                       /documents/legaux/                │
│                       cgu_v4_<timestamp>.pdf            │
│                                                         │
│  2. ACTIVATION (Admin)                                  │
│     Admin ──▶ API ──▶ BDD (actif=true)                │
│                                                         │
│  3. ACCEPTATION (Utilisateur)                           │
│     User ──▶ Frontend ──▶ API ──▶ BDD                 │
│             (inscription)   (trace IP/UA)              │
│                                                         │
└─────────────────────────────────────────────────────────┘
```

### Composants

1. **Table `documents_legaux`** : Stockage versions + métadonnées
2. **Table `acceptations_documents`** : Traçabilité acceptations
3. **Service `DocumentsLegauxService`** : Upload, versioning, activation
4. **API REST** : CRUD documents
5. **Interface Admin** : Upload + activation
6. **Interface Inscription** : Affichage + acceptation

---

## 📊 Tables BDD

### Table 1 : `documents_legaux`

```sql
-- Table pour gérer les versions des documents légaux
CREATE TABLE documents_legaux (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  type VARCHAR(50) NOT NULL,                    -- 'cgu' ou 'privacy'
  version INTEGER NOT NULL,                     -- Numéro de version (auto-incrémenté)
  fichier_nom VARCHAR(255) NOT NULL,            -- Nom original du fichier
  fichier_path VARCHAR(500) NOT NULL,           -- Chemin de stockage
  fichier_hash VARCHAR(64) NOT NULL,            -- Hash SHA-256 pour intégrité
  actif BOOLEAN DEFAULT false,                  -- Version actuellement active
  televerse_par UUID REFERENCES utilisateurs(id), -- Qui a uploadé
  televerse_le TIMESTAMPTZ DEFAULT now(),       -- Date d'upload
  active_le TIMESTAMPTZ,                        -- Date d'activation
  UNIQUE(type, version)                         -- Pas de doublon version
);

-- Index pour performance
CREATE INDEX idx_documents_legaux_type_actif ON documents_legaux(type, actif);
CREATE INDEX idx_documents_legaux_version ON documents_legaux(type, version DESC);
```

**Contraintes** :
- ✅ Un seul document `actif=true` par type à la fois
- ✅ Versioning auto-incrémenté (pas de gaps)
- ✅ Hash SHA-256 pour vérifier l'intégrité du fichier

---

### Table 2 : `acceptations_documents`

```sql
-- Table de traçabilité des acceptations (RGPD)
CREATE TABLE acceptations_documents (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  id_utilisateur UUID REFERENCES utilisateurs(id) ON DELETE CASCADE,
  id_document UUID REFERENCES documents_legaux(id),
  type_document VARCHAR(50) NOT NULL,           -- 'cgu' ou 'privacy'
  version_document INTEGER NOT NULL,            -- Version acceptée
  accepte_le TIMESTAMPTZ DEFAULT now(),         -- Date d'acceptation
  ip_address INET,                              -- IP de l'utilisateur (RGPD)
  user_agent TEXT                               -- Navigateur (preuve)
);

CREATE INDEX idx_acceptations_utilisateur ON acceptations_documents(id_utilisateur);
CREATE INDEX idx_acceptations_document ON acceptations_documents(id_document);
```

**Données capturées** :
- ✅ **Qui** : `id_utilisateur`
- ✅ **Quoi** : `type_document`, `version_document`
- ✅ **Quand** : `accepte_le`
- ✅ **Où** : `ip_address`
- ✅ **Comment** : `user_agent`

---

### Modification table `utilisateurs`

```sql
-- Ajouter colonnes pour référence rapide (optionnel)
ALTER TABLE utilisateurs 
  ADD COLUMN cgu_version_acceptee INTEGER,
  ADD COLUMN cgu_acceptee_le TIMESTAMPTZ,
  ADD COLUMN privacy_version_acceptee INTEGER,
  ADD COLUMN privacy_acceptee_le TIMESTAMPTZ;
```

**Note** : Ces colonnes sont **redondantes** avec `acceptations_documents`, mais permettent un accès rapide sans JOIN.

---

### Seed initial

```sql
-- Documents génériques v1 (fournis par défaut)
INSERT INTO documents_legaux (type, version, fichier_nom, fichier_path, fichier_hash, actif, televerse_le, active_le) VALUES
('cgu', 1, 'cgu_v1_default.pdf', '/documents/legaux/cgu_v1_default.pdf', 'a3f8b2c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2', true, now(), now()),
('privacy', 1, 'privacy_v1_default.pdf', '/documents/legaux/privacy_v1_default.pdf', 'b4f9c3d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4', true, now(), now());
```

**Fichiers à fournir** :
- `/documents/legaux/cgu_v1_default.pdf` (rédigé avec juriste)
- `/documents/legaux/privacy_v1_default.pdf` (conforme RGPD)

---

## 🔧 Service Documents Légaux

### Responsabilités

1. **Récupérer documents actifs** : `getDocumentsActifs()`
2. **Uploader nouvelle version** : `uploadNouvelleVersion(type, file, userId)`
3. **Activer une version** : `activerVersion(documentId)`
4. **Lister versions** : `listerVersions(type)`
5. **Télécharger document** : `telechargerDocument(documentId)`

### Implémentation (TypeScript)

```typescript
// backend/src/documents-legaux/documents-legaux.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { DocumentLegal } from './entities/document-legal.entity';
import * as crypto from 'crypto';
import * as fs from 'fs/promises';
import * as path from 'path';

@Injectable()
export class DocumentsLegauxService {
  private readonly UPLOAD_DIR = '/app/documents/legaux';

  constructor(
    @InjectRepository(DocumentLegal)
    private docRepo: Repository<DocumentLegal>,
  ) {}

  // Récupérer les documents actifs
  async getDocumentsActifs(): Promise<{ cgu: DocumentLegal; privacy: DocumentLegal }> {
    const cgu = await this.docRepo.findOne({
      where: { type: 'cgu', actif: true },
    });
    
    const privacy = await this.docRepo.findOne({
      where: { type: 'privacy', actif: true },
    });

    if (!cgu || !privacy) {
      throw new Error('Documents légaux manquants');
    }

    return { cgu, privacy };
  }

  // Uploader une nouvelle version
  async uploadNouvelleVersion(
    type: 'cgu' | 'privacy',
    file: Express.Multer.File,
    userId: string,
  ): Promise<DocumentLegal> {
    // 1. Calculer la prochaine version
    const lastDoc = await this.docRepo.findOne({
      where: { type },
      order: { version: 'DESC' },
    });
    const nouvelleVersion = (lastDoc?.version || 0) + 1;

    // 2. Calculer le hash du fichier
    const fileBuffer = file.buffer;
    const hash = crypto.createHash('sha256').update(fileBuffer).digest('hex');

    // 3. Générer le nom de fichier unique
    const timestamp = Date.now();
    const fileName = `${type}_v${nouvelleVersion}_${timestamp}.pdf`;
    const filePath = path.join(this.UPLOAD_DIR, fileName);

    // 4. Sauvegarder le fichier
    await fs.mkdir(this.UPLOAD_DIR, { recursive: true });
    await fs.writeFile(filePath, fileBuffer);

    // 5. Créer l'entrée en BDD
    const document = this.docRepo.create({
      type,
      version: nouvelleVersion,
      fichier_nom: file.originalname,
      fichier_path: filePath,
      fichier_hash: hash,
      actif: false, // Pas actif par défaut
      televerse_par: userId,
      televerse_le: new Date(),
    });

    return await this.docRepo.save(document);
  }

  // Activer une version
  async activerVersion(documentId: string): Promise<void> {
    const document = await this.docRepo.findOne({ where: { id: documentId } });
    
    if (!document) {
      throw new Error('Document non trouvé');
    }

    // Transaction : désactiver l'ancienne version, activer la nouvelle
    await this.docRepo.manager.transaction(async (manager) => {
      // Désactiver toutes les versions de ce type
      await manager.update(
        DocumentLegal,
        { type: document.type, actif: true },
        { actif: false },
      );

      // Activer la nouvelle version
      await manager.update(
        DocumentLegal,
        { id: documentId },
        { actif: true, active_le: new Date() },
      );
    });
  }

  // Lister toutes les versions (pour l'admin)
  async listerVersions(type: 'cgu' | 'privacy'): Promise<DocumentLegal[]> {
    return await this.docRepo.find({
      where: { type },
      order: { version: 'DESC' },
      relations: ['televerse_par'],
    });
  }

  // Télécharger un document (stream)
  async telechargerDocument(documentId: string): Promise<{ stream: Buffer; filename: string }> {
    const document = await this.docRepo.findOne({ where: { id: documentId } });
    
    if (!document) {
      throw new Error('Document non trouvé');
    }

    const fileBuffer = await fs.readFile(document.fichier_path);
    
    return {
      stream: fileBuffer,
      filename: document.fichier_nom,
    };
  }

  // Vérifier l'intégrité d'un document
  async verifierIntegrite(documentId: string): Promise<boolean> {
    const document = await this.docRepo.findOne({ where: { id: documentId } });
    
    if (!document) {
      throw new Error('Document non trouvé');
    }

    const fileBuffer = await fs.readFile(document.fichier_path);
    const hash = crypto.createHash('sha256').update(fileBuffer).digest('hex');
    
    return hash === document.fichier_hash;
  }
}
```

---

## 🔄 Workflow Upload & Activation

### Diagramme de séquence

```mermaid
sequenceDiagram
    participant A as Admin
    participant API as Backend API
    participant FS as File System
    participant DB as PostgreSQL
    
    A->>API: POST /api/v1/documents-legaux<br/>{type: 'cgu', file: PDF}
    
    API->>API: Validation fichier<br/>(PDF, max 10MB)
    API->>API: Calcul hash SHA-256
    
    API->>DB: SELECT MAX(version)<br/>WHERE type='cgu'
    DB-->>API: version = 3
    
    API->>API: Nouvelle version = 4
    
    API->>FS: Enregistrer fichier<br/>/documents/legaux/cgu_v4_<timestamp>.pdf
    FS-->>API: ✅ Fichier sauvegardé
    
    API->>DB: INSERT INTO documents_legaux<br/>(type, version=4, actif=false)
    DB-->>API: ✅ Document créé
    
    API-->>A: 201 Created<br/>{id, version: 4, actif: false}
    
    A->>A: Prévisualisation PDF
    A->>API: PATCH /api/v1/documents-legaux/{id}/activer
    
    API->>DB: BEGIN TRANSACTION
    API->>DB: UPDATE documents_legaux<br/>SET actif=false WHERE type='cgu'
    API->>DB: UPDATE documents_legaux<br/>SET actif=true, active_le=now()<br/>WHERE id={id}
    API->>DB: COMMIT
    
    API-->>A: ✅ CGU v4 activées
```

---

## 📥 Workflow Acceptation Utilisateur

### Diagramme de séquence

```mermaid
sequenceDiagram
    participant U as Utilisateur
    participant App as Frontend
    participant API as Backend
    participant DB as PostgreSQL
    
    U->>App: Inscription (étape CGU)
    
    App->>API: GET /api/v1/documents-legaux/actifs
    API->>DB: SELECT * FROM documents_legaux<br/>WHERE actif=true
    DB-->>API: {cgu: v4, privacy: v2}
    API-->>App: {cgu: {version: 4, url: '...'}, privacy: {...}}
    
    App->>App: Afficher liens PDF<br/>"CGU v4" et "Privacy v2"
    
    U->>U: Lit les documents
    U->>U: Coche "J'accepte"
    
    App->>API: POST /api/v1/auth/register<br/>{..., cgu_version: 4, privacy_version: 2, ip, user_agent}
    
    API->>DB: BEGIN TRANSACTION
    
    API->>DB: INSERT INTO utilisateurs<br/>(..., cgu_version_acceptee=4, privacy_version_acceptee=2)
    DB-->>API: id_utilisateur
    
    API->>DB: INSERT INTO acceptations_documents<br/>(id_utilisateur, type='cgu', version=4, ip, user_agent)
    API->>DB: INSERT INTO acceptations_documents<br/>(id_utilisateur, type='privacy', version=2, ip, user_agent)
    
    API->>DB: COMMIT
    
    API-->>App: ✅ Inscription réussie
```

---

## 🔌 APIs

### API 1 : Récupérer documents actifs (Public)

```http
GET /api/v1/documents-legaux/actifs
```

**Réponse 200** :
```json
{
  "cgu": {
    "id": "uuid-cgu-v4",
    "type": "cgu",
    "version": 4,
    "url": "/api/v1/documents-legaux/uuid-cgu-v4/download",
    "active_le": "2025-11-20T14:30:00Z"
  },
  "privacy": {
    "id": "uuid-privacy-v2",
    "type": "privacy",
    "version": 2,
    "url": "/api/v1/documents-legaux/uuid-privacy-v2/download",
    "active_le": "2025-10-15T09:15:00Z"
  }
}
```

---

### API 2 : Lister versions (Admin)

```http
GET /api/v1/documents-legaux/:type/versions
Authorization: Bearer <super_admin_token>
```

**Paramètres** :
- `type` : `cgu` | `privacy`

**Réponse 200** :
```json
[
  {
    "id": "uuid-cgu-v4",
    "version": 4,
    "fichier_nom": "CGU_Mairie_Bezons_2025.pdf",
    "actif": true,
    "televerse_par": {
      "id": "uuid-admin",
      "prenom": "Lucas",
      "nom": "MOREAU"
    },
    "televerse_le": "2025-11-20T14:00:00Z",
    "active_le": "2025-11-20T14:30:00Z"
  },
  {
    "id": "uuid-cgu-v3",
    "version": 3,
    "fichier_nom": "CGU_v3.pdf",
    "actif": false,
    "televerse_par": {
      "id": "uuid-admin",
      "prenom": "Admin",
      "nom": "Système"
    },
    "televerse_le": "2025-10-15T09:00:00Z",
    "active_le": "2025-10-15T09:15:00Z"
  }
]
```

---

### API 3 : Upload nouvelle version (Admin)

```http
POST /api/v1/documents-legaux
Authorization: Bearer <super_admin_token>
Content-Type: multipart/form-data
```

**Body** :
```
type: cgu
file: <fichier PDF>
```

**Réponse 201** :
```json
{
  "id": "uuid-nouveau-doc",
  "type": "cgu",
  "version": 5,
  "fichier_nom": "CGU_Mairie_Bezons_2025_v2.pdf",
  "actif": false,
  "televerse_le": "2025-11-25T10:00:00Z"
}
```

**Erreurs** :
- `400 Bad Request` : Fichier non PDF ou trop volumineux (>10MB)
- `401 Unauthorized` : Token manquant ou invalide
- `403 Forbidden` : Rôle insuffisant (pas super_admin)

---

### API 4 : Activer une version (Admin)

```http
PATCH /api/v1/documents-legaux/:id/activer
Authorization: Bearer <super_admin_token>
```

**Réponse 200** :
```json
{
  "message": "Document activé avec succès",
  "documentId": "uuid-nouveau-doc",
  "type": "cgu",
  "version": 5
}
```

---

### API 5 : Télécharger document (Public)

```http
GET /api/v1/documents-legaux/:id/download
```

**Réponse 200** :
```
Content-Type: application/pdf
Content-Disposition: attachment; filename="CGU_v5.pdf"

<binary PDF data>
```

---

### API 6 : Historique acceptations utilisateur (Admin)

```http
GET /api/v1/users/:userId/acceptations
Authorization: Bearer <super_admin_token>
```

**Réponse 200** :
```json
[
  {
    "type_document": "cgu",
    "version_document": 4,
    "accepte_le": "2025-11-20T15:30:00Z",
    "ip_address": "192.168.1.100",
    "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
  },
  {
    "type_document": "privacy",
    "version_document": 2,
    "accepte_le": "2025-11-20T15:30:00Z",
    "ip_address": "192.168.1.100",
    "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
  }
]
```

---

## 💻 Interface Admin

### Écran Gestion Documents Légaux

```
┌─────────────────────────────────────────────────────────┐
│  📄 Gestion des Documents Légaux                        │
├─────────────────────────────────────────────────────────┤
│  [ CGU ]  [ Politique de confidentialité ]              │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  📋 Conditions Générales d'Utilisation (CGU)           │
│                                                         │
│  Version active : v4                                    │
│  Activée le : 20/11/2025 14:30                         │
│  Téléversée par : Lucas MOREAU                         │
│                                                         │
│  [ 📥 Télécharger ]  [ 👁️ Prévisualiser ]              │
│                                                         │
│  ─────────────────────────────────────────────────     │
│                                                         │
│  📤 Uploader une nouvelle version                       │
│                                                         │
│  ⚠️ Attention : L'upload d'une nouvelle version        │
│     créera la version v5. Vous pourrez la prévisualiser│
│     avant de l'activer.                                │
│                                                         │
│  [ Choisir un fichier PDF ]  (max 10MB)               │
│                                                         │
│  [ 📤 Uploader ]                                        │
│                                                         │
│  ─────────────────────────────────────────────────     │
│                                                         │
│  📜 Historique des versions                            │
│                                                         │
│  ┌───────────────────────────────────────────────┐    │
│  │ ✅ v4 (Active)                                │    │
│  │ Activée le : 20/11/2025 14:30                │    │
│  │ Par : Lucas MOREAU                           │    │
│  │ Hash : a3f8b2c4...e0f1a2 ✓                   │    │
│  │ [ 📥 Télécharger ]  [ 👁️ Voir ]              │    │
│  └───────────────────────────────────────────────┘    │
│                                                         │
│  ┌───────────────────────────────────────────────┐    │
│  │ v3 (Inactive)                                 │    │
│  │ Activée le : 15/10/2025 09:15                │    │
│  │ Par : Admin Système                          │    │
│  │ Hash : b4f9c3d6...f2a3b4 ✓                   │    │
│  │ [ 📥 Télécharger ]  [ 👁️ Voir ]  [🔄 Réactiver]│    │
│  └───────────────────────────────────────────────┘    │
│                                                         │
└─────────────────────────────────────────────────────────┘
```

---

## 🔒 Conformité RGPD

### Données capturées

| Donnée | Justification RGPD | Durée conservation |
|--------|-------------------|-------------------|
| `id_utilisateur` | Traçabilité acceptation | Durée du compte |
| `version_document` | Preuve version acceptée | Durée du compte |
| `accepte_le` | Horodatage légal | Durée du compte |
| `ip_address` | Preuve origine acceptation | 1 an (recommandé) |
| `user_agent` | Preuve navigateur/appareil | 1 an (recommandé) |

### Droits utilisateur

#### Droit d'accès (Article 15)
L'utilisateur peut demander :
- Quelles versions il a acceptées
- Quand il les a acceptées
- Depuis quelle IP

**API** : `GET /api/v1/users/me/acceptations`

#### Droit à l'oubli (Article 17)
Lors de la suppression du compte :
- Suppression des données personnelles
- Conservation des acceptations anonymisées (obligation légale)

**Implémentation** :
```sql
-- Anonymisation (pas suppression totale)
UPDATE acceptations_documents
SET ip_address = NULL,
    user_agent = NULL
WHERE id_utilisateur = '<uuid>';

-- Puis suppression utilisateur
DELETE FROM utilisateurs WHERE id = '<uuid>';
```

---

## 📚 Références

### Documentation interne
- [01_CAHIER-DES-CHARGES.md](./01_CAHIER-DES-CHARGES.md)
- [10_DATABASE.md](./10_DATABASE.md)
- [11_API.md](./11_API.md)
- [21_CONFIGURATION-SYSTEME.md](./21_CONFIGURATION-SYSTEME.md)

### Documentation externe
- [RGPD - Article 7 (Consentement)](https://www.cnil.fr/fr/reglement-europeen-protection-donnees/chapitre2#Article7)
- [RGPD - Article 15 (Droit d'accès)](https://www.cnil.fr/fr/reglement-europeen-protection-donnees/chapitre3#Article15)
- [RGPD - Article 17 (Droit à l'oubli)](https://www.cnil.fr/fr/reglement-europeen-protection-donnees/chapitre3#Article17)

---

**Dernière mise à jour** : 25 Novembre 2025  
**Version** : 1.0  
**Statut** : ✅ Document validé



