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