feat(frontend): #50 — validation CGU et politique de confidentialité
- Modale de validation avec chargement des PDF distants, PdfViewPinch et barre de progression latérale (repli PdfView sous Windows). - Service documents légaux actifs, correction des URLs média, intégration au formulaire de présentation. - Documentation juridique et réorganisation (archive, index, tickets). Made-with: Cursor
This commit is contained in:
parent
241ce36b85
commit
7381ce356d
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Commentaire de clôture + fermeture issue Gitea #120.
|
* Commentaire de clôture + fermeture issue Gitea #120.
|
||||||
* Usage: node backend/scripts/close-gitea-issue-120.js
|
* 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 https = require('https');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@ -45,7 +45,10 @@ if (!token) {
|
|||||||
}
|
}
|
||||||
if (!token) {
|
if (!token) {
|
||||||
try {
|
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]+)/);
|
const m = briefing.match(/Token:\s*(giteabu_[a-f0-9]+)/);
|
||||||
if (m) token = m[1].trim();
|
if (m) token = m[1].trim();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Crée l'issue Gitea "[Frontend] Inscription Parent – Branchement soumission formulaire à l'API"
|
* Crée l'issue Gitea "[Frontend] Inscription Parent – Branchement soumission formulaire à l'API"
|
||||||
* Usage: node backend/scripts/create-gitea-issue-parent-api.js
|
* 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 https = require('https');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@ -19,13 +20,18 @@ if (!token) {
|
|||||||
}
|
}
|
||||||
if (!token) {
|
if (!token) {
|
||||||
try {
|
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]+)/);
|
const m = briefing.match(/Token:\s*(giteabu_[a-f0-9]+)/);
|
||||||
if (m) token = m[1].trim();
|
if (m) token = m[1].trim();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
if (!token) {
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Liste toutes les issues Gitea (ouvertes + fermées) pour jmartin/petitspas.
|
* 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 https = require('https');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@ -16,7 +16,10 @@ if (!token) {
|
|||||||
}
|
}
|
||||||
if (!token) {
|
if (!token) {
|
||||||
try {
|
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]+)/);
|
const m = briefing.match(/Token:\s*(giteabu_[a-f0-9]+)/);
|
||||||
if (m) token = m[1].trim();
|
if (m) token = m[1].trim();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Met à jour l'issue Gitea #119 : endpoint unifié GET /dossiers/:numeroDossier (option A)
|
* Met à jour l'issue Gitea #119 : endpoint unifié GET /dossiers/:numeroDossier (option A)
|
||||||
* Usage: node backend/scripts/update-gitea-issue-119-dossiers.js
|
* 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 https = require('https');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@ -17,7 +17,10 @@ if (!token) {
|
|||||||
}
|
}
|
||||||
if (!token) {
|
if (!token) {
|
||||||
try {
|
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]+)/);
|
const m = briefing.match(/Token:\s*(giteabu_[a-f0-9]+)/);
|
||||||
if (m) token = m[1].trim();
|
if (m) token = m[1].trim();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|||||||
@ -19,15 +19,39 @@ Ce fichier sert d'index pour naviguer dans toute la documentation du projet.
|
|||||||
### Développement
|
### Développement
|
||||||
- [**10 - Database Schema**](./10_DATABASE.md) - Schéma de la base de données et modèles
|
- [**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
|
- [**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
|
### Workflows Fonctionnels
|
||||||
- [**20 - Workflow Création de Compte**](./20_WORKFLOW-CREATION-COMPTE.md) - Workflow complet de création et validation des comptes utilisateurs
|
- [**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
|
- [**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
|
- [**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
|
- [**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
|
- [**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)
|
- [**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)
|
### Administration (À créer)
|
||||||
- [**30 - Guide d'administration**](./30_ADMIN.md) - Gestion des utilisateurs, accès PgAdmin, logs
|
- [**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)
|
# Fichier déplacé
|
||||||
|
|
||||||
**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é
|
|
||||||
|
|
||||||
|
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é |
|
| 44 | [Frontend] Dashboard Gestionnaire - Structure | ✅ Terminé |
|
||||||
| 45 | [Frontend] Dashboard Gestionnaire - Liste Parents | ✅ Fermé (obsolète, couvert #103-#111) |
|
| 45 | [Frontend] Dashboard Gestionnaire - Liste Parents | ✅ Fermé (obsolète, couvert #103-#111) |
|
||||||
| 46 | [Frontend] Dashboard Gestionnaire - Liste AM | ✅ 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 |
|
| 48 | [Frontend] Gestion Erreurs & Messages | Ouvert |
|
||||||
| 49 | [Frontend] Écran Gestion Documents Légaux (Admin) | Ouvert |
|
| 49 | [Frontend] Écran Gestion Documents Légaux (Admin) | Ouvert |
|
||||||
| 50 | [Frontend] Affichage dynamique CGU lors inscription | 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
|
- [ ] Créer migration Prisma
|
||||||
- [ ] Tester migration
|
- [ ] 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()`
|
- [ ] Méthode `listerVersions()`
|
||||||
- [ ] Tests unitaires
|
- [ ] 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
|
- [ ] Guards + validation
|
||||||
- [ ] Tests unitaires
|
- [ ] 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)
|
- [ ] API `GET /api/v1/users/:id/acceptations` (admin)
|
||||||
- [ ] Tests unitaires
|
- [ ] 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
|
- [ ] Activation version
|
||||||
- [ ] Historique versions
|
- [ ] 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).
|
**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
|
- Activation manuelle après prévisualisation
|
||||||
- Documents génériques v1 fournis par défaut
|
- 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
|
├── 11_API.md
|
||||||
├── 20_WORKFLOW-CREATION-COMPTE.md
|
├── 20_WORKFLOW-CREATION-COMPTE.md
|
||||||
├── 21_CONFIGURATION-SYSTEME.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
|
├── 23_LISTE-TICKETS.md
|
||||||
├── 24_DECISIONS-PROJET.md (ce document)
|
├── 24_DECISIONS-PROJET.md (ce document)
|
||||||
├── 90_AUDIT.md
|
├── 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
|
**[26_GITEA-API.md](./26_GITEA-API.md)**
|
||||||
- **API de base** : `https://git.ptits-pas.fr/api/v1`
|
|
||||||
- **Projet P'titsPas** : dépôt `jmartin/petitspas` (owner = `jmartin`, repo = `petitspas`)
|
|
||||||
|
|
||||||
## 2. Authentification
|
L’ancienne copie `PROCEDURE-API-GITEA.md` est archivée dans
|
||||||
|
`docs/archive/obsolete/` (doublon).
|
||||||
### 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.
|
|
||||||
|
|||||||
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
|
# 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
|
## 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
|
# 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
|
**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 |
|
| `11_API.md` | Endpoints API |
|
||||||
| `20_WORKFLOW-CREATION-COMPTE.md` | Workflow création compte |
|
| `20_WORKFLOW-CREATION-COMPTE.md` | Workflow création compte |
|
||||||
| `23_LISTE-TICKETS.md` | Liste des tickets |
|
| `23_LISTE-TICKETS.md` | Liste des tickets |
|
||||||
| `BRIEFING-FRONTEND.md` | Brief frontend, accès Git, tickets prioritaires |
|
| `27_BRIEFING-FRONTEND.md` | Brief frontend, accès Git, tickets prioritaires |
|
||||||
| `PROCEDURE-API-GITEA.md` | Utilisation API Gitea (issues, PR, token) |
|
| `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
|
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)
|
# 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é.
|
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;
|
return u;
|
||||||
}
|
}
|
||||||
final base = baseUrl.replaceAll(RegExp(r'/+$'), '');
|
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';
|
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/api_config.dart';
|
||||||
import 'package:p_tits_pas/services/api/tokenService.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 {
|
class UserService {
|
||||||
static Future<Map<String, String>> _headers() async {
|
static Future<Map<String, String>> _headers() async {
|
||||||
final token = await TokenService.getToken();
|
final token = await TokenService.getToken();
|
||||||
@ -34,6 +67,46 @@ class UserService {
|
|||||||
return _toStr(err) ?? 'Erreur inconnue';
|
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.
|
/// Utilisateurs en attente de validation (GET /users/pending). Ticket #107.
|
||||||
static Future<List<AppUser>> getPendingUsers({String? role}) async {
|
static Future<List<AppUser>> getPendingUsers({String? role}) async {
|
||||||
final query = role != null ? '?role=$role' : '';
|
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:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import 'custom_decorated_text_field.dart';
|
import 'custom_decorated_text_field.dart';
|
||||||
import 'app_custom_checkbox.dart';
|
import 'app_custom_checkbox.dart';
|
||||||
@ -9,6 +10,7 @@ import 'custom_navigation_button.dart';
|
|||||||
import 'hover_relief_widget.dart';
|
import 'hover_relief_widget.dart';
|
||||||
import '../models/card_assets.dart';
|
import '../models/card_assets.dart';
|
||||||
import '../config/display_config.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
|
/// Widget générique pour le formulaire de présentation avec texte libre + CGU
|
||||||
/// Supporte mode éditable et readonly, responsive mobile/desktop
|
/// Supporte mode éditable et readonly, responsive mobile/desktop
|
||||||
@ -75,6 +77,52 @@ class _PresentationFormScreenState extends State<PresentationFormScreen> {
|
|||||||
widget.onSubmit(_textController.text, _cguAccepted);
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final screenSize = MediaQuery.of(context).size;
|
final screenSize = MediaQuery.of(context).size;
|
||||||
@ -451,11 +499,13 @@ class _PresentationFormScreenState extends State<PresentationFormScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
AppCustomCheckbox(
|
config.isReadonly
|
||||||
|
? AppCustomCheckbox(
|
||||||
label: 'J\'accepte les Conditions Générales\nd\'Utilisation et la Politique de confidentialité',
|
label: 'J\'accepte les Conditions Générales\nd\'Utilisation et la Politique de confidentialité',
|
||||||
value: _cguAccepted,
|
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),
|
const SizedBox(height: 16),
|
||||||
// Checkbox en bas
|
// Checkbox en bas
|
||||||
Transform.scale(
|
Transform.scale(
|
||||||
scale: 0.85,
|
scale: 0.95,
|
||||||
child: AppCustomCheckbox(
|
child: config.isReadonly
|
||||||
|
? AppCustomCheckbox(
|
||||||
label: 'J\'accepte les CGU et la\nPolitique de confidentialité',
|
label: 'J\'accepte les CGU et la\nPolitique de confidentialité',
|
||||||
value: _cguAccepted,
|
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),
|
const SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: HoverReliefWidget(
|
child: HoverReliefWidget(
|
||||||
|
child: Opacity(
|
||||||
|
opacity: _cguAccepted ? 1 : 0.55,
|
||||||
|
child: AbsorbPointer(
|
||||||
|
absorbing: !_cguAccepted,
|
||||||
child: CustomNavigationButton(
|
child: CustomNavigationButton(
|
||||||
text: 'Suivant',
|
text: 'Suivant',
|
||||||
style: NavigationButtonStyle.green,
|
style: NavigationButtonStyle.green,
|
||||||
@ -559,6 +618,8 @@ class _PresentationFormScreenState extends State<PresentationFormScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -57,6 +57,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.6"
|
version: "3.0.6"
|
||||||
|
extension:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: extension
|
||||||
|
sha256: be3a6b7f8adad2f6e2e8c63c895d19811fcf203e23466c6296267941d0ff4f24
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.0"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -113,6 +121,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3+4"
|
version: "0.9.3+4"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -397,6 +413,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
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:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -514,6 +546,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.1"
|
||||||
|
synchronized:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: synchronized
|
||||||
|
sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.3.1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -538,6 +578,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
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:
|
url_launcher:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -602,6 +650,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.4"
|
version: "3.1.4"
|
||||||
|
uuid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.3"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -20,6 +20,8 @@ dependencies:
|
|||||||
url_launcher: ^6.2.4
|
url_launcher: ^6.2.4
|
||||||
http: ^1.2.2
|
http: ^1.2.2
|
||||||
# flutter_secure_storage: ^9.0.0
|
# flutter_secure_storage: ^9.0.0
|
||||||
|
pdfx: ^2.5.0
|
||||||
|
universal_platform: ^1.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@ -32,6 +34,7 @@ flutter:
|
|||||||
assets:
|
assets:
|
||||||
- assets/images/ # Déclarer le dossier entier
|
- assets/images/ # Déclarer le dossier entier
|
||||||
- assets/cards/ # Nouveau dossier de cartes
|
- assets/cards/ # Nouveau dossier de cartes
|
||||||
|
- assets/documents/
|
||||||
|
|
||||||
fonts:
|
fonts:
|
||||||
- family: Merienda
|
- family: Merienda
|
||||||
|
|||||||
@ -47,6 +47,16 @@
|
|||||||
<script src="flutter.js" defer></script>
|
<script src="flutter.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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>
|
<script>
|
||||||
window.addEventListener('load', function(ev) {
|
window.addEventListener('load', function(ev) {
|
||||||
// Download main.dart.js
|
// Download main.dart.js
|
||||||
|
|||||||
@ -18,15 +18,15 @@ fi
|
|||||||
if [ -z "$GITEA_TOKEN" ] && [ -f ~/.bashrc ]; then
|
if [ -z "$GITEA_TOKEN" ] && [ -f ~/.bashrc ]; then
|
||||||
eval "$(grep '^export GITEA_TOKEN=' ~/.bashrc 2>/dev/null)" || true
|
eval "$(grep '^export GITEA_TOKEN=' ~/.bashrc 2>/dev/null)" || true
|
||||||
fi
|
fi
|
||||||
if [ -z "$GITEA_TOKEN" ] && [ -f docs/BRIEFING-FRONTEND.md ]; then
|
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/BRIEFING-FRONTEND.md 2>/dev/null | head -1)
|
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
|
if [ -n "$token_from_briefing" ]; then
|
||||||
GITEA_TOKEN="$token_from_briefing"
|
GITEA_TOKEN="$token_from_briefing"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$GITEA_TOKEN" ]; then
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user