diff --git a/CESIZen_Presentation.pptx b/CESIZen_Presentation.pptx new file mode 100644 index 0000000..a09446a Binary files /dev/null and b/CESIZen_Presentation.pptx differ diff --git a/CESIZen_Presentation_Bloc3.pptx b/CESIZen_Presentation_Bloc3.pptx new file mode 100644 index 0000000..f698ab7 Binary files /dev/null and b/CESIZen_Presentation_Bloc3.pptx differ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..abcb777 --- /dev/null +++ b/CHANGELOG.md @@ -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). diff --git a/docs/Cahier de Tests.pdf b/docs/Cahier de Tests.pdf new file mode 100644 index 0000000..90ec365 Binary files /dev/null and b/docs/Cahier de Tests.pdf differ diff --git a/docs/DOCUMENTATION.md b/docs/DOCUMENTATION.md new file mode 100644 index 0000000..c61b21e --- /dev/null +++ b/docs/DOCUMENTATION.md @@ -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é (1–5) | +| `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 +- **150–299 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. diff --git a/docs/Documentation_Technique_CESIZen.docx b/docs/Documentation_Technique_CESIZen.docx new file mode 100644 index 0000000..8ce5e68 Binary files /dev/null and b/docs/Documentation_Technique_CESIZen.docx differ diff --git a/docs/Documentation_Technique_CESIZen.pdf b/docs/Documentation_Technique_CESIZen.pdf new file mode 100644 index 0000000..b161730 Binary files /dev/null and b/docs/Documentation_Technique_CESIZen.pdf differ diff --git a/docs/Dossier_Deploiement_Securisation_CESIZen.docx b/docs/Dossier_Deploiement_Securisation_CESIZen.docx new file mode 100644 index 0000000..5c3c547 Binary files /dev/null and b/docs/Dossier_Deploiement_Securisation_CESIZen.docx differ diff --git a/docs/Validation et PV.pdf b/docs/Validation et PV.pdf new file mode 100644 index 0000000..230b3f5 Binary files /dev/null and b/docs/Validation et PV.pdf differ diff --git a/docs/img/diag_archi.png b/docs/img/diag_archi.png new file mode 100644 index 0000000..97b553c Binary files /dev/null and b/docs/img/diag_archi.png differ diff --git a/docs/img/diag_cicd.png b/docs/img/diag_cicd.png new file mode 100644 index 0000000..996faff Binary files /dev/null and b/docs/img/diag_cicd.png differ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..34b677b --- /dev/null +++ b/package-lock.json @@ -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" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b0464f4 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "pptxgenjs": "^4.0.1" + } +} diff --git a/presentation.js b/presentation.js new file mode 100644 index 0000000..a7883c9 --- /dev/null +++ b/presentation.js @@ -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)); diff --git a/presentation_bloc3.js b/presentation_bloc3.js new file mode 100644 index 0000000..e9d15a1 --- /dev/null +++ b/presentation_bloc3.js @@ -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));