merge(master): intégration develop — ticket #50, documents légaux et validation CGU
Squash merge de la branche develop dans master : un seul commit sur master pour regrouper la livraison liée au ticket #50 (affichage dynamique des CGU et de la politique de confidentialité à l'inscription) ainsi que la documentation et les ajustements associés. Frontend (P'titsPas) : modale de validation bloquante avec onglets CGU / confidentialité, chargement des PDF depuis l'API des documents légaux actifs, affichage via pdfx (PdfViewPinch et barre de progression latérale sur le web, repli PdfView sous Windows). Branchement dans le flux d'inscription (écran de présentation), enrichissement du UserService et correction des chemins média pour les URL absolues. Documentation: jeux de fichiers juridiques (markdown et PDF de référence), réorganisation (dossier docs/juridique, archives, renommage du briefing), mises à jour index, liste de tickets et décisions projet. Scripts et métadonnées mineures (Gitea, pubspec, index web). Made-with: Cursor
This commit is contained in:
parent
241ce36b85
commit
d91caef0c5
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Commentaire de clôture + fermeture issue Gitea #120.
|
||||
* Usage: node backend/scripts/close-gitea-issue-120.js
|
||||
* Token : .gitea-token (racine), GITEA_TOKEN, ou docs/BRIEFING-FRONTEND.md
|
||||
* Token : .gitea-token (racine), GITEA_TOKEN, ou docs/27_BRIEFING-FRONTEND.md
|
||||
*/
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
@ -45,7 +45,10 @@ if (!token) {
|
||||
}
|
||||
if (!token) {
|
||||
try {
|
||||
const briefing = fs.readFileSync(path.join(repoRoot, 'docs/BRIEFING-FRONTEND.md'), 'utf8');
|
||||
const briefing = fs.readFileSync(
|
||||
path.join(repoRoot, 'docs/27_BRIEFING-FRONTEND.md'),
|
||||
'utf8',
|
||||
);
|
||||
const m = briefing.match(/Token:\s*(giteabu_[a-f0-9]+)/);
|
||||
if (m) token = m[1].trim();
|
||||
} catch (_) {}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Crée l'issue Gitea "[Frontend] Inscription Parent – Branchement soumission formulaire à l'API"
|
||||
* Usage: node backend/scripts/create-gitea-issue-parent-api.js
|
||||
* Token : .gitea-token (racine du dépôt), sinon GITEA_TOKEN, sinon docs/BRIEFING-FRONTEND.md (voir PROCEDURE-API-GITEA.md)
|
||||
* Token : .gitea-token (racine du dépôt), sinon GITEA_TOKEN, sinon
|
||||
* docs/27_BRIEFING-FRONTEND.md (voir docs/26_GITEA-API.md)
|
||||
*/
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
@ -19,13 +20,18 @@ if (!token) {
|
||||
}
|
||||
if (!token) {
|
||||
try {
|
||||
const briefing = fs.readFileSync(path.join(repoRoot, 'docs/BRIEFING-FRONTEND.md'), 'utf8');
|
||||
const briefing = fs.readFileSync(
|
||||
path.join(repoRoot, 'docs/27_BRIEFING-FRONTEND.md'),
|
||||
'utf8',
|
||||
);
|
||||
const m = briefing.match(/Token:\s*(giteabu_[a-f0-9]+)/);
|
||||
if (m) token = m[1].trim();
|
||||
} catch (_) {}
|
||||
}
|
||||
if (!token) {
|
||||
console.error('Token non trouvé : créer .gitea-token à la racine ou export GITEA_TOKEN (voir docs/PROCEDURE-API-GITEA.md)');
|
||||
console.error(
|
||||
'Token non trouvé : créer .gitea-token à la racine ou export GITEA_TOKEN (voir docs/26_GITEA-API.md)',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Liste toutes les issues Gitea (ouvertes + fermées) pour jmartin/petitspas.
|
||||
* Token : .gitea-token (racine), GITEA_TOKEN, ou docs/BRIEFING-FRONTEND.md
|
||||
* Token : .gitea-token (racine), GITEA_TOKEN, ou docs/27_BRIEFING-FRONTEND.md
|
||||
*/
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
@ -16,7 +16,10 @@ if (!token) {
|
||||
}
|
||||
if (!token) {
|
||||
try {
|
||||
const briefing = fs.readFileSync(path.join(repoRoot, 'docs/BRIEFING-FRONTEND.md'), 'utf8');
|
||||
const briefing = fs.readFileSync(
|
||||
path.join(repoRoot, 'docs/27_BRIEFING-FRONTEND.md'),
|
||||
'utf8',
|
||||
);
|
||||
const m = briefing.match(/Token:\s*(giteabu_[a-f0-9]+)/);
|
||||
if (m) token = m[1].trim();
|
||||
} catch (_) {}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Met à jour l'issue Gitea #119 : endpoint unifié GET /dossiers/:numeroDossier (option A)
|
||||
* Usage: node backend/scripts/update-gitea-issue-119-dossiers.js
|
||||
* Token : .gitea-token (racine), GITEA_TOKEN, ou docs/BRIEFING-FRONTEND.md
|
||||
* Token : .gitea-token (racine), GITEA_TOKEN, ou docs/27_BRIEFING-FRONTEND.md
|
||||
*/
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
@ -17,7 +17,10 @@ if (!token) {
|
||||
}
|
||||
if (!token) {
|
||||
try {
|
||||
const briefing = fs.readFileSync(path.join(repoRoot, 'docs/BRIEFING-FRONTEND.md'), 'utf8');
|
||||
const briefing = fs.readFileSync(
|
||||
path.join(repoRoot, 'docs/27_BRIEFING-FRONTEND.md'),
|
||||
'utf8',
|
||||
);
|
||||
const m = briefing.match(/Token:\s*(giteabu_[a-f0-9]+)/);
|
||||
if (m) token = m[1].trim();
|
||||
} catch (_) {}
|
||||
|
||||
@ -19,15 +19,39 @@ Ce fichier sert d'index pour naviguer dans toute la documentation du projet.
|
||||
### Développement
|
||||
- [**10 - Database Schema**](./10_DATABASE.md) - Schéma de la base de données et modèles
|
||||
- [**11 - API Documentation**](./11_API.md) - Documentation complète des endpoints REST
|
||||
- [**14 - Note backend config setup**](./14_NOTE-BACKEND-CONFIG-SETUP.md) - Setup configuration
|
||||
- [**92 - Note backend gestionnaires**](./92_NOTE-BACKEND-GESTIONNAIRES.md) - Gestionnaires
|
||||
- [**99 - Règles de codage**](./99_REGLES-CODAGE.md) - Conventions de code
|
||||
|
||||
### Workflows Fonctionnels
|
||||
- [**20 - Workflow Création de Compte**](./20_WORKFLOW-CREATION-COMPTE.md) - Workflow complet de création et validation des comptes utilisateurs
|
||||
- [**21 - Configuration Système**](./21_CONFIGURATION-SYSTEME.md) - Configuration on-premise dynamique
|
||||
- [**22 - Documents Légaux**](./22_DOCUMENTS-LEGAUX.md) - Gestion CGU/Privacy avec versioning
|
||||
- [**22 - Documents Légaux**](./juridique/22_DOCUMENTS-LEGAUX.md) - Gestion CGU/Privacy avec versioning
|
||||
|
||||
### Juridique (sources & technique)
|
||||
- [**Dossier juridique**](./juridique/README.md) - Index : CGU/CGC en Markdown,
|
||||
export PDF, lien vers la doc technique n°22
|
||||
|
||||
### Projet & suivi (Gitea / tickets)
|
||||
- [**23 - Liste des Tickets**](./23_LISTE-TICKETS.md) - 61 tickets Phase 1 détaillés
|
||||
- [**24 - Décisions Projet**](./24_DECISIONS-PROJET.md) - Décisions architecturales et fonctionnelles
|
||||
- [**25 - Backlog Phase 2**](./25_PHASE-2-BACKLOG.md) - Fonctionnalités techniques reportées
|
||||
- [**26 - API Gitea**](./26_GITEA-API.md) - Procédure d'utilisation de l'API Gitea (issues, PR, branches, labels)
|
||||
- [**27 - Briefing frontend**](./27_BRIEFING-FRONTEND.md) - Accès Git, priorités, scripts Gitea (token)
|
||||
|
||||
### Archive & convention de nommage
|
||||
- [**Dossier archive**](./archive/README.md) - Fichiers **sans** `NN_` déplacés
|
||||
(temporaires, obsolètes) ; règles de rangement et suppression
|
||||
- Pointeur : [PROCEDURE-API-GITEA.md](./PROCEDURE-API-GITEA.md) → voir **26**
|
||||
|
||||
### Exceptions de nommage (racine `docs/`)
|
||||
Fichiers **sans préfixe numérique** encore à la racine par **héritage** ou
|
||||
références outils (`.cursorrules`, etc.) — **à renommer** en `NN_` quand
|
||||
possible :
|
||||
- `CHARTE_GRAPHIQUE.md`
|
||||
- `EVOLUTIONS_CDC.md`
|
||||
- `SuperNounou_Cahier_Des_Charges_Complet_V1.1.md`
|
||||
- `SuperNounou_SSS-001.md`
|
||||
|
||||
### Administration (À créer)
|
||||
- [**30 - Guide d'administration**](./30_ADMIN.md) - Gestion des utilisateurs, accès PgAdmin, logs
|
||||
|
||||
@ -1,698 +1,8 @@
|
||||
# 📄 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é
|
||||
# Fichier déplacé
|
||||
|
||||
La documentation **Documents légaux** a été déplacée vers :
|
||||
|
||||
**[juridique/22_DOCUMENTS-LEGAUX.md](./juridique/22_DOCUMENTS-LEGAUX.md)**
|
||||
|
||||
Voir aussi le dossier **[juridique/](./juridique/)** pour les sources **CGU**
|
||||
et **CGC** en Markdown.
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
| 44 | [Frontend] Dashboard Gestionnaire - Structure | ✅ Terminé |
|
||||
| 45 | [Frontend] Dashboard Gestionnaire - Liste Parents | ✅ Fermé (obsolète, couvert #103-#111) |
|
||||
| 46 | [Frontend] Dashboard Gestionnaire - Liste AM | ✅ Fermé (obsolète, couvert #103-#111) |
|
||||
| 47 | [Frontend] Écran Changement MDP Obligatoire | Ouvert |
|
||||
| 47 | [Frontend] Écran Changement MDP Obligatoire | ✅ Fermé |
|
||||
| 48 | [Frontend] Gestion Erreurs & Messages | Ouvert |
|
||||
| 49 | [Frontend] Écran Gestion Documents Légaux (Admin) | Ouvert |
|
||||
| 50 | [Frontend] Affichage dynamique CGU lors inscription | Ouvert |
|
||||
@ -243,7 +243,7 @@ Créer les tables pour gérer les versions des documents légaux (CGU/Privacy) e
|
||||
- [ ] Créer migration Prisma
|
||||
- [ ] Tester migration
|
||||
|
||||
**Référence** : [22_DOCUMENTS-LEGAUX.md](./22_DOCUMENTS-LEGAUX.md)
|
||||
**Référence** : [22_DOCUMENTS-LEGAUX.md](./juridique/22_DOCUMENTS-LEGAUX.md)
|
||||
|
||||
---
|
||||
|
||||
@ -679,7 +679,7 @@ Créer le service de gestion des documents légaux (CGU/Privacy) avec versioning
|
||||
- [ ] Méthode `listerVersions()`
|
||||
- [ ] Tests unitaires
|
||||
|
||||
**Référence** : [22_DOCUMENTS-LEGAUX.md](./22_DOCUMENTS-LEGAUX.md#service-documents-légaux)
|
||||
**Référence** : [22_DOCUMENTS-LEGAUX.md](./juridique/22_DOCUMENTS-LEGAUX.md#service-documents-légaux)
|
||||
|
||||
---
|
||||
|
||||
@ -699,7 +699,7 @@ Créer les endpoints REST pour gérer les documents légaux.
|
||||
- [ ] Guards + validation
|
||||
- [ ] Tests unitaires
|
||||
|
||||
**Référence** : [22_DOCUMENTS-LEGAUX.md](./22_DOCUMENTS-LEGAUX.md#apis)
|
||||
**Référence** : [22_DOCUMENTS-LEGAUX.md](./juridique/22_DOCUMENTS-LEGAUX.md#apis)
|
||||
|
||||
---
|
||||
|
||||
@ -716,7 +716,7 @@ Enregistrer les acceptations de documents légaux lors de l'inscription (traçab
|
||||
- [ ] API `GET /api/v1/users/:id/acceptations` (admin)
|
||||
- [ ] Tests unitaires
|
||||
|
||||
**Référence** : [22_DOCUMENTS-LEGAUX.md](./22_DOCUMENTS-LEGAUX.md#workflow-acceptation-utilisateur)
|
||||
**Référence** : [22_DOCUMENTS-LEGAUX.md](./juridique/22_DOCUMENTS-LEGAUX.md#workflow-acceptation-utilisateur)
|
||||
|
||||
---
|
||||
|
||||
@ -1005,7 +1005,7 @@ Créer l'écran de gestion des documents légaux (CGU/Privacy) pour l'admin.
|
||||
- [ ] Activation version
|
||||
- [ ] Historique versions
|
||||
|
||||
**Référence** : [22_DOCUMENTS-LEGAUX.md](./22_DOCUMENTS-LEGAUX.md#interface-admin)
|
||||
**Référence** : [22_DOCUMENTS-LEGAUX.md](./juridique/22_DOCUMENTS-LEGAUX.md#interface-admin)
|
||||
|
||||
---
|
||||
|
||||
@ -1165,7 +1165,7 @@ Branchement du formulaire d'inscription parent (étape 5, récapitulatif) à l'e
|
||||
|
||||
**Référence** : [20_WORKFLOW-CREATION-COMPTE.md](./20_WORKFLOW-CREATION-COMPTE.md#étape-3--inscription-dun-parent), backend #18 (refonte API inscription parent).
|
||||
|
||||
**Création** : issue Gitea #101 créée. Pour recréer ou script : `node backend/scripts/create-gitea-issue-parent-api.js` (token dans `.gitea-token` ou voir [PROCEDURE-API-GITEA.md](./PROCEDURE-API-GITEA.md)).
|
||||
**Création** : issue Gitea #101 créée. Pour recréer ou script : `node backend/scripts/create-gitea-issue-parent-api.js` (token dans `.gitea-token` ou voir [26_GITEA-API.md](./26_GITEA-API.md)).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -95,7 +95,7 @@ ptitspas-app/
|
||||
- Activation manuelle après prévisualisation
|
||||
- Documents génériques v1 fournis par défaut
|
||||
|
||||
**Référence** : [22_DOCUMENTS-LEGAUX.md](./22_DOCUMENTS-LEGAUX.md)
|
||||
**Référence** : [22_DOCUMENTS-LEGAUX.md](./juridique/22_DOCUMENTS-LEGAUX.md)
|
||||
|
||||
---
|
||||
|
||||
@ -413,7 +413,18 @@ docs/
|
||||
├── 11_API.md
|
||||
├── 20_WORKFLOW-CREATION-COMPTE.md
|
||||
├── 21_CONFIGURATION-SYSTEME.md
|
||||
├── 22_DOCUMENTS-LEGAUX.md
|
||||
├── 22_DOCUMENTS-LEGAUX.md # pointeur → juridique/
|
||||
├── 27_BRIEFING-FRONTEND.md
|
||||
├── PROCEDURE-API-GITEA.md # pointeur → 26_GITEA-API.md
|
||||
├── juridique/
|
||||
│ ├── README.md
|
||||
│ ├── cgu.md
|
||||
│ ├── cgc.md
|
||||
│ └── 22_DOCUMENTS-LEGAUX.md
|
||||
├── archive/
|
||||
│ ├── README.md
|
||||
│ ├── temporaires/
|
||||
│ └── obsolete/
|
||||
├── 23_LISTE-TICKETS.md
|
||||
├── 24_DECISIONS-PROJET.md (ce document)
|
||||
├── 90_AUDIT.md
|
||||
|
||||
@ -1,176 +1,8 @@
|
||||
# Procédure – Utilisation de l’API Gitea
|
||||
# Fichier déplacé / fusionné
|
||||
|
||||
## 1. Contexte
|
||||
La procédure **API Gitea** est désormais documentée sous :
|
||||
|
||||
- **Instance** : https://git.ptits-pas.fr
|
||||
- **API de base** : `https://git.ptits-pas.fr/api/v1`
|
||||
- **Projet P'titsPas** : dépôt `jmartin/petitspas` (owner = `jmartin`, repo = `petitspas`)
|
||||
**[26_GITEA-API.md](./26_GITEA-API.md)**
|
||||
|
||||
## 2. Authentification
|
||||
|
||||
### 2.1 Token
|
||||
|
||||
Le token est défini dans l’environnement (ex. `~/.bashrc`) :
|
||||
|
||||
```bash
|
||||
export GITEA_TOKEN="<votre_token>"
|
||||
```
|
||||
|
||||
Pour l’utiliser dans les commandes :
|
||||
|
||||
```bash
|
||||
source ~/.bashrc # ou : . ~/.bashrc
|
||||
# Puis utiliser $GITEA_TOKEN dans les curl
|
||||
```
|
||||
|
||||
### 2.2 En-tête HTTP
|
||||
|
||||
Toutes les requêtes API doivent envoyer le token :
|
||||
|
||||
```bash
|
||||
-H "Authorization: token $GITEA_TOKEN"
|
||||
```
|
||||
|
||||
Exemple :
|
||||
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas"
|
||||
```
|
||||
|
||||
## 3. Endpoints utiles
|
||||
|
||||
### 3.1 Dépôt (repository)
|
||||
|
||||
| Action | Méthode | URL |
|
||||
|---------------|---------|-----|
|
||||
| Infos dépôt | GET | `/repos/{owner}/{repo}` |
|
||||
| Liste dépôts | GET | `/repos/search?q=petitspas` |
|
||||
|
||||
Exemple – infos du dépôt :
|
||||
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas" | jq .
|
||||
```
|
||||
|
||||
### 3.2 Issues (tickets)
|
||||
|
||||
| Action | Méthode | URL |
|
||||
|------------------|---------|-----|
|
||||
| Liste des issues | GET | `/repos/{owner}/{repo}/issues` |
|
||||
| Détail d’une issue | GET | `/repos/{owner}/{repo}/issues/{index}` |
|
||||
| Créer une issue | POST | `/repos/{owner}/{repo}/issues` |
|
||||
| Modifier une issue | PATCH | `/repos/{owner}/{repo}/issues/{index}` |
|
||||
| Fermer une issue | PATCH | (même URL, `state: "closed"`) |
|
||||
|
||||
**Paramètres GET utiles pour la liste :**
|
||||
|
||||
- `state` : `open` ou `closed`
|
||||
- `labels` : filtre par label (ex. `frontend`)
|
||||
- `page`, `limit` : pagination
|
||||
|
||||
Exemples :
|
||||
|
||||
```bash
|
||||
# Toutes les issues ouvertes
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues?state=open" | jq .
|
||||
|
||||
# Issues ouvertes avec label "frontend"
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues?state=open" | \
|
||||
jq '.[] | select(.labels[].name == "frontend") | {number, title, state}'
|
||||
|
||||
# Détail de l’issue #47
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues/47" | jq .
|
||||
|
||||
# Fermer l’issue #31
|
||||
curl -s -X PATCH -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"state":"closed"}' \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues/31"
|
||||
|
||||
# Créer une issue
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"title":"Titre du ticket","body":"Description","labels":[1]}' \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues"
|
||||
```
|
||||
|
||||
### 3.3 Pull requests
|
||||
|
||||
| Action | Méthode | URL |
|
||||
|---------------|---------|-----|
|
||||
| Liste des PR | GET | `/repos/{owner}/{repo}/pulls` |
|
||||
| Détail d’une PR | GET | `/repos/{owner}/{repo}/pulls/{index}` |
|
||||
| Créer une PR | POST | `/repos/{owner}/{repo}/pulls` |
|
||||
| Fusionner une PR | POST | `/repos/{owner}/{repo}/pulls/{index}/merge` |
|
||||
|
||||
Exemples :
|
||||
|
||||
```bash
|
||||
# Liste des PR ouvertes
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/pulls?state=open" | jq .
|
||||
|
||||
# Créer une PR (head = branche source, base = branche cible)
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"head":"develop","base":"master","title":"Titre de la PR"}' \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/pulls"
|
||||
```
|
||||
|
||||
### 3.4 Branches
|
||||
|
||||
| Action | Méthode | URL |
|
||||
|---------------|---------|-----|
|
||||
| Liste des branches | GET | `/repos/{owner}/{repo}/branches` |
|
||||
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/branches" | jq '.[].name'
|
||||
```
|
||||
|
||||
### 3.5 Webhooks
|
||||
|
||||
| Action | Méthode | URL |
|
||||
|---------------|---------|-----|
|
||||
| Liste webhooks | GET | `/repos/{owner}/{repo}/hooks` |
|
||||
| Créer webhook | POST | `/repos/{owner}/{repo}/hooks` |
|
||||
|
||||
### 3.6 Labels
|
||||
|
||||
| Action | Méthode | URL |
|
||||
|---------------|---------|-----|
|
||||
| Liste des labels | GET | `/repos/{owner}/{repo}/labels` |
|
||||
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/labels" | jq '.[] | {id, name}'
|
||||
```
|
||||
|
||||
## 4. Résumé des URLs pour P'titsPas
|
||||
|
||||
Remplacer `{owner}` par `jmartin` et `{repo}` par `petitspas` :
|
||||
|
||||
| Ressource | URL |
|
||||
|------------------|-----|
|
||||
| Dépôt | `https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas` |
|
||||
| Issues | `https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues` |
|
||||
| Issue #n | `https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues/{n}` |
|
||||
| Pull requests | `https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/pulls` |
|
||||
| Branches | `https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/branches` |
|
||||
| Labels | `https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/labels` |
|
||||
|
||||
## 5. Documentation officielle
|
||||
|
||||
- Swagger / OpenAPI : https://docs.gitea.com/api
|
||||
- Référence selon la version de Gitea installée (ex. 1.21, 1.25).
|
||||
|
||||
## 6. Dépannage
|
||||
|
||||
- **401 Unauthorized** : vérifier le token et l’en-tête `Authorization: token <TOKEN>`.
|
||||
- **404** : vérifier owner/repo et l’URL (sensible à la casse).
|
||||
- **422 / body invalide** : pour POST/PATCH, envoyer `Content-Type: application/json` et un JSON valide.
|
||||
L’ancienne copie `PROCEDURE-API-GITEA.md` est archivée dans
|
||||
`docs/archive/obsolete/` (doublon).
|
||||
|
||||
30
docs/archive/README.md
Normal file
30
docs/archive/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Archive documentation · P'titsPas
|
||||
|
||||
Ce dossier regroupe les fichiers **sans préfixe numérique** à la racine de
|
||||
`docs/` qui ne sont plus des **références actives**, ou qui sont des
|
||||
**brouillons / temporaires**.
|
||||
|
||||
## Règle de nommage (racine `docs/`)
|
||||
|
||||
- Les documents **normatifs** à la racine portent un préfixe **`NN_`**
|
||||
(deux chiffres), ex. `23_LISTE-TICKETS.md`.
|
||||
- **Exceptions** (héritage ou outillage) listées dans
|
||||
[**00_INDEX.md**](../00_INDEX.md#exceptions-de-nommage) : charte, CDC
|
||||
historique, évolutions — **cible** : les renommer progressivement en `NN_`
|
||||
et mettre à jour `.cursorrules` / liens.
|
||||
|
||||
## Sous-dossiers ici
|
||||
|
||||
| Dossier | Usage |
|
||||
|---------|--------|
|
||||
| [**temporaires/**](./temporaires/) | Notes jetables, exports de travail.
|
||||
**Supprimables** quand la tâche associée est close. |
|
||||
| [**obsolete/**](./obsolete/) | Ancienne doc **remplacée** ou **doublon**
|
||||
(conservée un temps pour historique). **Supprimer** après bascule confirmée
|
||||
si plus aucune référence. |
|
||||
|
||||
## Hors `docs/` racine
|
||||
|
||||
Les dossiers thématiques (**`juridique/`**, **`test-data/`**, etc.) peuvent
|
||||
contenir des fichiers sans `NN_` : la règle `NN_` s’applique surtout aux
|
||||
fichiers **directement** sous `docs/`.
|
||||
@ -1,4 +1,9 @@
|
||||
# Architecture Technique - P'titsPas
|
||||
|
||||
> **Archivé** (`docs/archive/obsolete/`) — non maintenu à la racine `docs/`.
|
||||
> Voir [`02_ARCHITECTURE.md`](../../02_ARCHITECTURE.md) pour la référence
|
||||
> courante.
|
||||
|
||||
## Guide d'Infrastructure et de Déploiement
|
||||
|
||||
---
|
||||
180
docs/archive/obsolete/PROCEDURE-API-GITEA.md
Normal file
180
docs/archive/obsolete/PROCEDURE-API-GITEA.md
Normal file
@ -0,0 +1,180 @@
|
||||
# Procédure – Utilisation de l’API Gitea
|
||||
|
||||
> **Archivé** (`docs/archive/obsolete/`) — **doublon** de
|
||||
> [`26_GITEA-API.md`](../../26_GITEA-API.md). Ne pas modifier ici ; éditer le
|
||||
> fichier numéroté.
|
||||
|
||||
## 1. Contexte
|
||||
|
||||
- **Instance** : https://git.ptits-pas.fr
|
||||
- **API de base** : `https://git.ptits-pas.fr/api/v1`
|
||||
- **Projet P'titsPas** : dépôt `jmartin/petitspas` (owner = `jmartin`, repo = `petitspas`)
|
||||
|
||||
## 2. Authentification
|
||||
|
||||
### 2.1 Token
|
||||
|
||||
Le token est défini dans l’environnement (ex. `~/.bashrc`) :
|
||||
|
||||
```bash
|
||||
export GITEA_TOKEN="<votre_token>"
|
||||
```
|
||||
|
||||
Pour l’utiliser dans les commandes :
|
||||
|
||||
```bash
|
||||
source ~/.bashrc # ou : . ~/.bashrc
|
||||
# Puis utiliser $GITEA_TOKEN dans les curl
|
||||
```
|
||||
|
||||
### 2.2 En-tête HTTP
|
||||
|
||||
Toutes les requêtes API doivent envoyer le token :
|
||||
|
||||
```bash
|
||||
-H "Authorization: token $GITEA_TOKEN"
|
||||
```
|
||||
|
||||
Exemple :
|
||||
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas"
|
||||
```
|
||||
|
||||
## 3. Endpoints utiles
|
||||
|
||||
### 3.1 Dépôt (repository)
|
||||
|
||||
| Action | Méthode | URL |
|
||||
|---------------|---------|-----|
|
||||
| Infos dépôt | GET | `/repos/{owner}/{repo}` |
|
||||
| Liste dépôts | GET | `/repos/search?q=petitspas` |
|
||||
|
||||
Exemple – infos du dépôt :
|
||||
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas" | jq .
|
||||
```
|
||||
|
||||
### 3.2 Issues (tickets)
|
||||
|
||||
| Action | Méthode | URL |
|
||||
|------------------|---------|-----|
|
||||
| Liste des issues | GET | `/repos/{owner}/{repo}/issues` |
|
||||
| Détail d’une issue | GET | `/repos/{owner}/{repo}/issues/{index}` |
|
||||
| Créer une issue | POST | `/repos/{owner}/{repo}/issues` |
|
||||
| Modifier une issue | PATCH | `/repos/{owner}/{repo}/issues/{index}` |
|
||||
| Fermer une issue | PATCH | (même URL, `state: "closed"`) |
|
||||
|
||||
**Paramètres GET utiles pour la liste :**
|
||||
|
||||
- `state` : `open` ou `closed`
|
||||
- `labels` : filtre par label (ex. `frontend`)
|
||||
- `page`, `limit` : pagination
|
||||
|
||||
Exemples :
|
||||
|
||||
```bash
|
||||
# Toutes les issues ouvertes
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues?state=open" | jq .
|
||||
|
||||
# Issues ouvertes avec label "frontend"
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues?state=open" | \
|
||||
jq '.[] | select(.labels[].name == "frontend") | {number, title, state}'
|
||||
|
||||
# Détail de l’issue #47
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues/47" | jq .
|
||||
|
||||
# Fermer l’issue #31
|
||||
curl -s -X PATCH -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"state":"closed"}' \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues/31"
|
||||
|
||||
# Créer une issue
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"title":"Titre du ticket","body":"Description","labels":[1]}' \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues"
|
||||
```
|
||||
|
||||
### 3.3 Pull requests
|
||||
|
||||
| Action | Méthode | URL |
|
||||
|---------------|---------|-----|
|
||||
| Liste des PR | GET | `/repos/{owner}/{repo}/pulls` |
|
||||
| Détail d’une PR | GET | `/repos/{owner}/{repo}/pulls/{index}` |
|
||||
| Créer une PR | POST | `/repos/{owner}/{repo}/pulls` |
|
||||
| Fusionner une PR | POST | `/repos/{owner}/{repo}/pulls/{index}/merge` |
|
||||
|
||||
Exemples :
|
||||
|
||||
```bash
|
||||
# Liste des PR ouvertes
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/pulls?state=open" | jq .
|
||||
|
||||
# Créer une PR (head = branche source, base = branche cible)
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"head":"develop","base":"master","title":"Titre de la PR"}' \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/pulls"
|
||||
```
|
||||
|
||||
### 3.4 Branches
|
||||
|
||||
| Action | Méthode | URL |
|
||||
|---------------|---------|-----|
|
||||
| Liste des branches | GET | `/repos/{owner}/{repo}/branches` |
|
||||
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/branches" | jq '.[].name'
|
||||
```
|
||||
|
||||
### 3.5 Webhooks
|
||||
|
||||
| Action | Méthode | URL |
|
||||
|---------------|---------|-----|
|
||||
| Liste webhooks | GET | `/repos/{owner}/{repo}/hooks` |
|
||||
| Créer webhook | POST | `/repos/{owner}/{repo}/hooks` |
|
||||
|
||||
### 3.6 Labels
|
||||
|
||||
| Action | Méthode | URL |
|
||||
|---------------|---------|-----|
|
||||
| Liste des labels | GET | `/repos/{owner}/{repo}/labels` |
|
||||
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/labels" | jq '.[] | {id, name}'
|
||||
```
|
||||
|
||||
## 4. Résumé des URLs pour P'titsPas
|
||||
|
||||
Remplacer `{owner}` par `jmartin` et `{repo}` par `petitspas` :
|
||||
|
||||
| Ressource | URL |
|
||||
|------------------|-----|
|
||||
| Dépôt | `https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas` |
|
||||
| Issues | `https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues` |
|
||||
| Issue #n | `https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues/{n}` |
|
||||
| Pull requests | `https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/pulls` |
|
||||
| Branches | `https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/branches` |
|
||||
| Labels | `https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/labels` |
|
||||
|
||||
## 5. Documentation officielle
|
||||
|
||||
- Swagger / OpenAPI : https://docs.gitea.com/api
|
||||
- Référence selon la version de Gitea installée (ex. 1.21, 1.25).
|
||||
|
||||
## 6. Dépannage
|
||||
|
||||
- **401 Unauthorized** : vérifier le token et l’en-tête `Authorization: token <TOKEN>`.
|
||||
- **404** : vérifier owner/repo et l’URL (sensible à la casse).
|
||||
- **422 / body invalide** : pour POST/PATCH, envoyer `Content-Type: application/json` et un JSON valide.
|
||||
14
docs/archive/obsolete/README.md
Normal file
14
docs/archive/obsolete/README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Obsolete
|
||||
|
||||
Ancienne documentation **déplacée** depuis `docs/` :
|
||||
|
||||
| Fichier | Motif |
|
||||
|---------|--------|
|
||||
| `PROCEDURE-API-GITEA.md` | Doublon fonctionnel de
|
||||
[**26_GITEA-API.md**](../../26_GITEA-API.md). |
|
||||
| `ARCHITECTURE_TECHNIQUE.md` | Non référencé ; la vue d’ensemble est dans
|
||||
[**02_ARCHITECTURE.md**](../../02_ARCHITECTURE.md). |
|
||||
| `STATUS-APPLICATION.md` | Instantané daté ; non tenu comme doc vivante. |
|
||||
|
||||
Après vérification qu’aucun lien externe ne pointe encore vers ces chemins, on
|
||||
peut **supprimer** ce sous-dossier ou ne garder que des pointeurs minimalistes.
|
||||
@ -1,5 +1,8 @@
|
||||
# Statut de l'application P'titsPas
|
||||
|
||||
> **Archivé** (`docs/archive/obsolete/`) — instantané du **8 février 2026**, non
|
||||
> maintenu comme référence courante. Voir `docs/00_INDEX.md` pour la doc à jour.
|
||||
|
||||
**Date du point** : 8 février 2026
|
||||
|
||||
---
|
||||
@ -99,8 +102,8 @@ Un seul déploiement à la fois (verrou) ; plus d’étape Prisma dans le script
|
||||
| `11_API.md` | Endpoints API |
|
||||
| `20_WORKFLOW-CREATION-COMPTE.md` | Workflow création compte |
|
||||
| `23_LISTE-TICKETS.md` | Liste des tickets |
|
||||
| `BRIEFING-FRONTEND.md` | Brief frontend, accès Git, tickets prioritaires |
|
||||
| `PROCEDURE-API-GITEA.md` | Utilisation API Gitea (issues, PR, token) |
|
||||
| `27_BRIEFING-FRONTEND.md` | Brief frontend, accès Git, tickets prioritaires |
|
||||
| `26_GITEA-API.md` | Utilisation API Gitea (issues, PR, token) |
|
||||
|
||||
---
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
# Archivé docs/archive/temporaires/ — export jetable, supprimer si inutile.
|
||||
Point tickets frontend (API Gitea) - 27/01/2026
|
||||
================================================
|
||||
|
||||
10
docs/archive/temporaires/README.md
Normal file
10
docs/archive/temporaires/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Temporaires
|
||||
|
||||
Fichiers **non numérotés** de travail (brouillons, listes de tickets exportées,
|
||||
alignements UI en cours, etc.).
|
||||
|
||||
- Préfixe conseillé pour les nouveaux fichiers jetables : **`TEMP_`** ou
|
||||
**`WIP_`** dans ce dossier.
|
||||
- **Suppression** : dès que la fonctionnalité est livrée ou le sujet clos,
|
||||
supprimer le fichier (ou le déplacer vers `obsolete/` si une trace utile
|
||||
reste nécessaire).
|
||||
@ -1,6 +1,8 @@
|
||||
# TEMP — Alignement front / API (inscription AM & validation gestionnaire)
|
||||
|
||||
> **Fichier temporaire** : à supprimer ou renommer une fois le front livré.
|
||||
> **Archivé** (`docs/archive/temporaires/`) — **fichier temporaire** ; à
|
||||
> **supprimer** une fois le front livré ou le sujet clos (voir
|
||||
> `docs/archive/temporaires/README.md`).
|
||||
|
||||
Ce document décrit les changements **côté API** et ce que **Flutter** doit faire pour rester aligné. Aucune modification front n’a été faite dans le chantier backend associé.
|
||||
|
||||
698
docs/juridique/22_DOCUMENTS-LEGAUX.md
Normal file
698
docs/juridique/22_DOCUMENTS-LEGAUX.md
Normal file
@ -0,0 +1,698 @@
|
||||
# 📄 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é
|
||||
|
||||
|
||||
|
||||
41
docs/juridique/README.md
Normal file
41
docs/juridique/README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Juridique · P'titsPas
|
||||
|
||||
Ce dossier regroupe la **documentation juridique** et les **sources Markdown**
|
||||
destinées à être publiées (souvent en **PDF**) dans l’application : notamment
|
||||
l’écran d’**acceptation à l’inscription** (CGU + politique de confidentialité).
|
||||
|
||||
## Documents « application » (à exporter en PDF)
|
||||
|
||||
| Fichier | Rôle |
|
||||
|--------|------|
|
||||
| [**cgu.md**](./cgu.md) | **Conditions générales d’utilisation** du service |
|
||||
| [**politique-confidentialite.md**](./politique-confidentialite.md) | **Politique de confidentialité** (RGPD, droits, finalités) |
|
||||
|
||||
Ces deux fichiers correspondent aux types **`cgu`** et **`privacy`** côté API
|
||||
documents légaux (voir doc technique ci-dessous).
|
||||
|
||||
## Document complémentaire (optionnel)
|
||||
|
||||
| Fichier | Rôle |
|
||||
|--------|------|
|
||||
| [**cgc.md**](./cgc.md) | **Conditions générales contractuelles** (cadre
|
||||
d’engagement des parties) — distinct de la politique de confidentialité |
|
||||
|
||||
## Documentation technique
|
||||
|
||||
| Fichier | Rôle |
|
||||
|--------|------|
|
||||
| [**22_DOCUMENTS-LEGAUX.md**](./22_DOCUMENTS-LEGAUX.md) | API, BDD, versioning,
|
||||
traçabilité des acceptations |
|
||||
|
||||
> Tous les textes ci-dessus sont des **brouillons** à valider par un **juriste**
|
||||
> ou la **DPD/DPO** avant diffusion en production.
|
||||
|
||||
## Export PDF (local)
|
||||
|
||||
1. Ouvrir le `.md` dans Cursor.
|
||||
2. `Ctrl+Shift+P` → **Markdown PDF: Export (pdf)** (extension *Markdown PDF*,
|
||||
auteur yzane).
|
||||
|
||||
Les PDF déployés côté API sont décrits dans
|
||||
[22_DOCUMENTS-LEGAUX.md](./22_DOCUMENTS-LEGAUX.md).
|
||||
116
docs/juridique/cgc.md
Normal file
116
docs/juridique/cgc.md
Normal file
@ -0,0 +1,116 @@
|
||||
# Conditions générales contractuelles (CGC)
|
||||
|
||||
**Produit** : P'titsPas
|
||||
**Version document** : 0.1 (brouillon)
|
||||
**Date** : avril 2026
|
||||
**Statut** : à valider juridiquement avant publication
|
||||
|
||||
> **Ce document n’est pas** la **politique de confidentialité** (données
|
||||
> personnelles / RGPD) : voir
|
||||
> [**politique-confidentialite.md**](./politique-confidentialite.md).
|
||||
|
||||
---
|
||||
|
||||
## 1. Objet
|
||||
|
||||
Les présentes **conditions générales contractuelles** (« CGC ») définissent le
|
||||
**cadre contractuel** entre les **parties** impliquées dans l’usage de la
|
||||
plateforme **P'titsPas** pour la gestion de la garde d’enfants au sein des
|
||||
**collectivités** ou structures habilitées.
|
||||
|
||||
Elles complètent les [**conditions générales d’utilisation**](./cgu.md) (CGU),
|
||||
qui régissent l’usage du **Service numérique** lui-même.
|
||||
|
||||
---
|
||||
|
||||
## 2. Parties
|
||||
|
||||
Selon les cas d’usage du déploiement, peuvent notamment être parties :
|
||||
|
||||
- la **collectivité** ou l’**employeur public** porteur du dispositif ;
|
||||
- l’**Éditeur** ou l’**opérateur** assurant l’hébergement et la maintenance du
|
||||
Service ;
|
||||
- les **Utilisateurs** (parents, assistantes maternelles, relais d’assistantes
|
||||
maternelles, gestionnaires, etc.).
|
||||
|
||||
[À compléter : identification précise des personnes morales signataires et
|
||||
rôles (maîtrise d’ouvrage, maîtrise d’œuvre, délégation).]
|
||||
|
||||
---
|
||||
|
||||
## 3. Objet du contrat
|
||||
|
||||
Le contrat a pour objet de préciser :
|
||||
|
||||
- les **missions** et **responsabilités** de chaque catégorie d’acteur ;
|
||||
- les **conditions d’inscription**, de **validation** des dossiers et de
|
||||
**clôture** des comptes ;
|
||||
- les **obligations** en matière de **sincérité** des informations et de
|
||||
**coopération** avec les services compétents.
|
||||
|
||||
---
|
||||
|
||||
## 4. Obligations des Utilisateurs
|
||||
|
||||
Les Utilisateurs s’engagent à :
|
||||
|
||||
- respecter le **cadre légal** applicable à la garde d’enfants et à la
|
||||
protection de l’enfance ;
|
||||
- ne fournir que des **documents** et **informations** licites et, le cas échéant,
|
||||
conformes aux demandes des gestionnaires ;
|
||||
- respecter la **charte d’usage** et les consignes communiquées par la
|
||||
collectivité ou l’Éditeur.
|
||||
|
||||
---
|
||||
|
||||
## 5. Obligations de la collectivité / de l’Éditeur
|
||||
|
||||
[À compléter : disponibilité cible, support, sauvegardes, sous-traitance,
|
||||
sous-traitants, transferts hors UE, etc.]
|
||||
|
||||
---
|
||||
|
||||
## 6. Durée et résiliation
|
||||
|
||||
Le présent cadre contractuel est conclu pour la **durée** d’utilisation du
|
||||
Service par les Utilisateurs concernés, sous réserve des **résiliations**
|
||||
prévues par la réglementation ou par décision légitime de l’Éditeur / de la
|
||||
collectivité.
|
||||
|
||||
La **résiliation** d’un compte Utilisateur n’implique pas nécessairement la
|
||||
**suppression immédiate** de toutes les données ; les **archives** et délais
|
||||
peuvent être imposés par la loi.
|
||||
|
||||
---
|
||||
|
||||
## 7. Modification
|
||||
|
||||
Les CGC peuvent être **adaptées** (évolution réglementaire, organisation du
|
||||
dispositif). Les Utilisateurs en sont informés par un **moyen approprié** et,
|
||||
lorsque la loi ou la pratique l’exige, une **nouvelle acceptation** est
|
||||
enregistrée.
|
||||
|
||||
---
|
||||
|
||||
## 8. Responsabilité et assurance
|
||||
|
||||
[À compléter : plafonds, exclusions, co-traitance, garanties, assurance
|
||||
responsabilité civile, etc.]
|
||||
|
||||
---
|
||||
|
||||
## 9. Litiges
|
||||
|
||||
[À compléter : médiation, compétence territoriale, droit applicable.]
|
||||
|
||||
---
|
||||
|
||||
## 10. Dispositions finales
|
||||
|
||||
[À compléter : intégralité de l’accord, nullité partielle, langue de référence,
|
||||
date d’effet.]
|
||||
|
||||
---
|
||||
|
||||
*Document généré comme base de travail pour le projet P'titsPas. Il ne
|
||||
constitue pas un conseil juridique.*
|
||||
119
docs/juridique/cgu.md
Normal file
119
docs/juridique/cgu.md
Normal file
@ -0,0 +1,119 @@
|
||||
# Conditions générales d’utilisation (CGU)
|
||||
|
||||
**Produit** : P'titsPas
|
||||
**Version document** : 0.2 (brouillon)
|
||||
**Date** : avril 2026
|
||||
**Statut** : à valider juridiquement avant publication
|
||||
|
||||
---
|
||||
|
||||
## 1. Objet et champ d’application
|
||||
|
||||
Les présentes **conditions générales d’utilisation** (« CGU ») encadrent
|
||||
l’accès et l’usage de la **plateforme logicielle P'titsPas** (ci-après le
|
||||
« **Service** »), dédiée à l’organisation de la **garde d’enfants de 0 à 3 ans**
|
||||
dans le cadre des **collectivités locales** ou structures habilitées.
|
||||
|
||||
Toute **création de compte** ou **utilisation** du Service vaut acceptation des
|
||||
CGU en vigueur au moment de l’utilisation.
|
||||
|
||||
---
|
||||
|
||||
## 2. Définitions
|
||||
|
||||
- **Éditeur** : la personne morale ou la collectivité responsable du
|
||||
déploiement et de l’administration du Service.
|
||||
- **Utilisateur** : toute personne disposant d’un compte (parent, assistante
|
||||
maternelle, relais, gestionnaire, administrateur, etc.).
|
||||
- **Données** : informations saisies ou générées dans le Service.
|
||||
|
||||
---
|
||||
|
||||
## 3. Accès au Service
|
||||
|
||||
Le Service est accessible selon les **modalités techniques** définies par
|
||||
l’Éditeur (URL, authentification, pare-feu, etc.).
|
||||
|
||||
L’Éditeur peut **suspendre** ou **restreindre** l’accès pour maintenance,
|
||||
sécurité ou obligation légale, dans la mesure du possible en informant les
|
||||
Utilisateurs par un moyen adapté.
|
||||
|
||||
---
|
||||
|
||||
## 4. Comptes et sécurité
|
||||
|
||||
Chaque Utilisateur s’engage à :
|
||||
|
||||
- fournir des **informations exactes** et les mettre à jour ;
|
||||
- **garder confidentiels** ses identifiants et mots de passe ;
|
||||
- signaler sans délai toute **utilisation non autorisée** de son compte.
|
||||
|
||||
L’Éditeur peut **désactiver** un compte en cas de manquement grave aux présentes
|
||||
CGU ou à la réglementation applicable.
|
||||
|
||||
---
|
||||
|
||||
## 5. Usage loyal du Service
|
||||
|
||||
Il est notamment interdit d’utiliser le Service pour :
|
||||
|
||||
- porter atteinte aux **droits des tiers** ou à l’**ordre public** ;
|
||||
- tenter d’**accéder** de manière frauduleuse à des données ou des fonctions non
|
||||
autorisées ;
|
||||
- **perturber** le fonctionnement du Service (attaques, surcharge, etc.).
|
||||
|
||||
---
|
||||
|
||||
## 6. Propriété intellectuelle
|
||||
|
||||
Les **éléments** du Service (logiciel, charte graphique, documentation interne
|
||||
non publique, etc.) restent la propriété de leurs titulaires respectifs, sauf
|
||||
mention contraire.
|
||||
|
||||
Les **contenus** saisis par les Utilisateurs (textes, pièces jointes autorisées)
|
||||
restent sous leur responsabilité ; ils concèdent à l’Éditeur les droits
|
||||
**strictement nécessaires** à l’hébergement et au fonctionnement du Service.
|
||||
|
||||
---
|
||||
|
||||
## 7. Données personnelles
|
||||
|
||||
Le traitement des données personnelles est décrit dans la [**politique de
|
||||
confidentialité**](./politique-confidentialite.md) et les documents affichés
|
||||
dans le Service, conformément au **RGPD** et aux obligations locales.
|
||||
|
||||
---
|
||||
|
||||
## 8. Responsabilité
|
||||
|
||||
Le Service est fourni **en l’état**, dans les limites de l’état de l’art et des
|
||||
moyens raisonnables de l’Éditeur.
|
||||
|
||||
L’Éditeur ne saurait être tenu responsable des **dommages indirects** ou des
|
||||
**indisponibilités** indépendantes de sa volonté, dans les limites autorisées par
|
||||
la loi.
|
||||
|
||||
---
|
||||
|
||||
## 9. Évolution des CGU
|
||||
|
||||
Les CGU peuvent être **modifiées**. Les Utilisateurs sont informés selon les
|
||||
moyens prévus (notification, ré-affichage à la connexion, etc.). La **version**
|
||||
acceptée est **tracée** dans le Service lorsque la fonctionnalité est activée.
|
||||
|
||||
---
|
||||
|
||||
## 10. Droit applicable et litiges
|
||||
|
||||
[À compléter : droit applicable, juridiction compétente, médiation éventuelle.]
|
||||
|
||||
---
|
||||
|
||||
## 11. Contact
|
||||
|
||||
[À compléter : coordonnées du DPO ou du service utilisateurs.]
|
||||
|
||||
---
|
||||
|
||||
*Document généré comme base de travail pour le projet P'titsPas. Il ne
|
||||
constitue pas un conseil juridique.*
|
||||
BIN
docs/juridique/cgu.pdf
Normal file
BIN
docs/juridique/cgu.pdf
Normal file
Binary file not shown.
188
docs/juridique/politique-confidentialite.md
Normal file
188
docs/juridique/politique-confidentialite.md
Normal file
@ -0,0 +1,188 @@
|
||||
# Politique de confidentialité
|
||||
|
||||
**Produit** : P'titsPas
|
||||
**Version document** : 0.1 (brouillon)
|
||||
**Date** : avril 2026
|
||||
**Statut** : à valider juridiquement avant publication
|
||||
|
||||
Document distinct des [**conditions générales d’utilisation**](./cgu.md)
|
||||
(CGU), qui régissent l’usage du service.
|
||||
|
||||
---
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
La présente **politique de confidentialité** décrit comment sont traitées les
|
||||
**données à caractère personnel** dans le cadre de la plateforme **P'titsPas**,
|
||||
application dédiée à l’organisation de la **garde d’enfants** (notamment 0 à 3
|
||||
ans) pour le compte de **collectivités** ou structures habilitées.
|
||||
|
||||
Elle s’applique aux **Utilisateurs** du Service (parents, assistantes
|
||||
maternelles, relais, gestionnaires, administrateurs, etc.).
|
||||
|
||||
---
|
||||
|
||||
## 2. Responsable du traitement
|
||||
|
||||
[À compléter : identité et coordonnées du **responsable du traitement** — en
|
||||
général la collectivité ou la personne morale désignée selon le mode de
|
||||
déploiement (on-premise, délégation, etc.).]
|
||||
|
||||
Pour toute question relative à cette politique ou à vos droits :
|
||||
|
||||
- **Contact** : [À compléter : courriel / adresse postale du service ou du
|
||||
DPD/DPO.]
|
||||
- **Délégué à la protection des données (DPD/DPO)** : [À compléter si désigné.]
|
||||
|
||||
---
|
||||
|
||||
## 3. Données collectées
|
||||
|
||||
Selon les profils et les fonctionnalités activées, peuvent notamment être
|
||||
traitées :
|
||||
|
||||
- **Données d’identité et de contact** : nom, prénom, adresse électronique,
|
||||
numéro de téléphone, adresse postale, etc.
|
||||
- **Données d’authentification** : identifiant, données de connexion, indicateur
|
||||
de changement de mot de passe obligatoire, etc.
|
||||
- **Données professionnelles** (assistantes maternelles, relais) : agrément,
|
||||
capacité, présentation, pièces ou identifiants professionnels selon le
|
||||
formulaire.
|
||||
- **Données relatives aux enfants** : prénom, date de naissance, lien avec les
|
||||
titulaires de l’autorité parentale, informations de santé ou de garde **uniquement**
|
||||
dans la mesure où leur collecte est **nécessaire** au service et **encadrée**
|
||||
par la collectivité et la loi.
|
||||
- **Données de connexion et de traçabilité** : journaux techniques, adresse IP,
|
||||
agent utilisateur, horodatages, traces d’**acceptation** des documents légaux
|
||||
(CGU, présente politique), etc.
|
||||
- **Contenus** saisis dans le Service (textes libres, messages si la messagerie
|
||||
est activée, etc.).
|
||||
|
||||
[À compléter : liste exhaustive alignée sur les écrans et tables réelles du
|
||||
déploiement.]
|
||||
|
||||
---
|
||||
|
||||
## 4. Finalités et bases légales
|
||||
|
||||
Les traitements poursuivent notamment les finalités suivantes (bases légales
|
||||
indicatives, **à affiner** avec le juriste) :
|
||||
|
||||
| Finalité | Base légale (indicative) |
|
||||
|----------|---------------------------|
|
||||
| Création et gestion des **comptes** | Exécution de mesures précontractuelles /
|
||||
contrat ; obligation légale le cas échéant |
|
||||
| Gestion des **dossiers** de garde et des **inscriptions** | Mission de
|
||||
service public / contrat / obligation légale selon contexte |
|
||||
| **Sécurité** du Service, prévention des abus | Intérêt légitime ; obligation
|
||||
légale |
|
||||
| **Traçabilité** des acceptations de documents (CGU, politique) | Obligation
|
||||
légale ; preuve d’un contrat |
|
||||
| **Amélioration** et statistiques d’usage (si activées) | Intérêt légitime ou
|
||||
consentement selon les moyens |
|
||||
|
||||
---
|
||||
|
||||
## 5. Destinataires des données
|
||||
|
||||
Les données peuvent être accessibles :
|
||||
|
||||
- aux **personnels habilités** de la collectivité ou de l’Éditeur (selon les
|
||||
rôles : gestionnaire, administrateur, etc.) ;
|
||||
- aux **Utilisateurs** dans la limite de ce qui est nécessaire au fonctionnement
|
||||
du dispositif (ex. dossier partagé) ;
|
||||
- aux **sous-traitants** techniques strictement nécessaires (hébergement,
|
||||
sauvegarde, messagerie, etc.) **sous contrat** conforme au RGPD.
|
||||
|
||||
[À compléter : liste des sous-traitants et pays d’hébergement.]
|
||||
|
||||
---
|
||||
|
||||
## 6. Durée de conservation
|
||||
|
||||
Les données sont conservées :
|
||||
|
||||
- pendant la **durée d’utilisation** du compte et du dossier ;
|
||||
- puis, le cas échéant, pour les **durées légales** ou réglementaires de
|
||||
conservation (archives, preuve, comptabilité, etc.).
|
||||
|
||||
[À compléter : grille de conservation par type de donnée.]
|
||||
|
||||
---
|
||||
|
||||
## 7. Vos droits
|
||||
|
||||
Conformément au **RGPD**, vous disposez notamment des droits suivants (dans les
|
||||
conditions et limites prévues par la loi) :
|
||||
|
||||
- **droit d’accès** et de **rectification** ;
|
||||
- **droit à l’effacement** (« droit à l’oubli ») ;
|
||||
- **droit à la limitation** du traitement ;
|
||||
- **droit à la portabilité** (le cas échéant) ;
|
||||
- **droit d’opposition** (notamment pour prospection, selon contexte) ;
|
||||
- **droit de retirer votre consentement** lorsque le traitement repose sur le
|
||||
consentement ;
|
||||
- **droit d’introduire une réclamation** auprès de la **CNIL** (France) :
|
||||
[https://www.cnil.fr](https://www.cnil.fr).
|
||||
|
||||
Pour exercer vos droits : [À compléter : procédure, délais de réponse, pièce
|
||||
d’identité si exigée.]
|
||||
|
||||
---
|
||||
|
||||
## 8. Sécurité
|
||||
|
||||
L’Éditeur met en œuvre des **mesures techniques et organisationnelles**
|
||||
appropriées (contrôle d’accès, chiffrement en transit selon configuration,
|
||||
journalisation, mots de passe, etc.).
|
||||
|
||||
[À compléter : politique de sauvegarde, PRA, certification éventuelle.]
|
||||
|
||||
---
|
||||
|
||||
## 9. Transferts hors Union européenne
|
||||
|
||||
[À compléter : absence de transfert, ou garanties (clauses types, pays
|
||||
adéquats, etc.).]
|
||||
|
||||
---
|
||||
|
||||
## 10. Cookies et traceurs (application web)
|
||||
|
||||
Lorsque le Service est utilisé via un **navigateur**, des **cookies** ou
|
||||
mécanismes équivalents peuvent être utilisés pour la **session**, la
|
||||
**sécurité** ou des mesures d’**audience** si elles sont activées.
|
||||
|
||||
[À compléter : liste des traceurs, finalité, durée, consentement si requis.]
|
||||
|
||||
---
|
||||
|
||||
## 11. Données des enfants
|
||||
|
||||
Les données concernant les **enfants** ne sont traitées que dans le **cadre
|
||||
légal** de la garde et des missions de la collectivité, avec les **garants**
|
||||
habituellement titulaires de l’autorité parentale ou personnes désignées par la
|
||||
loi.
|
||||
|
||||
[À compléter : référence au cadre local (protection de l’enfance, secret
|
||||
professionnel, etc.).]
|
||||
|
||||
---
|
||||
|
||||
## 12. Évolution de la politique
|
||||
|
||||
La présente politique peut être **modifiée**. Les Utilisateurs en sont informés
|
||||
par un **moyen adapté** ; une **nouvelle version** peut être présentée lors de la
|
||||
connexion ou de l’inscription, avec **trace** de l’acceptation lorsque le
|
||||
Service le prévoit.
|
||||
|
||||
---
|
||||
|
||||
## 13. Contact
|
||||
|
||||
[À compléter : même bloc que section 2 si besoin.]
|
||||
|
||||
---
|
||||
|
||||
*Document généré comme base de travail pour le projet P'titsPas. Il ne
|
||||
constitue pas un conseil juridique.*
|
||||
BIN
docs/juridique/politique-confidentialite.pdf
Normal file
BIN
docs/juridique/politique-confidentialite.pdf
Normal file
Binary file not shown.
BIN
frontend/assets/documents/cgu_ptitspas.pdf
Normal file
BIN
frontend/assets/documents/cgu_ptitspas.pdf
Normal file
Binary file not shown.
@ -27,6 +27,12 @@ class ApiConfig {
|
||||
return u;
|
||||
}
|
||||
final base = baseUrl.replaceAll(RegExp(r'/+$'), '');
|
||||
// Le back renvoie parfois des chemins déjà préfixés `/api/v1/...` (ex. documents
|
||||
// légaux). Ne pas les coller à [baseUrl] sous peine de doubler `/api/v1`.
|
||||
if (u.startsWith('/api/')) {
|
||||
final origin = apiOrigin.replaceAll(RegExp(r'/+$'), '');
|
||||
return '$origin$u';
|
||||
}
|
||||
return u.startsWith('/') ? '$base$u' : '$base/$u';
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,39 @@ import 'package:p_tits_pas/models/dossier_unifie.dart';
|
||||
import 'package:p_tits_pas/services/api/api_config.dart';
|
||||
import 'package:p_tits_pas/services/api/tokenService.dart';
|
||||
|
||||
class DocumentActifInfo {
|
||||
final String id;
|
||||
final String type;
|
||||
final int version;
|
||||
final String url;
|
||||
|
||||
const DocumentActifInfo({
|
||||
required this.id,
|
||||
required this.type,
|
||||
required this.version,
|
||||
required this.url,
|
||||
});
|
||||
|
||||
factory DocumentActifInfo.fromJson(Map<String, dynamic> json) {
|
||||
return DocumentActifInfo(
|
||||
id: (json['id'] ?? '').toString(),
|
||||
type: (json['type'] ?? '').toString(),
|
||||
version: int.tryParse((json['version'] ?? '').toString()) ?? 0,
|
||||
url: (json['url'] ?? '').toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentsActifsInfo {
|
||||
final DocumentActifInfo cgu;
|
||||
final DocumentActifInfo privacy;
|
||||
|
||||
const DocumentsActifsInfo({
|
||||
required this.cgu,
|
||||
required this.privacy,
|
||||
});
|
||||
}
|
||||
|
||||
class UserService {
|
||||
static Future<Map<String, String>> _headers() async {
|
||||
final token = await TokenService.getToken();
|
||||
@ -34,6 +67,46 @@ class UserService {
|
||||
return _toStr(err) ?? 'Erreur inconnue';
|
||||
}
|
||||
|
||||
/// Documents légaux actifs (CGU + privacy). GET /documents-legaux/actifs.
|
||||
static Future<DocumentsActifsInfo> getDocumentsLegauxActifs() async {
|
||||
final response = await http.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}/documents-legaux/actifs'),
|
||||
headers: ApiConfig.headers,
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
try {
|
||||
final err = jsonDecode(response.body);
|
||||
throw Exception(_errMessage(err is Map ? err['message'] : err));
|
||||
} catch (e) {
|
||||
if (e is Exception) rethrow;
|
||||
throw Exception(
|
||||
'Erreur chargement documents légaux (${response.statusCode})',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
final decoded = jsonDecode(response.body);
|
||||
if (decoded is! Map<String, dynamic>) {
|
||||
throw const FormatException('Réponse invalide');
|
||||
}
|
||||
final cgu = decoded['cgu'];
|
||||
final privacy = decoded['privacy'];
|
||||
if (cgu is! Map || privacy is! Map) {
|
||||
throw const FormatException('Documents actifs manquants');
|
||||
}
|
||||
return DocumentsActifsInfo(
|
||||
cgu: DocumentActifInfo.fromJson(Map<String, dynamic>.from(cgu)),
|
||||
privacy: DocumentActifInfo.fromJson(Map<String, dynamic>.from(privacy)),
|
||||
);
|
||||
} catch (e) {
|
||||
if (e is FormatException) rethrow;
|
||||
throw Exception(
|
||||
'Réponse invalide (documents légaux): ${e is Exception ? e.toString() : "format inattendu"}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Utilisateurs en attente de validation (GET /users/pending). Ticket #107.
|
||||
static Future<List<AppUser>> getPendingUsers({String? role}) async {
|
||||
final query = role != null ? '?role=$role' : '';
|
||||
|
||||
449
frontend/lib/widgets/cgu_privacy_validation_dialog.dart
Normal file
449
frontend/lib/widgets/cgu_privacy_validation_dialog.dart
Normal file
@ -0,0 +1,449 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:pdfx/pdfx.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import '../services/api/api_config.dart';
|
||||
import '../services/user_service.dart';
|
||||
|
||||
/// Tons **turquoise / menthe** (charte : `#8AD0C8`) — plus doux que le vert
|
||||
/// Material « gazon ».
|
||||
abstract final class _ModaleCouleur {
|
||||
static const Color turquoise = Color(0xFF8AD0C8);
|
||||
static const Color turquoiseFonce = Color(0xFF5A9D94);
|
||||
static const Color mentheTresClair = Color(0xFFF3FAF8);
|
||||
static const Color mentheClair = Color(0xFFE2F3EF);
|
||||
static const Color ivoire = Color(0xFFFFFEF9);
|
||||
static const Color encre = Color(0xFF2F2F2F);
|
||||
}
|
||||
|
||||
enum LegalDocType {
|
||||
cgu,
|
||||
privacy,
|
||||
}
|
||||
|
||||
class CguPrivacyValidationDialog extends StatefulWidget {
|
||||
const CguPrivacyValidationDialog({super.key});
|
||||
|
||||
@override
|
||||
State<CguPrivacyValidationDialog> createState() =>
|
||||
_CguPrivacyValidationDialogState();
|
||||
}
|
||||
|
||||
class _CguPrivacyValidationDialogState extends State<CguPrivacyValidationDialog> {
|
||||
bool _loadingLegalDocs = false;
|
||||
DocumentActifInfo? _cguDoc;
|
||||
DocumentActifInfo? _privacyDoc;
|
||||
LegalDocType _selectedDocType = LegalDocType.cgu;
|
||||
|
||||
static const double _cornerRadius = 16;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadActiveLegalDocs();
|
||||
}
|
||||
|
||||
Future<void> _loadActiveLegalDocs() async {
|
||||
setState(() => _loadingLegalDocs = true);
|
||||
try {
|
||||
final docs = await UserService.getDocumentsLegauxActifs();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_cguDoc = docs.cgu;
|
||||
_privacyDoc = docs.privacy;
|
||||
});
|
||||
} catch (_) {
|
||||
// Silencieux : message dans la zone PDF si besoin.
|
||||
} finally {
|
||||
if (mounted) setState(() => _loadingLegalDocs = false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sur le web, [PdfDocument.openFile] n’est pas implémenté : on charge les
|
||||
/// octets puis [PdfDocument.openData].
|
||||
Future<Uint8List> _fetchPdfBytes(String pathOrUrl) async {
|
||||
final absolute = ApiConfig.absoluteMediaUrl(pathOrUrl);
|
||||
final response = await http.get(Uri.parse(absolute));
|
||||
if (response.statusCode != 200) {
|
||||
throw StateError('PDF indisponible (HTTP ${response.statusCode})');
|
||||
}
|
||||
return response.bodyBytes;
|
||||
}
|
||||
|
||||
Future<PdfDocument>? get _currentPdfDocumentFuture {
|
||||
switch (_selectedDocType) {
|
||||
case LegalDocType.cgu:
|
||||
if (_cguDoc == null) return null;
|
||||
return PdfDocument.openData(_fetchPdfBytes(_cguDoc!.url));
|
||||
case LegalDocType.privacy:
|
||||
if (_privacyDoc == null) return null;
|
||||
return PdfDocument.openData(_fetchPdfBytes(_privacyDoc!.url));
|
||||
}
|
||||
}
|
||||
|
||||
String get _dialogTitle {
|
||||
switch (_selectedDocType) {
|
||||
case LegalDocType.cgu:
|
||||
return 'Conditions Générales d’Utilisation';
|
||||
case LegalDocType.privacy:
|
||||
return 'Politique de Confidentialité';
|
||||
}
|
||||
}
|
||||
|
||||
bool get _canValidate {
|
||||
return (_cguDoc != null && _privacyDoc != null) ||
|
||||
(_cguDoc != null &&
|
||||
_privacyDoc == null &&
|
||||
_selectedDocType == LegalDocType.cgu) ||
|
||||
(_privacyDoc != null &&
|
||||
_cguDoc == null &&
|
||||
_selectedDocType == LegalDocType.privacy);
|
||||
}
|
||||
|
||||
/// [PdfViewPinch] n’est pas supporté sur Windows (pdfx) : repli [PdfView].
|
||||
Widget _buildPdfBody() {
|
||||
final doc = _currentPdfDocumentFuture!;
|
||||
final key = ValueKey(_selectedDocType);
|
||||
if (UniversalPlatform.isWindows) {
|
||||
return PdfView(
|
||||
key: key,
|
||||
scrollDirection: Axis.vertical,
|
||||
controller: PdfController(document: doc),
|
||||
backgroundDecoration: const BoxDecoration(color: Colors.white),
|
||||
);
|
||||
}
|
||||
return _PdfPaneWithScrollbar(
|
||||
key: key,
|
||||
documentFuture: doc,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
final dialogW = size.width * 0.92;
|
||||
final dialogH = size.height * 0.88;
|
||||
|
||||
return Dialog(
|
||||
insetPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 20),
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(_cornerRadius),
|
||||
child: Material(
|
||||
color: _ModaleCouleur.ivoire,
|
||||
elevation: 6,
|
||||
shadowColor: _ModaleCouleur.encre.withValues(alpha: 0.12),
|
||||
child: SizedBox(
|
||||
width: dialogW,
|
||||
height: dialogH,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildHeader(context),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 8, 12, 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_tabButton(
|
||||
context,
|
||||
label: 'CGU',
|
||||
selected: _selectedDocType == LegalDocType.cgu,
|
||||
enabled: !_loadingLegalDocs && _cguDoc != null,
|
||||
onTap: () => setState(
|
||||
() => _selectedDocType = LegalDocType.cgu,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
_tabButton(
|
||||
context,
|
||||
label: 'Confidentialité',
|
||||
selected: _selectedDocType == LegalDocType.privacy,
|
||||
enabled: !_loadingLegalDocs && _privacyDoc != null,
|
||||
onTap: () => setState(
|
||||
() => _selectedDocType = LegalDocType.privacy,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 4, 12, 8),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: _ModaleCouleur.turquoiseFonce.withValues(
|
||||
alpha: 0.35,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: _loadingLegalDocs
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: _ModaleCouleur.turquoiseFonce,
|
||||
strokeWidth: 3,
|
||||
),
|
||||
)
|
||||
: _currentPdfDocumentFuture != null
|
||||
? _buildPdfBody()
|
||||
: Center(
|
||||
child: Text(
|
||||
'Document non disponible.',
|
||||
style: GoogleFonts.merienda(
|
||||
color: _ModaleCouleur.encre
|
||||
.withValues(alpha: 0.75),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 520),
|
||||
child: FilledButton(
|
||||
onPressed: _canValidate
|
||||
? () => Navigator.of(context).pop(true)
|
||||
: null,
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: _ModaleCouleur.turquoiseFonce,
|
||||
foregroundColor: Colors.white,
|
||||
disabledBackgroundColor:
|
||||
_ModaleCouleur.mentheClair.withValues(alpha: 0.7),
|
||||
disabledForegroundColor:
|
||||
_ModaleCouleur.encre.withValues(alpha: 0.45),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 14,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'Valider les CGU et la Politique '
|
||||
'de Confidentialité',
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.merienda(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
_ModaleCouleur.mentheTresClair,
|
||||
_ModaleCouleur.mentheClair,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: Text(
|
||||
_dialogTitle,
|
||||
style: GoogleFonts.merienda(
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: _ModaleCouleur.encre,
|
||||
height: 1.25,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: 'Fermer',
|
||||
icon: Icon(
|
||||
Icons.close,
|
||||
color: _ModaleCouleur.encre.withValues(alpha: 0.55),
|
||||
),
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _tabButton(
|
||||
BuildContext context, {
|
||||
required String label,
|
||||
required bool selected,
|
||||
required bool enabled,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return OutlinedButton(
|
||||
onPressed: enabled ? onTap : null,
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: _ModaleCouleur.encre,
|
||||
backgroundColor: selected
|
||||
? _ModaleCouleur.turquoise
|
||||
: _ModaleCouleur.mentheTresClair,
|
||||
side: BorderSide(
|
||||
color: _ModaleCouleur.turquoiseFonce.withValues(alpha: 0.55),
|
||||
width: 1.5,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 10),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: GoogleFonts.merienda(
|
||||
fontSize: 13,
|
||||
fontWeight: selected ? FontWeight.w600 : FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// [PdfViewPinch] s’appuie sur un [InteractiveViewer], sans vrai [Scrollable] :
|
||||
/// barre verticale reliée à [PdfControllerPinch.documentProgress] pour que le
|
||||
/// « chariot » reste visible (surtout sur le web où la barre OS est souvent
|
||||
/// masquée).
|
||||
class _PdfPaneWithScrollbar extends StatefulWidget {
|
||||
const _PdfPaneWithScrollbar({
|
||||
super.key,
|
||||
required this.documentFuture,
|
||||
});
|
||||
|
||||
final Future<PdfDocument> documentFuture;
|
||||
|
||||
@override
|
||||
State<_PdfPaneWithScrollbar> createState() => _PdfPaneWithScrollbarState();
|
||||
}
|
||||
|
||||
class _PdfPaneWithScrollbarState extends State<_PdfPaneWithScrollbar> {
|
||||
static const double _barW = 14;
|
||||
static const double _thumbW = 9;
|
||||
static const double _minThumbH = 44;
|
||||
|
||||
late final PdfControllerPinch _ctrl;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_ctrl = PdfControllerPinch(document: widget.documentFuture);
|
||||
_ctrl.addListener(_onCtrl);
|
||||
}
|
||||
|
||||
void _onCtrl() {
|
||||
// [PdfViewPinch] met à jour documentProgress dans son propre listener :
|
||||
// replanifier pour lire la valeur après ce tour de notifications.
|
||||
Future.microtask(() {
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ctrl.removeListener(_onCtrl);
|
||||
_ctrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final raw = _ctrl.documentProgress;
|
||||
final safeP = (!raw.isFinite || raw.isNaN) ? 0.0 : raw.clamp(0.0, 1.0);
|
||||
|
||||
return Stack(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: _barW),
|
||||
child: PdfViewPinch(
|
||||
padding: 10,
|
||||
scrollDirection: Axis.vertical,
|
||||
minScale: 1,
|
||||
maxScale: 4,
|
||||
controller: _ctrl,
|
||||
backgroundDecoration: const BoxDecoration(color: Colors.white),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 6,
|
||||
right: 1,
|
||||
bottom: 6,
|
||||
width: _barW,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, c) {
|
||||
final trackH = c.maxHeight;
|
||||
final thumbH = (trackH * 0.2).clamp(_minThumbH, trackH * 0.5);
|
||||
final maxTop = (trackH - thumbH).clamp(0.0, double.infinity);
|
||||
final top = safeP * maxTop;
|
||||
|
||||
return Semantics(
|
||||
label: 'Position dans le document',
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: _ModaleCouleur.turquoise.withValues(
|
||||
alpha: 0.35,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: top,
|
||||
left: (_barW - _thumbW) / 2,
|
||||
child: Container(
|
||||
width: _thumbW,
|
||||
height: thumbH,
|
||||
decoration: BoxDecoration(
|
||||
color: _ModaleCouleur.turquoiseFonce,
|
||||
borderRadius: BorderRadius.circular(_thumbW / 2),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x40000000),
|
||||
blurRadius: 3,
|
||||
offset: Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'custom_decorated_text_field.dart';
|
||||
import 'app_custom_checkbox.dart';
|
||||
@ -9,6 +10,7 @@ import 'custom_navigation_button.dart';
|
||||
import 'hover_relief_widget.dart';
|
||||
import '../models/card_assets.dart';
|
||||
import '../config/display_config.dart';
|
||||
import 'cgu_privacy_validation_dialog.dart';
|
||||
|
||||
/// Widget générique pour le formulaire de présentation avec texte libre + CGU
|
||||
/// Supporte mode éditable et readonly, responsive mobile/desktop
|
||||
@ -75,6 +77,52 @@ class _PresentationFormScreenState extends State<PresentationFormScreen> {
|
||||
widget.onSubmit(_textController.text, _cguAccepted);
|
||||
}
|
||||
|
||||
Future<void> _openCguValidationDialog() async {
|
||||
final validated = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => const CguPrivacyValidationDialog(),
|
||||
);
|
||||
|
||||
if (validated == true && mounted) {
|
||||
setState(() => _cguAccepted = true);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildCguValidationControl({
|
||||
required double fontSize,
|
||||
bool compact = false,
|
||||
}) {
|
||||
final label = compact
|
||||
? 'J’ai validé les CGU et la\nPolitique de confidentialité'
|
||||
: 'J’ai validé les Conditions Générales\nd’Utilisation et la Politique de confidentialité';
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Checkbox(
|
||||
value: _cguAccepted,
|
||||
onChanged: null,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
style: GoogleFonts.merienda(fontSize: fontSize),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
TextButton.icon(
|
||||
onPressed: _openCguValidationDialog,
|
||||
icon: const Icon(Icons.open_in_new, size: 18),
|
||||
label: const Text('Consulter et valider les CGU'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenSize = MediaQuery.of(context).size;
|
||||
@ -451,11 +499,13 @@ class _PresentationFormScreenState extends State<PresentationFormScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
AppCustomCheckbox(
|
||||
config.isReadonly
|
||||
? AppCustomCheckbox(
|
||||
label: 'J\'accepte les Conditions Générales\nd\'Utilisation et la Politique de confidentialité',
|
||||
value: _cguAccepted,
|
||||
onChanged: config.isReadonly ? (v) {} : (value) => setState(() => _cguAccepted = value ?? false),
|
||||
),
|
||||
onChanged: (v) {},
|
||||
)
|
||||
: _buildCguValidationControl(fontSize: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -507,11 +557,16 @@ class _PresentationFormScreenState extends State<PresentationFormScreen> {
|
||||
const SizedBox(height: 16),
|
||||
// Checkbox en bas
|
||||
Transform.scale(
|
||||
scale: 0.85,
|
||||
child: AppCustomCheckbox(
|
||||
scale: 0.95,
|
||||
child: config.isReadonly
|
||||
? AppCustomCheckbox(
|
||||
label: 'J\'accepte les CGU et la\nPolitique de confidentialité',
|
||||
value: _cguAccepted,
|
||||
onChanged: config.isReadonly ? (v) {} : (value) => setState(() => _cguAccepted = value ?? false),
|
||||
onChanged: (v) {},
|
||||
)
|
||||
: _buildCguValidationControl(
|
||||
fontSize: 14,
|
||||
compact: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -549,6 +604,10 @@ class _PresentationFormScreenState extends State<PresentationFormScreen> {
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: HoverReliefWidget(
|
||||
child: Opacity(
|
||||
opacity: _cguAccepted ? 1 : 0.55,
|
||||
child: AbsorbPointer(
|
||||
absorbing: !_cguAccepted,
|
||||
child: CustomNavigationButton(
|
||||
text: 'Suivant',
|
||||
style: NavigationButtonStyle.green,
|
||||
@ -559,6 +618,8 @@ class _PresentationFormScreenState extends State<PresentationFormScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@ -57,6 +57,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
extension:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: extension
|
||||
sha256: be3a6b7f8adad2f6e2e8c63c895d19811fcf203e23466c6296267941d0ff4f24
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -113,6 +121,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+4"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -397,6 +413,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
pdfx:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: pdfx
|
||||
sha256: "29db9b71d46bf2335e001f91693f2c3fbbf0760e4c2eb596bf4bafab211471c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.9.2"
|
||||
photo_view:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: photo_view
|
||||
sha256: "1fc3d970a91295fbd1364296575f854c9863f225505c28c46e0a03e48960c75e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.15.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -514,6 +546,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -538,6 +578,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
universal_platform:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: universal_platform
|
||||
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -602,6 +650,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.3"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -20,6 +20,8 @@ dependencies:
|
||||
url_launcher: ^6.2.4
|
||||
http: ^1.2.2
|
||||
# flutter_secure_storage: ^9.0.0
|
||||
pdfx: ^2.5.0
|
||||
universal_platform: ^1.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -32,6 +34,7 @@ flutter:
|
||||
assets:
|
||||
- assets/images/ # Déclarer le dossier entier
|
||||
- assets/cards/ # Nouveau dossier de cartes
|
||||
- assets/documents/
|
||||
|
||||
fonts:
|
||||
- family: Merienda
|
||||
|
||||
@ -47,6 +47,16 @@
|
||||
<script src="flutter.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src='https://cdn.jsdelivr.net/npm/pdfjs-dist@4.6.82/build/pdf.min.mjs' type='module'></script>
|
||||
<script type='module'>
|
||||
var { pdfjsLib } = globalThis;
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@4.6.82/build/pdf.worker.mjs';
|
||||
|
||||
var pdfRenderOptions = {
|
||||
cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@4.6.82/cmaps/',
|
||||
cMapPacked: true,
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
window.addEventListener('load', function(ev) {
|
||||
// Download main.dart.js
|
||||
|
||||
@ -18,15 +18,15 @@ fi
|
||||
if [ -z "$GITEA_TOKEN" ] && [ -f ~/.bashrc ]; then
|
||||
eval "$(grep '^export GITEA_TOKEN=' ~/.bashrc 2>/dev/null)" || true
|
||||
fi
|
||||
if [ -z "$GITEA_TOKEN" ] && [ -f docs/BRIEFING-FRONTEND.md ]; then
|
||||
token_from_briefing=$(sed -n 's/.*Token: *\(giteabu_[a-f0-9]*\).*/\1/p' docs/BRIEFING-FRONTEND.md 2>/dev/null | head -1)
|
||||
if [ -z "$GITEA_TOKEN" ] && [ -f docs/27_BRIEFING-FRONTEND.md ]; then
|
||||
token_from_briefing=$(sed -n 's/.*Token: *\(giteabu_[a-f0-9]*\).*/\1/p' docs/27_BRIEFING-FRONTEND.md 2>/dev/null | head -1)
|
||||
if [ -n "$token_from_briefing" ]; then
|
||||
GITEA_TOKEN="$token_from_briefing"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$GITEA_TOKEN" ]; then
|
||||
echo "Définir GITEA_TOKEN ou créer .gitea-token avec votre token Gitea (voir docs/PROCEDURE-API-GITEA.md)."
|
||||
echo "Définir GITEA_TOKEN ou créer .gitea-token avec votre token Gitea (voir docs/26_GITEA-API.md)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user