Feat/ci cd securisation infcdaal3 #1

Merged
sam merged 6 commits from feat/ci-cd-securisation-infcdaal3 into main 2026-07-08 16:47:05 +00:00
15 changed files with 1766 additions and 0 deletions
Showing only changes of commit 89b4107266 - Show all commits
Binary file not shown.
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
# Journal des modifications — CESIZen
Toutes les évolutions notables du projet sont consignées dans ce fichier.
Le format s'appuie sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/)
et le versionnage suit [SemVer](https://semver.org/lang/fr/).
## [Non publié]
### Ajouté
- Pipeline d'intégration continue Gitea Actions (tests, Pint, audit de sécurité).
- Pipeline de déploiement continu (build image → registre Gitea → webhook Portainer).
- Middleware `SecurityHeaders` (HSTS, CSP, X-Frame-Options, etc.).
- Modèle de configuration `.env.production.example` durci.
- Modèles de tickets Gitea (incident / évolution) et modèle de Pull Request.
- Politique de sécurité (`SECURITY.md`).
### Sécurité
- Confiance au reverse proxy (`trustProxies`) pour la détection HTTPS.
- Chiffrement des sessions et cookies `Secure` / `HttpOnly` en production.
## [1.0.0] - 2025-05-20
### Ajouté
- Application Web Laravel (diagnostic de stress, tracker émotionnel, relaxation, informations).
- Application mobile Flutter (iOS / Android).
- Application Wear OS (Kotlin / Jetpack Compose).
- Administration Filament et API REST (Sanctum).
Binary file not shown.
+456
View File
@@ -0,0 +1,456 @@
# Documentation Technique — CESIZen
## Table des matières
1. [Présentation du projet](#1-présentation-du-projet)
2. [Architecture globale](#2-architecture-globale)
3. [Application Web (Laravel)](#3-application-web-laravel)
- [Stack technique](#31-stack-technique)
- [Structure des dossiers](#32-structure-des-dossiers)
- [Modèles de données](#33-modèles-de-données)
- [Routes & Contrôleurs Web](#34-routes--contrôleurs-web)
- [API REST](#35-api-rest)
- [Administration Filament](#36-administration-filament)
- [Services](#37-services)
4. [Application Mobile (Flutter)](#4-application-mobile-flutter)
- [Stack technique](#41-stack-technique)
- [Architecture](#42-architecture)
- [Fonctionnalités](#43-fonctionnalités)
5. [Application Wear OS](#5-application-wear-os)
- [Stack technique](#51-stack-technique)
- [Fonctionnement](#52-fonctionnement)
6. [Base de données](#6-base-de-données)
7. [Déploiement](#7-déploiement)
---
## 1. Présentation du projet
**CESIZen** est une application de bien-être mental destinée aux étudiants et collaborateurs CESI. Elle propose :
- Un **diagnostic de stress** basé sur l'échelle Holmes & Rahe (événements de vie stressants cotés en points)
- Un **tracker émotionnel** pour enregistrer et suivre son humeur au quotidien
- Des **activités de relaxation** (méditation, respiration, yoga, etc.) avec gestion des favoris
- Des **informations / articles** bien-être publiés par les administrateurs
- Une **application Wear OS** pour enregistrer son humeur directement depuis la montre
---
## 2. Architecture globale
```
CESIZen/
├── WEB/ → Backend Laravel + Frontend Blade + API REST + Admin Filament
├── mobile/ → Application Flutter (iOS & Android)
└── Wear/ → Application Wear OS (Kotlin/Jetpack Compose)
```
La communication entre les composants :
```
[Navigateur] ←→ [Laravel WEB (Blade + Sanctum)]
[App Flutter] ←→ [Laravel API REST (Sanctum Token)]
[Montre Wear] ←→ [App Flutter] ←→ [Laravel API REST]
(via Wearable Data Layer API)
```
---
## 3. Application Web (Laravel)
### 3.1 Stack technique
| Élément | Technologie |
|---|---|
| Framework | Laravel 11 |
| Admin Panel | Filament 3 |
| Auth API | Laravel Sanctum |
| Base de données | SQLite (dev) / MySQL (prod) |
| Frontend | Blade + Vite |
| Tests | PHPUnit |
| Conteneurisation | Docker + Nginx |
### 3.2 Structure des dossiers
```
WEB/
├── app/
│ ├── Filament/
│ │ └── Resources/
│ │ └── UserResource/ ← CRUD utilisateurs dans l'admin
│ ├── Http/
│ │ └── Controllers/
│ │ ├── Api/ ← Contrôleurs pour l'API mobile
│ │ │ ├── AuthController.php
│ │ │ ├── EmotionController.php
│ │ │ ├── InformationController.php
│ │ │ └── StressDiagnosticController.php
│ │ └── Web/ ← Contrôleurs pour les pages Blade
│ │ ├── AuthController.php
│ │ ├── DiagnosticController.php
│ │ ├── EmotionController.php
│ │ ├── InformationController.php
│ │ ├── ProfileController.php
│ │ └── RelaxationController.php
│ ├── Models/ ← Modèles Eloquent
│ └── Services/
│ └── StressCalculator.php ← Logique de calcul du score de stress
├── database/
│ ├── migrations/
│ └── seeders/
├── routes/
│ ├── api.php ← Routes API (préfixe /api)
│ └── web.php ← Routes Web (Blade)
├── Dockerfile
└── docker-compose.yml
```
### 3.3 Modèles de données
#### `User`
| Champ | Type | Description |
|---|---|---|
| `id` | int | Clé primaire |
| `name` | string | Nom complet |
| `email` | string | Adresse email (unique) |
| `password` | string | Mot de passe hashé |
| `id_role` | int | FK → `roles` |
| `is_active` | boolean | Compte actif/désactivé |
Relations :
- `role()``BelongsTo(Role)` — rôle de l'utilisateur
- `emotionRecords()``HasMany(EmotionRecord)` — historique émotionnel
- `favoritedActivities()``BelongsToMany(RelaxationActivity)` via table `favorites`
> L'accès au panel Filament est restreint aux utilisateurs avec `is_active = true` ET rôle `Admin`.
#### `Role`
Rôles disponibles : `Admin`, `User`.
#### `StressEvent`
Événements de vie stressants de l'échelle Holmes & Rahe.
| Champ | Type | Description |
|---|---|---|
| `event_name` | string | Nom de l'événement |
| `points` | int | Valeur en points de stress |
#### `ResultatDiag`
Résultat d'un diagnostic de stress effectué par un utilisateur.
Champs : `user_id`, `score` (total des points), `level` (Faible / Modéré / Élevé), `events` (JSON des événements cochés).
#### `EmotionRecord`
Enregistrement d'une émotion quotidienne.
| Champ | Type | Description |
|---|---|---|
| `user_id` | int | FK → `users` |
| `emotion` | string | Nom de l'émotion |
| `intensity` | int | Intensité (15) |
| `note` | string (nullable) | Note libre |
#### `RelaxationActivity`
Activité de relaxation disponible dans le catalogue.
| Champ | Type | Description |
|---|---|---|
| `title` | string | Titre de l'activité |
| `type` | string | Type : méditation, respiration, yoga, etc. |
| `duration` | int | Durée en minutes |
| `url` | string | Lien vers la ressource (vidéo, audio…) |
| `description` | string | Description |
Relations :
- `favoritedBy()``BelongsToMany(User)` via table `favorites`
#### `Information`
Articles bien-être publiés par les administrateurs.
| Champ | Type | Description |
|---|---|---|
| `title` | string | Titre de l'article |
| `content` | text | Contenu |
| `category` | string | Catégorie |
| `image_url` | string (nullable) | URL de l'image |
| `is_published` | boolean | Visibilité publique |
### 3.4 Routes & Contrôleurs Web
#### Routes publiques (sans authentification)
| Méthode | URL | Contrôleur | Description |
|---|---|---|---|
| GET | `/` | `DashboardController@index` | Page d'accueil |
| GET | `/diagnostics` | `DiagnosticController@index` | Formulaire de diagnostic |
| GET | `/relaxation` | `RelaxationController@index` | Catalogue d'activités |
| GET | `/informations` | `InformationController@index` | Liste des articles |
| GET | `/informations/{id}` | `InformationController@show` | Détail d'un article |
| GET | `/login` | `AuthController@showLogin` | Page de connexion |
| POST | `/login` | `AuthController@login` | Traitement connexion |
| GET | `/register` | `AuthController@showRegister` | Page d'inscription |
| POST | `/register` | `AuthController@register` | Traitement inscription |
| POST | `/logout` | `AuthController@logout` | Déconnexion |
#### Routes protégées (middleware `auth`)
| Méthode | URL | Contrôleur | Description |
|---|---|---|---|
| GET | `/profile` | `ProfileController@index` | Page profil |
| PATCH | `/profile` | `ProfileController@update` | Mise à jour infos |
| PUT | `/profile/password` | `ProfileController@updatePassword` | Changement de mot de passe |
| DELETE | `/profile` | `ProfileController@destroy` | Suppression du compte |
| GET | `/emotions` | `EmotionController@index` | Historique émotions |
| POST | `/emotions` | `EmotionController@store` | Enregistrer une émotion |
| GET | `/diagnostics/history` | `DiagnosticController@history` | Historique diagnostics |
| POST | `/diagnostics` | `DiagnosticController@store` | Soumettre un diagnostic |
| POST | `/relaxation/{id}/favorite` | `RelaxationController@toggleFavorite` | Ajouter/retirer un favori |
### 3.5 API REST
Préfixe : `/api` — Authentification : **Laravel Sanctum** (Bearer Token)
#### Routes publiques
| Méthode | URL | Description |
|---|---|---|
| POST | `/api/login` | Connexion — retourne un token Sanctum |
| POST | `/api/register` | Inscription d'un nouvel utilisateur |
#### Routes protégées (middleware `auth:sanctum`)
| Méthode | URL | Description |
|---|---|---|
| GET | `/api/profile` | Récupère le profil de l'utilisateur connecté |
| POST | `/api/update-profile` | Met à jour le profil |
| GET | `/api/stress-events` | Liste tous les événements de stress (Holmes & Rahe) |
| POST | `/api/stress-diagnostics` | Enregistre un diagnostic de stress |
| GET | `/api/emotions` | Historique émotionnel de l'utilisateur |
| POST | `/api/emotions` | Enregistre une émotion |
| GET | `/api/informations` | Liste des articles publiés |
| GET | `/api/informations/{id}` | Détail d'un article |
### 3.6 Administration Filament
Le panel d'administration est accessible à `/admin` uniquement pour les utilisateurs avec le rôle `Admin` et `is_active = true`.
**Ressource disponible :**
- **UserResource** : gestion complète des utilisateurs (CRUD, activation/désactivation, assignation de rôle)
### 3.7 Services
#### `StressCalculator`
Contient la logique métier du diagnostic de stress (échelle Holmes & Rahe).
```php
$calculator = new StressCalculator();
$score = $calculator->calculateScore($points); // somme des points des événements cochés
$level = $calculator->determineLevel($score); // 'Faible', 'Modéré', ou 'Élevé'
```
Seuils :
- **< 150 points** → Niveau Faible
- **150299 points** → Niveau Modéré
- **≥ 300 points** → Niveau Élevé
---
## 4. Application Mobile (Flutter)
### 4.1 Stack technique
| Élément | Technologie |
|---|---|
| Framework | Flutter 3 / Dart |
| State management | Provider |
| Navigation | go_router |
| Requêtes HTTP | Dio |
| Stockage sécurisé | flutter_secure_storage |
| Internationalisation | intl |
### 4.2 Architecture
L'application suit une architecture feature-first avec séparation claire des responsabilités :
```
mobile/lib/
├── core/
│ └── network/ ← Client HTTP Dio + intercepteurs (token)
├── features/
│ ├── auth/ ← Connexion / Inscription
│ │ ├── models/
│ │ ├── providers/ ← AuthProvider (gère le token Sanctum)
│ │ ├── screens/
│ │ └── services/ ← Appels API auth
│ ├── diagnostics/ ← Diagnostic de stress
│ │ ├── models/
│ │ ├── providers/
│ │ ├── screens/
│ │ └── services/
│ ├── exercises/ ← Activités de relaxation
│ │ ├── models/
│ │ ├── providers/
│ │ ├── screens/
│ │ └── services/
│ ├── informations/ ← Articles bien-être
│ │ ├── models/
│ │ ├── providers/
│ │ ├── screens/
│ │ └── services/
│ ├── relaxation/ ← Catalogue relaxation + favoris
│ │ ├── models/
│ │ ├── providers/
│ │ ├── screens/
│ │ └── services/
│ ├── tracker/ ← Tracker émotionnel (+ sync Wear OS)
│ │ ├── models/
│ │ ├── providers/
│ │ ├── screens/
│ │ └── services/
│ └── wear/ ← Communication Wearable Data Layer
│ ├── models/
│ ├── providers/
│ ├── screens/
│ └── services/
└── main.dart
```
### 4.3 Fonctionnalités
| Fonctionnalité | Description |
|---|---|
| **Authentification** | Connexion / inscription avec stockage sécurisé du token |
| **Diagnostic de stress** | Questionnaire Holmes & Rahe, calcul du score, historique |
| **Tracker émotionnel** | Enregistrement quotidien de l'humeur avec intensité et note |
| **Relaxation** | Catalogue d'activités filtrables, gestion des favoris |
| **Informations** | Lecture des articles bien-être publiés |
| **Sync Wear OS** | Transmission de l'humeur et du statut d'auth vers la montre |
**Flux d'authentification :**
1. L'utilisateur se connecte → le token Sanctum est stocké dans `flutter_secure_storage`
2. `AuthProvider` expose l'état de connexion à toute l'app via `Provider`
3. `go_router` redirige automatiquement selon l'état d'authentification
---
## 5. Application Wear OS
### 5.1 Stack technique
| Élément | Technologie |
|---|---|
| Langage | Kotlin |
| UI | Jetpack Compose for Wear OS |
| Communication | Wearable Data Layer API (Google) |
| Composants UI | `androidx.wear.compose.material3` |
### 5.2 Fonctionnement
L'application Wear OS est **dépendante de l'app Flutter** installée sur le téléphone couplé. Elle ne communique pas directement avec l'API Laravel.
**Architecture de communication :**
```
[Wear OS App] ←──── Wearable Data Layer (Bluetooth/Wi-Fi) ────→ [App Flutter]
[API Laravel REST]
```
**Canal de communication :**
- Chemin `/wearable_communication` : messages envoyés de la montre vers le téléphone (commandes)
- Chemin `/auth_status` : données synchronisées téléphone → montre (nom d'utilisateur, statut)
- Chemin `/daily_mood` : données synchronisées téléphone → montre (humeur du jour)
**Commandes supportées :**
| Commande | Description |
|---|---|
| `get_sync_data` | Demande au téléphone de synchroniser les données (auth + humeur) |
| `save_mood` | Envoie l'humeur sélectionnée sur la montre pour enregistrement via l'API |
**Écran principal (`WearApp`) :**
- Si l'utilisateur **n'est pas connecté** sur le téléphone → affiche "Connexion requise"
- Si connecté et **aucune humeur du jour** → propose 5 choix d'humeur (Très bien, Bien, Neutre, Pas top, Stressé)
- Si une humeur est déjà enregistrée → affiche l'humeur du jour avec option de modification
**Connectivité :**
La montre utilise la priorité réseau suivante : Wi-Fi → Bluetooth (pont vers le téléphone).
---
## 6. Base de données
### Schéma simplifié
```
roles ─────────────────────────── users
id, libelle id, name, email, password, id_role, is_active
┌───────────────┼───────────────────┐
│ │ │
emotion_records resultat_diags favorites
user_id, emotion user_id, score id_user, id_activite
intensity, note level, events (JSON)
relaxation_activities
title, type, duration, url
stress_events information
event_name, points title, content, category, image_url, is_published
```
### Migrations (ordre chronologique)
| Migration | Table créée |
|---|---|
| `000001` | `roles` |
| `000002` | `users` |
| `000003` | `stress_events` |
| `000004` | `relaxation_activities` |
| `000005` | `resultat_diags` |
| `000006` | `emotion_records` |
| `000007` | `personal_access_tokens` (Sanctum) |
| `103734` | `cache`, `sessions` |
| `111910/111917` | Colonne `is_active` sur `users` |
| `141553` | `information` |
---
## 7. Déploiement
### Environnement Docker (WEB)
Le projet Laravel embarque une configuration Docker prête à l'emploi :
```
WEB/
├── Dockerfile ← Image PHP-FPM
├── docker-compose.yml ← Services : app + nginx
└── nginx.conf ← Configuration Nginx
```
**Démarrer l'environnement :**
```bash
cd WEB
docker-compose up -d
php artisan migrate --seed
```
### Application Mobile
```bash
cd mobile
flutter pub get
flutter run
```
Configurer l'URL de l'API dans le fichier de configuration réseau (`lib/core/network/`).
### Application Wear OS
Ouvrir le dossier `Wear/` dans **Android Studio**, puis lancer sur une montre Wear OS physique ou un émulateur.
> La montre doit être couplée à un téléphone Android ayant l'application Flutter installée.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

+168
View File
@@ -0,0 +1,168 @@
{
"name": "CESIZen",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"pptxgenjs": "^4.0.1"
}
},
"node_modules/@types/node": {
"version": "22.19.19",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz",
"integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/https": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz",
"integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==",
"license": "ISC"
},
"node_modules/image-size": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
"integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
"license": "MIT",
"dependencies": {
"queue": "6.0.2"
},
"bin": {
"image-size": "bin/image-size.js"
},
"engines": {
"node": ">=16.x"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
"node_modules/pptxgenjs": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pptxgenjs/-/pptxgenjs-4.0.1.tgz",
"integrity": "sha512-TeJISr8wouAuXw4C1F/mC33xbZs/FuEG6nH9FG1Zj+nuPcGMP5YRHl6X+j3HSUnS1f3at6k75ZZXPMZlA5Lj9A==",
"license": "MIT",
"dependencies": {
"@types/node": "^22.8.1",
"https": "^1.0.0",
"image-size": "^1.2.1",
"jszip": "^3.10.1"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"license": "MIT",
"dependencies": {
"inherits": "~2.0.3"
}
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT"
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
}
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"dependencies": {
"pptxgenjs": "^4.0.1"
}
}
+862
View File
@@ -0,0 +1,862 @@
const pptxgen = require("pptxgenjs");
const pres = new pptxgen();
pres.layout = "LAYOUT_16x9"; // 10" x 5.625"
pres.author = "Sam DEPARDIEU";
pres.title = "CESIZen — Présentation CDA CESI";
// ─── Palette (strictement calquée sur la réf) ───────────────
const C = {
darkBg: "0D1B2A",
lightBg: "F7F7F2",
teal: "00BF63",
yellow: "FFDE59",
dark: "2D2D2D",
navy: "1B2838",
white: "FFFFFF",
gray: "6B7280",
border: "E5E7EB",
offWhiteAlt: "F0F0EC",
};
// ─── Helpers ────────────────────────────────────────────────
function topBar(s) {
s.addShape(pres.shapes.RECTANGLE, {
x: 0, y: 0, w: 10, h: 0.09,
fill: { color: C.teal }, line: { color: C.teal },
});
}
function vLine(s, x, y, h, color) {
s.addShape(pres.shapes.RECTANGLE, {
x, y, w: 0.07, h,
fill: { color: color || C.teal },
line: { color: color || C.teal },
});
}
function hLine(s, x, y, w, color) {
s.addShape(pres.shapes.RECTANGLE, {
x, y, w, h: 0.02,
fill: { color: color || C.border },
line: { color: color || C.border },
});
}
function tealBar(s, x, y, w) {
s.addShape(pres.shapes.RECTANGLE, {
x, y, w, h: 0.07,
fill: { color: C.teal }, line: { color: C.teal },
});
}
// Entête standard (slides claires)
function header(s, num, section, title) {
topBar(s);
// numéro de slide
s.addText(String(num).padStart(2, "0"), {
x: 9.0, y: 0.11, w: 0.82, h: 0.38,
fontSize: 14, color: C.teal, fontFace: "Calibri",
bold: true, align: "right", margin: 0,
});
// section label
s.addText(section.toUpperCase(), {
x: 0.65, y: 0.13, w: 7, h: 0.26,
fontSize: 9, color: C.gray, fontFace: "Calibri",
charSpacing: 2, margin: 0,
});
// ligne jaune accent
s.addShape(pres.shapes.RECTANGLE, {
x: 0.65, y: 0.44, w: 0.55, h: 0.07,
fill: { color: C.yellow }, line: { color: C.yellow },
});
// titre
s.addText(title, {
x: 0.65, y: 0.52, w: 8.7, h: 0.7,
fontSize: 26, color: C.darkBg, fontFace: "Calibri",
bold: false, margin: 0,
});
hLine(s, 0.65, 1.25, 8.8);
}
// Entête slides sombres
function darkHeader(s, num, section, title) {
topBar(s);
s.addText(String(num).padStart(2, "0"), {
x: 9.0, y: 0.11, w: 0.82, h: 0.38,
fontSize: 14, color: C.teal, fontFace: "Calibri",
bold: true, align: "right", margin: 0,
});
s.addText(section.toUpperCase(), {
x: 0.65, y: 0.13, w: 7, h: 0.26,
fontSize: 9, color: "4B5563", fontFace: "Calibri",
charSpacing: 2, margin: 0,
});
s.addShape(pres.shapes.RECTANGLE, {
x: 0.65, y: 0.44, w: 0.55, h: 0.07,
fill: { color: C.yellow }, line: { color: C.yellow },
});
s.addText(title, {
x: 0.65, y: 0.52, w: 8.7, h: 0.7,
fontSize: 26, color: C.white, fontFace: "Calibri",
bold: false, margin: 0,
});
hLine(s, 0.65, 1.25, 8.8, "374151");
}
// ─── SLIDE 01 — Couverture ──────────────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.darkBg };
topBar(s);
// Accent jaune
s.addShape(pres.shapes.RECTANGLE, {
x: 0.7, y: 0.72, w: 0.55, h: 0.09,
fill: { color: C.yellow }, line: { color: C.yellow },
});
// Titre principal
s.addText("CESIZEN", {
x: 0.7, y: 0.83, w: 8.5, h: 1.45,
fontSize: 56, bold: false, color: C.white,
fontFace: "Calibri", margin: 0,
});
// Sous-titre
s.addText("L'application de votre santé mentale", {
x: 0.7, y: 2.35, w: 8.5, h: 0.5,
fontSize: 18, color: C.teal, fontFace: "Calibri",
italic: false, margin: 0,
});
hLine(s, 0.7, 2.98, 8.6, "374151");
s.addText("Projet CDA CESI — BLOC 2 · Concevoir les solutions logicielles", {
x: 0.7, y: 3.1, w: 8.5, h: 0.38,
fontSize: 12, color: "D1D5DB", fontFace: "Calibri", margin: 0,
});
s.addText("Sam DEPARDIEU", {
x: 0.7, y: 3.52, w: 5, h: 0.38,
fontSize: 13, color: C.yellow, fontFace: "Calibri", margin: 0,
});
s.addText("Mai 2026", {
x: 0.7, y: 3.92, w: 4, h: 0.35,
fontSize: 12, color: C.gray, fontFace: "Calibri", margin: 0,
});
// Panel droit (teal)
s.addShape(pres.shapes.RECTANGLE, {
x: 6.95, y: 0.09, w: 3.05, h: 5.535,
fill: { color: C.teal }, line: { color: C.teal },
});
s.addText("Périmètre", {
x: 7.1, y: 0.28, w: 2.75, h: 0.44,
fontSize: 13, bold: true, color: C.white, fontFace: "Calibri", margin: 0,
});
hLine(s, 7.1, 0.74, 2.7, "00995A");
[
{ label: "WEB", teal: false },
{ label: "Mobile (iOS & Android)",teal: false },
{ label: "API REST", teal: false },
{ label: "Admin dashboard", teal: false },
{ label: "⌚ Surprise...", teal: true },
].forEach((item, i) => {
vLine(s, 7.1, 0.92 + i * 0.82, 0.6, item.teal ? C.yellow : C.white);
s.addText(item.label, {
x: 7.28, y: 1.0 + i * 0.82, w: 2.55, h: 0.44,
fontSize: 12, color: item.teal ? C.yellow : C.white,
fontFace: "Calibri", italic: item.teal, margin: 0,
});
});
}
// ─── SLIDE 02 — Sommaire ────────────────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
topBar(s);
s.addShape(pres.shapes.RECTANGLE, {
x: 0.65, y: 0.37, w: 0.55, h: 0.09,
fill: { color: C.yellow }, line: { color: C.yellow },
});
s.addText("SOMMAIRE", {
x: 0.65, y: 0.47, w: 8, h: 0.88,
fontSize: 34, color: C.darkBg, fontFace: "Calibri", bold: false, margin: 0,
});
hLine(s, 0.65, 1.38, 8.8);
const toc = [
"Contexte & objectifs",
"L'écosystème CESIZen",
"Plateforme WEB (Laravel 12)",
"Application Mobile (Flutter)",
"Architecture technique",
"Modèle de données",
"Tests, sécurité & validation",
"Surprise",
"Bilan & livrables",
];
toc.forEach((item, i) => {
s.addText(String(i + 1).padStart(2, "0"), {
x: 0.65, y: 1.5 + i * 0.45, w: 0.65, h: 0.38,
fontSize: 16, color: C.teal, fontFace: "Calibri", bold: false, margin: 0,
});
s.addText(item, {
x: 1.38, y: 1.53 + i * 0.45, w: 5.0, h: 0.36,
fontSize: 13.5, color: C.dark, fontFace: "Calibri", margin: 0,
});
});
// Panel teal droit
s.addShape(pres.shapes.RECTANGLE, {
x: 6.95, y: 0.09, w: 3.05, h: 5.535,
fill: { color: C.teal }, line: { color: C.teal },
});
s.addText("3 applications", {
x: 7.1, y: 0.3, w: 2.75, h: 0.44,
fontSize: 13, bold: true, color: C.white, fontFace: "Calibri", margin: 0,
});
["WEB", "Mobile", "Wear OS"].forEach((item, i) => {
s.addText("— " + item, {
x: 7.1, y: 0.88 + i * 0.5, w: 2.75, h: 0.4,
fontSize: 12, color: C.white, fontFace: "Calibri", margin: 0,
});
});
hLine(s, 7.1, 2.62, 2.7, "00995A");
s.addText("Évaluation", {
x: 7.1, y: 2.75, w: 2.75, h: 0.38,
fontSize: 12, bold: true, color: C.white, fontFace: "Calibri", margin: 0,
});
[
"20 min de présentation",
"Rapport 15-20 pages",
"30 pts — Grille A/B/C/D",
].forEach((line, i) => {
s.addText(line, {
x: 7.1, y: 3.2 + i * 0.45, w: 2.75, h: 0.38,
fontSize: 11, color: "D1D5DB", fontFace: "Calibri", margin: 0,
});
});
}
// ─── SLIDE 03 — Contexte ────────────────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
header(s, "03", "Contexte du projet", "Contexte & objectifs");
// Bloc gauche : description
vLine(s, 0.55, 1.38, 3.75);
s.addText(
"Commanditaire : Ministère de la Santé et de la Prévention\n" +
"\n" +
"Application dédiée à la gestion du stress et du bien-être mental pour le grand public.\n" +
"\n" +
"Durée du projet : 3 mois de développement individuel\n" +
"\n" +
"Conformité RGPD & protection des données personnelles exigées.",
{
x: 0.78, y: 1.4, w: 5.2, h: 3.7,
fontSize: 12.5, color: C.dark, fontFace: "Calibri", margin: 0,
}
);
// Bloc droit : fonctionnalités
s.addShape(pres.shapes.RECTANGLE, {
x: 6.3, y: 1.38, w: 3.45, h: 4.0,
fill: { color: C.white }, line: { color: C.border, width: 1 },
});
tealBar(s, 6.3, 1.38, 3.45);
s.addText("Fonctionnalités requises", {
x: 6.45, y: 1.55, w: 3.15, h: 0.38,
fontSize: 11.5, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
});
hLine(s, 6.35, 1.94, 3.3);
s.addText("OBLIGATOIRES", {
x: 6.45, y: 2.02, w: 3.15, h: 0.28,
fontSize: 9, bold: true, color: C.teal, fontFace: "Calibri",
charSpacing: 1, margin: 0,
});
[
"Questionnaire de stress",
"Exercices de respiration",
"Tracker d'émotions",
].forEach((item, i) => {
s.addText("— " + item, {
x: 6.5, y: 2.34 + i * 0.38, w: 3.05, h: 0.33,
fontSize: 11.5, color: C.dark, fontFace: "Calibri", margin: 0,
});
});
hLine(s, 6.35, 3.5, 3.3);
s.addText("OPTIONNEL", {
x: 6.45, y: 3.58, w: 3.15, h: 0.28,
fontSize: 9, bold: true, color: C.teal, fontFace: "Calibri",
charSpacing: 1, margin: 0,
});
s.addText("— Module complémentaire", {
x: 6.5, y: 3.9, w: 3.05, h: 0.33,
fontSize: 11.5, color: C.dark, fontFace: "Calibri", margin: 0,
});
// Barre bas
s.addShape(pres.shapes.RECTANGLE, {
x: 0.55, y: 5.22, w: 9.2, h: 0.52,
fill: { color: C.darkBg }, line: { color: C.darkBg },
});
s.addText(
"Rapport écrit (15-20 pages) · Présentation orale 20 min · Notation sur 30 points",
{
x: 0.7, y: 5.24, w: 9.0, h: 0.46,
fontSize: 11, color: "D1D5DB", align: "center",
fontFace: "Calibri", margin: 0, valign: "middle",
}
);
}
// ─── SLIDE 04 — L'écosystème ────────────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
header(s, "04", "Périmètre", "L'écosystème CESIZen");
// 3 colonnes
const cols = [
{
title: "WEB", sub: "Plateforme & API",
items: ["Laravel 12 — MVC", "FilamentPHP (admin)", "API REST Sanctum", "Tailwind CSS + DSFR", "Docker + Nginx"],
mystery: false,
},
{
title: "Mobile", sub: "iOS & Android",
items: ["Flutter (Dart)", "7 modules fonctionnels", "Provider + GoRouter", "DIO + Secure Storage", "Synchronisation montre"],
mystery: false,
},
{
title: "???", sub: "À venir...",
items: ["?", "?", "?", "?", "?"],
mystery: true,
},
];
const cW = 2.9;
cols.forEach((col, i) => {
const cx = 0.55 + i * (cW + 0.2);
tealBar(s, cx, 1.38, cW);
s.addShape(pres.shapes.RECTANGLE, {
x: cx, y: 1.45, w: cW, h: 3.95,
fill: { color: col.mystery ? C.lightBg : C.white },
line: { color: col.mystery ? "D1D5DB" : C.border, width: 1 },
});
s.addText(col.title, {
x: cx + 0.15, y: 1.55, w: cW - 0.3, h: 0.5,
fontSize: 20, bold: true,
color: col.mystery ? "9CA3AF" : C.darkBg,
fontFace: "Calibri", margin: 0,
});
s.addText(col.sub, {
x: cx + 0.15, y: 2.05, w: cW - 0.3, h: 0.3,
fontSize: 11, color: col.mystery ? "9CA3AF" : C.teal,
fontFace: "Calibri", italic: col.mystery, margin: 0,
});
hLine(s, cx + 0.1, 2.4, cW - 0.2);
col.items.forEach((item, ii) => {
s.addText(col.mystery ? "· · · · ·" : "— " + item, {
x: cx + 0.15, y: 2.5 + ii * 0.47, w: cW - 0.3, h: 0.4,
fontSize: 11.5, color: col.mystery ? "D1D5DB" : C.dark,
fontFace: "Calibri", margin: 0,
});
});
});
s.addText(
"Les 3 composantes communiquent via l'API REST — Laravel Sanctum + HTTPS",
{
x: 0.65, y: 5.3, w: 9.0, h: 0.24,
fontSize: 10, color: C.gray, align: "center",
fontFace: "Calibri", margin: 0,
}
);
}
// ─── SLIDE 05 — Plateforme WEB ──────────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
header(s, "05", "Plateforme WEB", "Laravel 12 — Architecture & fonctionnalités");
// Gauche : stack
tealBar(s, 0.55, 1.38, 4.45);
s.addText("Stack technique", {
x: 0.55, y: 1.52, w: 4.45, h: 0.38,
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
});
hLine(s, 0.55, 1.92, 4.45);
[
["Framework", "Laravel 12 — PHP 8.2"],
["Admin", "FilamentPHP 3.2"],
["Auth API", "Laravel Sanctum"],
["Frontend", "Tailwind CSS 4 + Alpine.js"],
["Charte UI", "DSFR (Design Système État)"],
["Base de données", "MySQL 8.0"],
["Déploiement", "Docker + Nginx (Alpine)"],
].forEach(([label, val], i) => {
const ry = 2.0 + i * 0.47;
s.addShape(pres.shapes.RECTANGLE, {
x: 0.55, y: ry, w: 4.45, h: 0.44,
fill: { color: i % 2 === 0 ? C.white : C.offWhiteAlt },
line: { color: C.border, width: 0 },
});
s.addText(label, {
x: 0.7, y: ry + 0.06, w: 1.5, h: 0.3,
fontSize: 11, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
});
s.addText(val, {
x: 2.25, y: ry + 0.06, w: 2.7, h: 0.3,
fontSize: 11, color: C.dark, fontFace: "Calibri", margin: 0,
});
});
// Droite : fonctionnalités
tealBar(s, 5.3, 1.38, 4.4);
s.addText("Fonctionnalités", {
x: 5.3, y: 1.52, w: 4.4, h: 0.38,
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
});
hLine(s, 5.3, 1.92, 4.4);
[
"Authentification sécurisée (Sanctum)",
"Dashboard de visualisation du stress",
"Exercices de respiration (3 modes DSFR)",
"Journal des émotions quotidien",
"Questionnaire diagnostique de stress",
"Articles & informations santé",
"Panel admin — CRUD complet",
].forEach((item, i) => {
vLine(s, 5.3, 2.06 + i * 0.48, 0.36);
s.addText(item, {
x: 5.52, y: 2.09 + i * 0.48, w: 4.1, h: 0.36,
fontSize: 12, color: C.dark, fontFace: "Calibri", margin: 0,
});
});
}
// ─── SLIDE 06 — Application Mobile ─────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
header(s, "06", "Application Mobile", "Flutter — 7 modules fonctionnels");
const modules = [
{ n: "01", title: "Authentification", sub: "Token persistant (Secure Storage)" },
{ n: "02", title: "Tracker émotionnel", sub: "5 niveaux · Journal quotidien" },
{ n: "03", title: "Diagnostic stress", sub: "Questionnaire + historique des résultats" },
{ n: "04", title: "Respiration guidée", sub: "Animation 60 FPS · Cohérence cardiaque" },
{ n: "05", title: "Relaxation", sub: "Bibliothèque d'activités filtrables" },
{ n: "06", title: "Informations santé", sub: "Articles & conseils bien-être" },
{ n: "07", title: "Sync montre", sub: "Wearable Data Layer (bonus)", bonus: true },
];
// 4 gauche + 3 droite
modules.slice(0, 4).forEach((m, i) => {
const my = 1.38 + i * 0.99;
s.addShape(pres.shapes.RECTANGLE, {
x: 0.55, y: my, w: 4.45, h: 0.88,
fill: { color: C.white }, line: { color: C.border, width: 1 },
});
vLine(s, 0.55, my, 0.88);
s.addText(m.n, {
x: 0.72, y: my + 0.07, w: 0.55, h: 0.35,
fontSize: 15, color: C.teal, fontFace: "Calibri", bold: false, margin: 0,
});
s.addText(m.title, {
x: 1.3, y: my + 0.09, w: 3.5, h: 0.35,
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
});
s.addText(m.sub, {
x: 1.3, y: my + 0.49, w: 3.5, h: 0.3,
fontSize: 10.5, color: C.gray, fontFace: "Calibri", margin: 0,
});
});
modules.slice(4).forEach((m, i) => {
const my = 1.38 + i * 0.99;
s.addShape(pres.shapes.RECTANGLE, {
x: 5.25, y: my, w: 4.45, h: 0.88,
fill: { color: m.bonus ? "F7F3E0" : C.white },
line: { color: m.bonus ? "E8DFA0" : C.border, width: 1 },
});
vLine(s, 5.25, my, 0.88, m.bonus ? C.yellow : C.teal);
s.addText(m.n, {
x: 5.42, y: my + 0.07, w: 0.55, h: 0.35,
fontSize: 15, color: m.bonus ? C.yellow : C.teal,
fontFace: "Calibri", bold: false, margin: 0,
});
s.addText(m.title, {
x: 6.0, y: my + 0.09, w: 3.5, h: 0.35,
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
});
s.addText(m.sub, {
x: 6.0, y: my + 0.49, w: 3.5, h: 0.3,
fontSize: 10.5, color: m.bonus ? C.teal : C.gray,
fontFace: "Calibri", italic: m.bonus, margin: 0,
});
});
}
// ─── SLIDE 07 — Architecture technique ─────────────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
header(s, "07", "Architecture technique", "Architecture 3-tiers & choix techniques");
// 3 colonnes
const tiers = [
{ name: "Clients", items: ["Navigateur WEB", "App Mobile Flutter", "Wear OS (Kotlin)"] },
{ name: "Serveur Docker", items: ["Nginx (reverse proxy)", "Laravel 12 / PHP 8.2", "Sanctum (auth tokens)", "FilamentPHP (admin)"] },
{ name: "Données", items: ["MySQL 8.0", "12 modèles Eloquent", "Migrations + Seeders"] },
];
const widths = [2.55, 3.55, 2.95];
let cx = 0.5;
tiers.forEach((tier, i) => {
const tw = widths[i];
tealBar(s, cx, 1.38, tw);
s.addShape(pres.shapes.RECTANGLE, {
x: cx, y: 1.45, w: tw, h: 3.85,
fill: { color: C.white }, line: { color: C.border, width: 1 },
});
s.addText(tier.name, {
x: cx + 0.15, y: 1.55, w: tw - 0.3, h: 0.42,
fontSize: 14, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
});
hLine(s, cx + 0.1, 1.98, tw - 0.2);
tier.items.forEach((item, ii) => {
s.addText("— " + item, {
x: cx + 0.15, y: 2.08 + ii * 0.62, w: tw - 0.3, h: 0.52,
fontSize: 12, color: C.dark, fontFace: "Calibri", margin: 0,
});
});
if (i < tiers.length - 1) {
s.addText("→", {
x: cx + tw + 0.04, y: 2.75, w: 0.2, h: 0.38,
fontSize: 15, color: C.teal, fontFace: "Calibri", align: "center", margin: 0,
});
}
cx += tw + 0.22;
});
// Note bas
s.addText(
"Pattern MVC · API RESTful · Merise / UML · Ports : 8090 (WEB) · 8091 (API)",
{
x: 0.65, y: 5.33, w: 9.0, h: 0.22,
fontSize: 9.5, color: C.gray, align: "center",
fontFace: "Calibri", margin: 0,
}
);
}
// ─── SLIDE 08 — Modèle de données ──────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
header(s, "08", "Modèle de données", "12 entités — Modèle Logique de Données");
const groupsLeft = [
{ domain: "Utilisateurs", models: ["User", "Role"] },
{ domain: "Suivi émotionnel", models: ["EmotionRecord", "StressEvent", "EvenementStress"] },
{ domain: "Diagnostics", models: ["ResultatDiag", "DetailResultat"] },
];
const groupsRight = [
{ domain: "Activités & Relaxation", models: ["RelaxationActivity", "Activite", "Categorie", "Favorite"] },
{ domain: "Informations santé", models: ["Information"] },
];
let y = 1.38;
groupsLeft.forEach((g) => {
const h = 0.44 + g.models.length * 0.36;
vLine(s, 0.55, y, h);
s.addText(g.domain, {
x: 0.77, y: y + 0.05, w: 4.1, h: 0.32,
fontSize: 12, bold: true, color: C.teal, fontFace: "Calibri", margin: 0,
});
g.models.forEach((m, mi) => {
s.addText(m, {
x: 0.77, y: y + 0.4 + mi * 0.35, w: 4.1, h: 0.3,
fontSize: 11.5, color: C.dark, fontFace: "Calibri", margin: 0,
});
});
y += h + 0.22;
});
let y2 = 1.38;
groupsRight.forEach((g) => {
const h = 0.44 + g.models.length * 0.36;
vLine(s, 5.25, y2, h);
s.addText(g.domain, {
x: 5.47, y: y2 + 0.05, w: 4.2, h: 0.32,
fontSize: 12, bold: true, color: C.teal, fontFace: "Calibri", margin: 0,
});
g.models.forEach((m, mi) => {
s.addText(m, {
x: 5.47, y: y2 + 0.4 + mi * 0.35, w: 4.2, h: 0.3,
fontSize: 11.5, color: C.dark, fontFace: "Calibri", margin: 0,
});
});
y2 += h + 0.22;
});
s.addText(
"12 migrations Eloquent · Associations (hasMany, belongsTo, belongsToMany) · Seeders & Factories",
{
x: 0.65, y: 5.33, w: 9.0, h: 0.22,
fontSize: 9.5, color: C.gray, align: "center",
fontFace: "Calibri", margin: 0,
}
);
}
// ─── SLIDE 09 — Tests & Sécurité ───────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
header(s, "09", "Tests & Sécurité", "Validation & conformité");
// Gauche : tests
tealBar(s, 0.55, 1.38, 4.35);
s.addText("Stratégie de tests", {
x: 0.55, y: 1.52, w: 4.35, h: 0.38,
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
});
hLine(s, 0.55, 1.92, 4.35);
[
{ type: "Tests unitaires", tool: "PHPUnit", note: "Controllers & services" },
{ type: "Tests fonctionnels", tool: "PHPUnit", note: "Scénarios end-to-end" },
{ type: "Non-régression", tool: "PHPUnit", note: "Après chaque évolution" },
{ type: "Cahier de tests", tool: "Document", note: "Scénarios détaillés" },
{ type: "PV de recette", tool: "Document", note: "Validation finale" },
].forEach((t, i) => {
const ty = 2.02 + i * 0.62;
vLine(s, 0.55, ty, 0.52);
s.addText(t.type, {
x: 0.77, y: ty + 0.04, w: 2.5, h: 0.28,
fontSize: 12, bold: true, color: C.dark, fontFace: "Calibri", margin: 0,
});
s.addText("[" + t.tool + "] " + t.note, {
x: 0.77, y: ty + 0.3, w: 4.0, h: 0.22,
fontSize: 10, color: C.gray, fontFace: "Calibri", margin: 0,
});
});
// Droite : sécurité
tealBar(s, 5.25, 1.38, 4.45);
s.addText("Sécurité & conformité", {
x: 5.25, y: 1.52, w: 4.45, h: 0.38,
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
});
hLine(s, 5.25, 1.92, 4.45);
[
{ label: "Authentification", val: "Laravel Sanctum — Tokens API" },
{ label: "Chiffrement", val: "Bcrypt (mots de passe)" },
{ label: "CSRF", val: "Middleware Laravel natif" },
{ label: "RGPD", val: "Protection données personnelles" },
{ label: "Stockage mobile", val: "Flutter Secure Storage" },
{ label: "Transport", val: "HTTPS obligatoire en production" },
].forEach((sec, i) => {
const ty = 2.02 + i * 0.55;
s.addShape(pres.shapes.RECTANGLE, {
x: 5.25, y: ty, w: 4.45, h: 0.48,
fill: { color: i % 2 === 0 ? C.white : C.offWhiteAlt },
line: { color: C.border, width: 0 },
});
s.addText(sec.label, {
x: 5.4, y: ty + 0.07, w: 1.7, h: 0.3,
fontSize: 11, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
});
s.addText(sec.val, {
x: 7.15, y: ty + 0.07, w: 2.5, h: 0.3,
fontSize: 11, color: C.dark, fontFace: "Calibri", margin: 0,
});
});
}
// ─── SLIDE 10 — SURPRISE: Wear OS ──────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.darkBg };
darkHeader(s, "10", "Bonus", "Application Wear OS");
s.addText("Un module non demandé — développé en supplément", {
x: 0.65, y: 1.32, w: 9.1, h: 0.35,
fontSize: 13, color: C.teal, fontFace: "Calibri", italic: true, margin: 0,
});
// 4 cartes 2x2
const feats = [
{ title: "Sélection d'humeur", desc: "5 niveaux en 1 clic\nTrès bien → Stressé" },
{ title: "Synchronisation bidirect.", desc: "Temps réel via\nGoogle Wearable Data Layer" },
{ title: "Réinitialisation automatique",desc: "Reset quotidien à minuit\nSuivi journalier indépendant" },
{ title: "Connectivité", desc: "Wi-Fi + Bluetooth\nWear OS 3.0+ · API 30+" },
];
feats.forEach((f, i) => {
const col = i % 2, row = Math.floor(i / 2);
const fx = 0.6 + col * 4.85;
const fy = 1.78 + row * 1.7;
const fw = 4.55;
tealBar(s, fx, fy, fw);
s.addShape(pres.shapes.RECTANGLE, {
x: fx, y: fy + 0.07, w: fw, h: 1.5,
fill: { color: C.navy }, line: { color: "374151", width: 1 },
});
s.addText(f.title, {
x: fx + 0.2, y: fy + 0.17, w: fw - 0.4, h: 0.42,
fontSize: 13.5, bold: true, color: C.white, fontFace: "Calibri", margin: 0,
});
s.addText(f.desc, {
x: fx + 0.2, y: fy + 0.64, w: fw - 0.4, h: 0.82,
fontSize: 12, color: "9CA3AF", fontFace: "Calibri", margin: 0,
});
});
s.addText(
"Kotlin · Jetpack Compose for Wear OS · DataClient / MessageClient / NodeClient",
{
x: 0.65, y: 5.35, w: 9.0, h: 0.22,
fontSize: 9.5, color: "374151", align: "center",
fontFace: "Calibri", margin: 0,
}
);
}
// ─── SLIDE 11 — Bilan & Livrables ──────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
header(s, "11", "Bilan", "Livrables & chiffres clés");
// Gauche : checklist
tealBar(s, 0.55, 1.38, 5.55);
s.addText("Livrables réalisés", {
x: 0.55, y: 1.52, w: 5.55, h: 0.38,
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
});
hLine(s, 0.55, 1.92, 5.55);
[
[false, "Plateforme WEB — Laravel 12 + FilamentPHP"],
[false, "API REST complète (Sanctum + 4 controllers)"],
[false, "Application Mobile Flutter (iOS & Android)"],
[false, "Base de données — 12 modèles Eloquent"],
[false, "Dockerisation (Nginx + PHP + MySQL)"],
[false, "Cahier de tests (unitaires, fonctionnels, NR)"],
[false, "Documentation technique complète"],
[false, "Procès-Verbal de validation (PV)"],
[true, "Application Wear OS — Bonus non demandé"],
].forEach(([isBonus, text], i) => {
s.addText((isBonus ? "⭐" : "✓") + " " + text, {
x: 0.7, y: 2.02 + i * 0.36, w: 5.25, h: 0.32,
fontSize: 11.5,
color: isBonus ? C.teal : C.dark,
bold: isBonus,
fontFace: "Calibri", margin: 0,
});
});
// Droite : chiffres
tealBar(s, 6.35, 1.38, 3.3);
s.addText("Chiffres clés", {
x: 6.35, y: 1.52, w: 3.3, h: 0.38,
fontSize: 13, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0,
});
hLine(s, 6.35, 1.92, 3.3);
[
{ n: "3", l: "Applications\ndéveloppées" },
{ n: "12", l: "Modèles de\nbase de données" },
{ n: "7", l: "Modules\nmobiles" },
{ n: "4", l: "Documents\nlivrés" },
].forEach((st, i) => {
const sy = 2.05 + i * 0.88;
vLine(s, 6.35, sy, 0.74);
s.addText(st.n, {
x: 6.6, y: sy + 0.02, w: 0.85, h: 0.7,
fontSize: 32, bold: false, color: C.teal, fontFace: "Calibri",
margin: 0, valign: "middle",
});
s.addText(st.l, {
x: 7.52, y: sy + 0.1, w: 2.05, h: 0.5,
fontSize: 11, color: C.dark, fontFace: "Calibri", margin: 0,
});
});
}
// ─── SLIDE 12 — Conclusion ──────────────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.darkBg };
topBar(s);
s.addShape(pres.shapes.RECTANGLE, {
x: 0.7, y: 0.73, w: 0.55, h: 0.09,
fill: { color: C.yellow }, line: { color: C.yellow },
});
s.addText("Merci.", {
x: 0.7, y: 0.84, w: 9, h: 1.45,
fontSize: 56, bold: false, color: C.white,
fontFace: "Calibri", margin: 0,
});
hLine(s, 0.7, 2.4, 8.6, "374151");
[
{ line: "WEB : Laravel 12 + FilamentPHP + API REST + Docker", col: false },
{ line: "Mobile : Flutter — 7 modules complets (iOS & Android)", col: false },
{ line: "Wear OS : synchronisation bidirectionnelle — Bonus", col: true },
].forEach((item, i) => {
vLine(s, 0.7, 2.52 + i * 0.68, 0.5, item.col ? C.yellow : C.teal);
s.addText(item.line, {
x: 0.93, y: 2.59 + i * 0.68, w: 8.8, h: 0.38,
fontSize: 13, color: item.col ? C.yellow : "D1D5DB",
fontFace: "Calibri", margin: 0,
});
});
s.addText("Questions ?", {
x: 0.7, y: 4.3, w: 9, h: 0.7,
fontSize: 30, bold: false, color: C.teal,
fontFace: "Calibri", margin: 0,
});
s.addText("CESIZen · CDA CESI BLOC 2 · Sam DEPARDIEU · Mai 2026", {
x: 0.7, y: 5.2, w: 9, h: 0.28,
fontSize: 10, color: "374151", fontFace: "Calibri", margin: 0,
});
}
// ─── Save ───────────────────────────────────────────────────
pres.writeFile({ fileName: "CESIZen_Presentation.pptx" })
.then(() => console.log("OK — CESIZen_Presentation.pptx"))
.catch((err) => console.error("ERR:", err));
+248
View File
@@ -0,0 +1,248 @@
// ══════════════════════════════════════════════════════════════
// Soutenance — BLOC 3 (INFCDAAL3)
// « Déployer et sécuriser les applications informatiques »
// Style strictement calqué sur presentation.js (thème CESIZen).
// Génération : node presentation_bloc3.js
// ══════════════════════════════════════════════════════════════
const pptxgen = require("pptxgenjs");
const pres = new pptxgen();
pres.layout = "LAYOUT_16x9";
pres.author = "Sam DEPARDIEU";
pres.title = "CESIZen — Soutenance BLOC 3 (Déploiement & Sécurité)";
const C = {
darkBg: "0D1B2A", lightBg: "F7F7F2", teal: "00BF63", yellow: "FFDE59",
dark: "2D2D2D", navy: "1B2838", white: "FFFFFF", gray: "6B7280",
border: "E5E7EB",
};
// ─── Helpers (identiques à presentation.js) ─────────────────
const topBar = (s) => s.addShape(pres.shapes.RECTANGLE, { x: 0, y: 0, w: 10, h: 0.09, fill: { color: C.teal }, line: { color: C.teal } });
const vLine = (s, x, y, h, color) => s.addShape(pres.shapes.RECTANGLE, { x, y, w: 0.07, h, fill: { color: color || C.teal }, line: { color: color || C.teal } });
const hLine = (s, x, y, w, color) => s.addShape(pres.shapes.RECTANGLE, { x, y, w, h: 0.02, fill: { color: color || C.border }, line: { color: color || C.border } });
function header(s, num, section, title) {
topBar(s);
s.addText(String(num).padStart(2, "0"), { x: 9.0, y: 0.11, w: 0.82, h: 0.38, fontSize: 14, color: C.teal, fontFace: "Calibri", bold: true, align: "right", margin: 0 });
s.addText(section.toUpperCase(), { x: 0.65, y: 0.13, w: 7, h: 0.26, fontSize: 9, color: C.gray, fontFace: "Calibri", charSpacing: 2, margin: 0 });
s.addShape(pres.shapes.RECTANGLE, { x: 0.65, y: 0.44, w: 0.55, h: 0.07, fill: { color: C.yellow }, line: { color: C.yellow } });
s.addText(title, { x: 0.65, y: 0.52, w: 8.7, h: 0.7, fontSize: 26, color: C.darkBg, fontFace: "Calibri", bold: false, margin: 0 });
hLine(s, 0.65, 1.25, 8.8);
}
// Carte (bloc encadré avec titre + lignes)
function card(s, x, y, w, h, accent, title, lines) {
s.addShape(pres.shapes.RECTANGLE, { x, y, w, h, fill: { color: C.white }, line: { color: C.border, width: 1 } });
vLine(s, x, y, h, accent);
s.addText(title, { x: x + 0.2, y: y + 0.14, w: w - 0.35, h: 0.35, fontSize: 13.5, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0 });
s.addText(lines.map((t) => ({ text: t, options: { bullet: { code: "2022", indent: 12 }, color: C.dark, fontSize: 11, paraSpaceAfter: 4 } })),
{ x: x + 0.22, y: y + 0.52, w: w - 0.4, h: h - 0.62, fontFace: "Calibri", valign: "top", margin: 0 });
}
// ─── SLIDE 01 — Couverture ──────────────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.darkBg };
topBar(s);
s.addShape(pres.shapes.RECTANGLE, { x: 0.7, y: 0.72, w: 0.55, h: 0.09, fill: { color: C.yellow }, line: { color: C.yellow } });
s.addText("CESIZEN", { x: 0.7, y: 0.83, w: 8.5, h: 1.25, fontSize: 54, color: C.white, fontFace: "Calibri", margin: 0 });
s.addText("Déploiement & Sécurisation de l'application", { x: 0.7, y: 2.05, w: 8.5, h: 0.5, fontSize: 18, color: C.teal, fontFace: "Calibri", margin: 0 });
hLine(s, 0.7, 2.78, 5.9, "374151");
s.addText("Soutenance CDA CESI — BLOC 3 · Déployer et sécuriser les applications", { x: 0.7, y: 2.9, w: 5.9, h: 0.38, fontSize: 12, color: "D1D5DB", fontFace: "Calibri", margin: 0 });
s.addText("Réf. : INFCDAAL3", { x: 0.7, y: 3.28, w: 5, h: 0.34, fontSize: 11, color: C.gray, fontFace: "Calibri", margin: 0 });
s.addText("Sam DEPARDIEU", { x: 0.7, y: 3.72, w: 5, h: 0.38, fontSize: 13, color: C.yellow, fontFace: "Calibri", margin: 0 });
s.addText("Juillet 2026", { x: 0.7, y: 4.12, w: 4, h: 0.35, fontSize: 12, color: C.gray, fontFace: "Calibri", margin: 0 });
// Panel droit teal
s.addShape(pres.shapes.RECTANGLE, { x: 6.95, y: 0.09, w: 3.05, h: 5.535, fill: { color: C.teal }, line: { color: C.teal } });
s.addText("Au programme", { x: 7.1, y: 0.28, w: 2.75, h: 0.44, fontSize: 13, bold: true, color: C.white, fontFace: "Calibri", margin: 0 });
hLine(s, 7.1, 0.74, 2.7, "00995A");
["Gestion de version", "Évolutions & maintenance", "Plan de déploiement", "CI/CD automatisée", "🔒 Sécurité & RGPD"].forEach((label, i) => {
const hl = label.startsWith("🔒");
vLine(s, 7.1, 0.95 + i * 0.82, 0.6, hl ? C.yellow : C.white);
s.addText(label, { x: 7.28, y: 1.03 + i * 0.82, w: 2.55, h: 0.44, fontSize: 12, color: hl ? C.yellow : C.white, fontFace: "Calibri", margin: 0 });
});
}
// ─── SLIDE 02 — Sommaire ────────────────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
topBar(s);
s.addShape(pres.shapes.RECTANGLE, { x: 0.65, y: 0.37, w: 0.55, h: 0.09, fill: { color: C.yellow }, line: { color: C.yellow } });
s.addText("SOMMAIRE", { x: 0.65, y: 0.47, w: 8, h: 0.88, fontSize: 34, color: C.darkBg, fontFace: "Calibri", margin: 0 });
hLine(s, 0.65, 1.38, 8.8);
["Contexte & objectifs du bloc", "Gestion des versions (Gitea)", "Gestion des évolutions & maintenance",
"Plan de déploiement & environnements", "Intégration & déploiement continus (CI/CD)",
"Plan de sécurisation & RGPD", "Bilan & livrables"].forEach((item, i) => {
s.addText(String(i + 1).padStart(2, "0"), { x: 0.65, y: 1.6 + i * 0.5, w: 0.65, h: 0.38, fontSize: 16, color: C.teal, fontFace: "Calibri", margin: 0 });
s.addText(item, { x: 1.38, y: 1.63 + i * 0.5, w: 5.2, h: 0.36, fontSize: 13.5, color: C.dark, fontFace: "Calibri", margin: 0 });
});
// panel droit
s.addShape(pres.shapes.RECTANGLE, { x: 6.95, y: 0.09, w: 3.05, h: 5.535, fill: { color: C.teal }, line: { color: C.teal } });
s.addText("Objectif", { x: 7.1, y: 0.35, w: 2.75, h: 0.4, fontSize: 13, bold: true, color: C.white, fontFace: "Calibri", margin: 0 });
hLine(s, 7.1, 0.78, 2.7, "00995A");
s.addText("Déployer et sécuriser une application déjà développée, dans un cadre professionnel et conforme au RGPD.",
{ x: 7.1, y: 0.95, w: 2.75, h: 1.4, fontSize: 12.5, color: C.white, fontFace: "Calibri", valign: "top", margin: 0 });
}
// ─── SLIDE 03 — Contexte & objectifs ────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
header(s, 3, "Introduction", "Contexte & objectifs du bloc");
s.addText("L'application CESIZen (Web, Mobile, Wear OS) a été conçue et développée au BLOC 2. L'enjeu de ce bloc est de la mettre en service de façon fiable, automatisée et sécurisée.",
{ x: 0.65, y: 1.4, w: 8.8, h: 0.7, fontSize: 13, color: C.dark, fontFace: "Calibri", valign: "top", margin: 0 });
card(s, 0.65, 2.25, 4.35, 2.9, C.teal, "4 livrables attendus", [
"Outil de gestion de version", "Outil de gestion des évolutions",
"Plan de déploiement (CI/CD)", "Plan de sécurisation (RGPD, chiffrement)"]);
card(s, 5.15, 2.25, 4.3, 2.9, C.yellow, "Contexte du projet", [
"Projet (RE)Sources Relationnelles", "Données personnelles sensibles",
"Exigences ministérielles & RGPD", "Architecture MVC (Laravel 12)"]);
}
// ─── SLIDE 04 — Gestion de version ──────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
header(s, 4, "Maintenance", "Gestion des versions : Git + Gitea");
card(s, 0.65, 1.45, 4.35, 3.7, C.teal, "Gitea auto-hébergé", [
"gitea.sam-coffre.duckdns.org", "Souveraineté totale des données",
"Très léger (~200 Mo RAM)", "CI/CD native intégrée",
"Open source & gratuit"]);
card(s, 5.15, 1.45, 4.3, 3.7, C.yellow, "Organisation Git", [
"Monorepo : WEB / mobile / Wear", "main : production",
"develop : intégration", "feature/* : via Pull Request",
"Conventional Commits + CHANGELOG"]);
s.addText("Pourquoi Gitea plutôt que GitHub/GitLab ? → maîtrise de l'hébergement des données, indispensable pour un projet ministériel.",
{ x: 0.65, y: 5.05, w: 8.8, h: 0.4, fontSize: 11, italic: true, color: C.gray, fontFace: "Calibri", margin: 0 });
}
// ─── SLIDE 05 — Évolutions & maintenance ────────────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
header(s, 5, "Maintenance", "Gestion des évolutions & incidents");
card(s, 0.65, 1.45, 4.35, 2.5, C.teal, "Gitea Issues (2 modèles)", [
"🐞 Rapport d'incident (correctif)", "✨ Demande d'évolution (évolutif)",
"Champs structurés & priorité"]);
card(s, 5.15, 1.45, 4.3, 2.5, C.yellow, "Pilotage", [
"Labels + tableau Kanban", "Jalons = versions (SemVer)",
"Pull Request obligatoire"]);
// Flux
s.addText("Cycle de vie d'une demande", { x: 0.65, y: 4.15, w: 8.8, h: 0.3, fontSize: 12, bold: true, color: C.darkBg, fontFace: "Calibri", margin: 0 });
const steps = ["Nouveau", "Priorisé", "En cours", "En revue", "Déployé"];
steps.forEach((st, i) => {
const x = 0.65 + i * 1.78;
s.addShape(pres.shapes.RECTANGLE, { x, y: 4.5, w: 1.55, h: 0.55, fill: { color: i === 4 ? C.teal : C.navy }, line: { color: C.navy } });
s.addText(st, { x, y: 4.5, w: 1.55, h: 0.55, fontSize: 11, color: C.white, align: "center", valign: "middle", fontFace: "Calibri", margin: 0 });
if (i < 4) s.addText("→", { x: x + 1.5, y: 4.5, w: 0.32, h: 0.55, fontSize: 16, color: C.teal, align: "center", valign: "middle", margin: 0 });
});
}
// ─── SLIDE 06 — Plan de déploiement (diagramme) ─────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
header(s, 6, "Déploiement", "Environnements & hébergement");
s.addText([
{ text: "4 environnements : ", options: { bold: true } },
{ text: "Développement → Test/CI → Préproduction → Production", options: {} },
], { x: 0.65, y: 1.4, w: 8.8, h: 0.4, fontSize: 12.5, color: C.dark, fontFace: "Calibri", margin: 0 });
s.addImage({ path: "docs/img/diag_archi.png", x: 0.65, y: 1.95, w: 8.8, h: 2.68 });
s.addText("Production : cesizen.sam-coffre.duckdns.org — conteneurisée & orchestrée via Portainer, TLS géré par Nginx Proxy Manager.",
{ x: 0.65, y: 4.8, w: 8.8, h: 0.4, fontSize: 11, italic: true, color: C.gray, fontFace: "Calibri", margin: 0 });
}
// ─── SLIDE 07 — CI/CD (diagramme) ───────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
header(s, 7, "Déploiement", "CI/CD automatisée — Gitea Actions");
s.addImage({ path: "docs/img/diag_cicd.png", x: 0.65, y: 1.4, w: 8.8, h: 3.5 });
s.addText("Un push lance les tests & contrôles qualité ; une fusion sur main construit l'image, la publie et redéploie automatiquement.",
{ x: 0.65, y: 4.95, w: 8.8, h: 0.4, fontSize: 11, italic: true, color: C.gray, fontFace: "Calibri", margin: 0 });
}
// ─── SLIDE 08 — Sécurité (fond sombre) ──────────────────────
{
const s = pres.addSlide();
s.background = { color: C.darkBg };
topBar(s);
s.addText("08", { x: 9.0, y: 0.11, w: 0.82, h: 0.38, fontSize: 14, color: C.teal, fontFace: "Calibri", bold: true, align: "right", margin: 0 });
s.addText("SÉCURITÉ", { x: 0.65, y: 0.13, w: 7, h: 0.26, fontSize: 9, color: "9CA3AF", fontFace: "Calibri", charSpacing: 2, margin: 0 });
s.addShape(pres.shapes.RECTANGLE, { x: 0.65, y: 0.44, w: 0.55, h: 0.07, fill: { color: C.yellow }, line: { color: C.yellow } });
s.addText("Plan de sécurisation & RGPD", { x: 0.65, y: 0.52, w: 8.7, h: 0.7, fontSize: 26, color: C.white, fontFace: "Calibri", margin: 0 });
hLine(s, 0.65, 1.25, 8.8, "374151");
const sec = [
["Applicatif", ["Auth Sanctum + rôles (RBAC)", "CSRF, validation, en-têtes HTTP", "CSP / HSTS / X-Frame-Options"]],
["Chiffrement", ["HTTPS/TLS (Let's Encrypt)", "Mots de passe : bcrypt", "Sessions chiffrées"]],
["RGPD", ["Minimisation & consentement", "Droit à l'effacement", "Anonymisation des stats"]],
["Qualité", ["Pint + tests PHPUnit", "Audit des dépendances (CI)", "Revues via Pull Request"]],
];
sec.forEach(([title, lines], i) => {
const x = 0.65 + (i % 2) * 4.5;
const y = 1.5 + Math.floor(i / 2) * 1.95;
s.addShape(pres.shapes.RECTANGLE, { x, y, w: 4.2, h: 1.75, fill: { color: C.navy }, line: { color: "374151", width: 1 } });
vLine(s, x, y, 1.75, C.teal);
s.addText(title, { x: x + 0.2, y: y + 0.12, w: 3.8, h: 0.35, fontSize: 14, bold: true, color: C.yellow, fontFace: "Calibri", margin: 0 });
s.addText(lines.map((t) => ({ text: t, options: { bullet: { code: "2022", indent: 12 }, color: "E5E7EB", fontSize: 11, paraSpaceAfter: 3 } })),
{ x: x + 0.22, y: y + 0.5, w: 3.8, h: 1.15, fontFace: "Calibri", valign: "top", margin: 0 });
});
}
// ─── SLIDE 09 — Analyse des risques (OWASP) ─────────────────
{
const s = pres.addSlide();
s.background = { color: C.lightBg };
header(s, 9, "Sécurité", "Analyse des risques & traitement");
const rows = [
["Injection SQL", "ORM Eloquent / requêtes préparées"],
["XSS", "Échappement Blade + CSP"],
["CSRF", "Jeton CSRF Laravel"],
["Vol de session", "HTTPS forcé, cookies Secure, HSTS"],
["Dépendances vulnérables", "composer audit / npm audit (CI)"],
["Force brute", "Rate limiting"],
];
// en-tête tableau
s.addShape(pres.shapes.RECTANGLE, { x: 0.65, y: 1.45, w: 4.3, h: 0.5, fill: { color: C.navy }, line: { color: C.navy } });
s.addShape(pres.shapes.RECTANGLE, { x: 4.95, y: 1.45, w: 4.5, h: 0.5, fill: { color: C.navy }, line: { color: C.navy } });
s.addText("Risque", { x: 0.8, y: 1.45, w: 4.1, h: 0.5, fontSize: 12, bold: true, color: C.white, valign: "middle", fontFace: "Calibri", margin: 0 });
s.addText("Mesure de prévention", { x: 5.1, y: 1.45, w: 4.3, h: 0.5, fontSize: 12, bold: true, color: C.white, valign: "middle", fontFace: "Calibri", margin: 0 });
rows.forEach((r, i) => {
const y = 1.95 + i * 0.5;
const bg = i % 2 ? "FFFFFF" : "EFF5F1";
s.addShape(pres.shapes.RECTANGLE, { x: 0.65, y, w: 4.3, h: 0.5, fill: { color: bg }, line: { color: C.border } });
s.addShape(pres.shapes.RECTANGLE, { x: 4.95, y, w: 4.5, h: 0.5, fill: { color: bg }, line: { color: C.border } });
s.addText(r[0], { x: 0.8, y, w: 4.1, h: 0.5, fontSize: 11, color: C.dark, valign: "middle", fontFace: "Calibri", margin: 0 });
s.addText(r[1], { x: 5.1, y, w: 4.3, h: 0.5, fontSize: 11, color: C.dark, valign: "middle", fontFace: "Calibri", margin: 0 });
});
}
// ─── SLIDE 10 — Bilan & livrables ───────────────────────────
{
const s = pres.addSlide();
s.background = { color: C.darkBg };
topBar(s);
s.addShape(pres.shapes.RECTANGLE, { x: 0.7, y: 0.6, w: 0.55, h: 0.09, fill: { color: C.yellow }, line: { color: C.yellow } });
s.addText("Bilan & livrables", { x: 0.7, y: 0.72, w: 8.6, h: 0.9, fontSize: 34, color: C.white, fontFace: "Calibri", margin: 0 });
hLine(s, 0.7, 1.7, 8.6, "374151");
const items = [
["Gestion de version", "Gitea auto-hébergé + stratégie de branches"],
["Évolutions & maintenance", "Gitea Issues, Kanban, SemVer, CHANGELOG"],
["Déploiement automatisé", "CI/CD Gitea Actions → Registry → Portainer"],
["Sécurité", "Chiffrement, en-têtes, RGPD, audits, tests"],
];
items.forEach((it, i) => {
const y = 2.0 + i * 0.78;
vLine(s, 0.7, y, 0.6, C.teal);
s.addText(it[0], { x: 0.9, y: y - 0.02, w: 3.6, h: 0.4, fontSize: 14, bold: true, color: C.yellow, fontFace: "Calibri", margin: 0 });
s.addText(it[1], { x: 4.4, y: y - 0.02, w: 5.2, h: 0.4, fontSize: 12.5, color: "E5E7EB", fontFace: "Calibri", margin: 0 });
});
s.addText("Une base industrialisée, reproductible et sécurisée. Merci de votre attention — place aux questions.",
{ x: 0.7, y: 5.25, w: 8.8, h: 0.4, fontSize: 12, italic: true, color: C.teal, fontFace: "Calibri", margin: 0 });
}
pres.writeFile({ fileName: "CESIZen_Presentation_Bloc3.pptx" }).then((f) => console.log("Généré :", f));