From 9d836eeaac57b7611b2049ec6bf8a3f4ea22653b Mon Sep 17 00:00:00 2001 From: soufiane Date: Fri, 28 Nov 2025 14:26:20 +0100 Subject: [PATCH] feat: add user archiving (soft delete) with is_active field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add is_active column migration for users table - Update user.controller.js to support isActive in profile updates - Update admin.controller.js to support isActive filtering and updates - Add migration script for is_active column 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../migrations/add-is-active-to-users.sql | 14 + docs/BPMN_DIAGRAMS.md | 834 ++++++++++++++++++ scripts/apply-is-active-migration.js | 40 + src/controllers/admin.controller.js | 46 +- src/controllers/user.controller.js | 12 +- 5 files changed, 936 insertions(+), 10 deletions(-) create mode 100644 database/migrations/add-is-active-to-users.sql create mode 100644 docs/BPMN_DIAGRAMS.md create mode 100644 scripts/apply-is-active-migration.js diff --git a/database/migrations/add-is-active-to-users.sql b/database/migrations/add-is-active-to-users.sql new file mode 100644 index 00000000..49f714ee --- /dev/null +++ b/database/migrations/add-is-active-to-users.sql @@ -0,0 +1,14 @@ +-- ============================================ +-- MIGRATION: Ajouter is_active à la table users +-- ============================================ +-- Cette migration ajoute le champ is_active pour permettre +-- l'archivage des comptes utilisateurs (soft delete) + +-- Ajouter la colonne is_active avec valeur par défaut TRUE +ALTER TABLE users ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT TRUE; + +-- Créer un index pour améliorer les performances des filtres +CREATE INDEX IF NOT EXISTS idx_users_is_active ON users(is_active); + +-- Mettre à jour tous les utilisateurs existants comme actifs +UPDATE users SET is_active = TRUE WHERE is_active IS NULL; diff --git a/docs/BPMN_DIAGRAMS.md b/docs/BPMN_DIAGRAMS.md new file mode 100644 index 00000000..927aa9ae --- /dev/null +++ b/docs/BPMN_DIAGRAMS.md @@ -0,0 +1,834 @@ +# Diagrammes BPMN - Thé Tip Top + +Ce document présente les différents processus et procédures du projet Thé Tip Top sous forme de diagrammes BPMN. + +--- + +## Table des matières + +1. [Diagramme d'activité principal - Jeu concours](#1-diagramme-dactivité-principal---jeu-concours) +2. [Processus de déploiement CI/CD](#2-processus-de-déploiement-cicd) +3. [Procédure de sauvegarde du workflow Production](#3-procédure-de-sauvegarde-du-workflow-production) +4. [Procédure de restauration](#4-procédure-de-restauration) + +--- + +## 1. Diagramme d'activité principal - Jeu concours + +### 1.1 Processus de participation au jeu + +Ce diagramme représente le parcours utilisateur pour participer au jeu-concours. + +```mermaid +flowchart TD + subgraph Client["🧑 Client"] + A([Début]) --> B{Utilisateur connecté ?} + B -->|Non| C[Afficher page Login] + C --> D{Choix authentification} + D -->|Formulaire| E[Saisir email/mot de passe] + D -->|Google OAuth| F[Redirection Google] + D -->|Facebook OAuth| G[Redirection Facebook] + E --> H{Credentials valides ?} + F --> H + G --> H + H -->|Non| I[Afficher erreur] + I --> C + H -->|Oui| J[Créer session JWT] + B -->|Oui| K[Accéder page Jeu] + J --> K + end + + subgraph Jeu["🎮 Participation"] + K --> L[Saisir code ticket 10 caractères] + L --> M{Code valide ?} + M -->|Non| N[Afficher erreur format] + N --> L + M -->|Oui| O[Envoyer requête API] + end + + subgraph Backend["⚙️ Backend API"] + O --> P{Ticket existe ?} + P -->|Non| Q[Erreur: Ticket inexistant] + P -->|Oui| R{Ticket déjà utilisé ?} + R -->|Oui| S[Erreur: Ticket déjà joué] + R -->|Non| T[Attribuer lot au ticket] + T --> U[Associer ticket à l'utilisateur] + U --> V[Mettre à jour statut: pending] + end + + subgraph Resultat["🎁 Résultat"] + Q --> W[Afficher modal erreur] + S --> W + V --> X[Afficher modal gain] + X --> Y[Afficher type de lot gagné] + Y --> Z([Fin]) + W --> L + end +``` + +### 1.2 Processus de validation des gains (Employé) + +```mermaid +flowchart TD + subgraph Employe["👨‍💼 Employé"] + A([Début]) --> B[Connexion espace employé] + B --> C{Authentifié + Rôle Employee ?} + C -->|Non| D[Redirection Login] + D --> B + C -->|Oui| E[Afficher dashboard employé] + end + + subgraph Verification["🔍 Vérification"] + E --> F{Mode de recherche} + F -->|Recherche| G[Saisir code ticket] + F -->|Liste| H[Afficher tickets en attente] + G --> I[Rechercher ticket] + H --> J[Sélectionner ticket] + I --> K{Ticket trouvé ?} + K -->|Non| L[Afficher erreur] + L --> F + K -->|Oui| M[Afficher détails ticket] + J --> M + end + + subgraph Validation["✅ Validation"] + M --> N{Client présent avec pièce d'identité ?} + N -->|Non| O[Refuser validation] + O --> F + N -->|Oui| P[Vérifier correspondance] + P --> Q{Informations correctes ?} + Q -->|Non| O + Q -->|Oui| R[Cliquer Valider] + R --> S[Mettre à jour statut: claimed] + S --> T[Enregistrer date remise] + T --> U[Enregistrer ID employé] + U --> V[Remettre le lot au client] + V --> W([Fin]) + end +``` + +### 1.3 Processus d'administration + +```mermaid +flowchart TD + subgraph Admin["👨‍💻 Administrateur"] + A([Début]) --> B[Connexion espace admin] + B --> C{Authentifié + Rôle Admin ?} + C -->|Non| D[Redirection Login] + D --> B + C -->|Oui| E[Afficher dashboard admin] + end + + subgraph Stats["📊 Statistiques"] + E --> F{Action souhaitée} + F -->|Statistiques| G[Charger statistiques globales] + G --> H[Afficher total participations] + H --> I[Afficher distribution lots] + I --> J[Afficher taux conversion] + end + + subgraph Users["👥 Gestion Utilisateurs"] + F -->|Utilisateurs| K[Charger liste utilisateurs] + K --> L[Afficher tableau utilisateurs] + L --> M{Action sur utilisateur} + M -->|Voir détails| N[Afficher profil complet] + M -->|Modifier rôle| O[Changer rôle utilisateur] + end + + subgraph Tickets["🎫 Gestion Tickets"] + F -->|Tickets| P[Charger liste tickets] + P --> Q[Afficher tableau tickets] + Q --> R{Filtrer par} + R -->|Statut| S[Filtrer pending/claimed/unclaimed] + R -->|Date| T[Filtrer par période] + R -->|Lot| U[Filtrer par type de lot] + end + + subgraph Export["📤 Export"] + F -->|Export| V[Générer rapport] + V --> W[Exporter CSV/Excel] + W --> X([Fin]) + J --> E + N --> E + O --> E + S --> E + T --> E + U --> E + end +``` + +--- + +## 2. Processus de déploiement CI/CD + +### 2.1 Processus global de déploiement + +```mermaid +flowchart TD + subgraph Trigger["🔔 Déclencheur"] + A([Début]) --> B{Type de déclenchement} + B -->|Push Git| C[Webhook Gitea] + B -->|Manuel| D[Jenkins Dashboard] + B -->|Polling| E[Poll SCM chaque minute] + C --> F[Détecter branche] + D --> F + E --> F + end + + subgraph Detection["🧭 Détection Environnement"] + F --> G{Quelle branche ?} + G -->|dev| H[ENV = dev] + G -->|preprod| I[ENV = preprod] + G -->|main| J[ENV = prod] + G -->|autre| K[ENV = paramètre choisi] + H --> L[TAG = dev-latest] + I --> M[TAG = preprod-latest] + J --> N[TAG = prod-latest] + K --> O[TAG = {env}-latest] + end + + subgraph Pipeline["⚙️ Pipeline Jenkins"] + L --> P[Checkout code] + M --> P + N --> P + O --> P + P --> Q[Install dépendances] + Q --> R[Tests & Qualité] + R --> S{Tests OK ?} + S -->|Non| T[❌ Pipeline échoué] + S -->|Oui| U[Build image Docker] + U --> V[Push vers Registry] + V --> W[Deploy sur serveur] + W --> X[Health Check] + X --> Y{Service OK ?} + Y -->|Non| T + Y -->|Oui| Z[✅ Pipeline réussi] + T --> AA([Fin - Échec]) + Z --> AB([Fin - Succès]) + end +``` + +### 2.2 Processus de déploiement DEV + +```mermaid +flowchart TD + subgraph TriggerDev["🔔 Déclencheur DEV"] + A([Push sur branche dev]) --> B[Webhook → Jenkins] + B --> C[Démarrer pipeline dev] + end + + subgraph InitDev["🧭 Initialisation"] + C --> D[Détecter branche: dev] + D --> E[Définir ENV=dev] + E --> F[Définir TAG=dev-latest] + F --> G["Définir DEPLOY_PATH=/srv/devops/the-tip-top/dev"] + end + + subgraph QualityDev["🔍 Qualité Code"] + G --> H[npm ci - Installation] + H --> I[npm run lint] + I --> J{Lint OK ?} + J -->|Non| K["⚠️ Warning lint (continue)"] + J -->|Oui| L[npm test] + K --> L + L --> M{Tests OK ?} + M -->|Non| N["⚠️ Warning tests (continue)"] + M -->|Oui| O[Continuer] + N --> O + end + + subgraph BuildDev["🐳 Build Docker"] + O --> P["docker build -t registry/backend:dev-latest"] + P --> Q["docker tag → latest"] + end + + subgraph PushDev["📤 Push Registry"] + Q --> R[docker login registry] + R --> S["docker push :dev-latest"] + S --> T["docker push :latest"] + end + + subgraph DeployDev["🚀 Déploiement DEV"] + T --> U["cd /srv/devops/the-tip-top/dev"] + U --> V[docker compose pull] + V --> W[docker compose up -d --force-recreate] + end + + subgraph HealthDev["🩺 Vérification"] + W --> X["curl https://api.dev.dsp5-archi-o24a-15m-g3.fr/health"] + X --> Y{HTTP 200 ?} + Y -->|Non, retry| Z[Attendre 5s] + Z --> X + Y -->|Oui| AA[✅ DEV déployé] + AA --> AB([Fin]) + end + + style TriggerDev fill:#e3f2fd + style DeployDev fill:#e8f5e9 +``` + +### 2.3 Processus de déploiement PREPROD + +```mermaid +flowchart TD + subgraph TriggerPreprod["🔔 Déclencheur PREPROD"] + A([Push sur branche preprod]) --> B[Webhook → Jenkins] + B --> C[Démarrer pipeline preprod] + end + + subgraph InitPreprod["🧭 Initialisation"] + C --> D[Détecter branche: preprod] + D --> E[Définir ENV=preprod] + E --> F[Définir TAG=preprod-latest] + F --> G["Définir DEPLOY_PATH=/srv/devops/the-tip-top/preprod"] + end + + subgraph QualityPreprod["🔍 Qualité Code - Stricte"] + G --> H[npm ci - Installation] + H --> I["Parallèle: Lint & Tests"] + I --> J[npm run lint] + I --> K[npm test] + J --> L{Lint OK ?} + K --> M{Tests OK ?} + L -->|Non| N[❌ Échec - Lint obligatoire] + M -->|Non| O[❌ Échec - Tests obligatoires] + L -->|Oui| P[✅ Lint passé] + M -->|Oui| Q[✅ Tests passés] + P --> R{Tous OK ?} + Q --> R + N --> S([Fin - Échec]) + O --> S + end + + subgraph SonarPreprod["📊 Analyse SonarQube"] + R -->|Oui| T[sonar-scanner] + T --> U[Envoyer métriques SonarQube] + U --> V{Quality Gate ?} + V -->|Non| W[⚠️ Warning qualité] + V -->|Oui| X[✅ Qualité validée] + W --> Y[Continuer avec warning] + X --> Y + end + + subgraph BuildPreprod["🐳 Build Docker"] + Y --> Z["Lire .env.preprod"] + Z --> AA["docker build --build-arg NEXT_PUBLIC_*"] + AA --> AB["docker tag → preprod-latest"] + end + + subgraph DeployPreprod["🚀 Déploiement PREPROD"] + AB --> AC[docker login registry] + AC --> AD["docker push :preprod-latest"] + AD --> AE["cd /srv/devops/the-tip-top/preprod"] + AE --> AF[docker compose pull] + AF --> AG[docker compose up -d --force-recreate] + end + + subgraph HealthPreprod["🩺 Vérification"] + AG --> AH["curl https://api.preprod.dsp5-archi-o24a-15m-g3.fr/health"] + AH --> AI{HTTP 200 ?} + AI -->|Non| AJ[Retry 10x avec 5s intervalle] + AJ --> AK{Max retries ?} + AK -->|Non| AH + AK -->|Oui| AL[❌ Health check échoué] + AI -->|Oui| AM[✅ PREPROD déployé] + AL --> S + AM --> AN([Fin - Succès]) + end + + style TriggerPreprod fill:#fff3e0 + style DeployPreprod fill:#e8f5e9 +``` + +### 2.4 Processus de déploiement PROD + +```mermaid +flowchart TD + subgraph TriggerProd["🔔 Déclencheur PROD"] + A([Push sur branche main]) --> B[Webhook → Jenkins] + B --> C{Validation requise ?} + C -->|Manuel| D[Approbation manuelle] + C -->|Auto| E[Démarrer pipeline prod] + D --> E + end + + subgraph InitProd["🧭 Initialisation"] + E --> F[Détecter branche: main] + F --> G[Définir ENV=prod] + G --> H[Définir TAG=prod-latest] + H --> I["Définir DEPLOY_PATH=/srv/devops/the-tip-top/prod"] + end + + subgraph QualityProd["🔍 Qualité Code - Maximum"] + I --> J[npm ci - Installation] + J --> K["Parallèle: Lint, Tests, Sonar"] + K --> L[npm run lint - STRICT] + K --> M[npm test - STRICT] + K --> N[SonarQube Analysis] + L --> O{Lint OK ?} + M --> P{Tests OK ?} + N --> Q{Quality Gate OK ?} + O -->|Non| R[❌ Échec - Aucune tolérance] + P -->|Non| R + Q -->|Non| S[⚠️ Alerte qualité] + O -->|Oui| T[✅] + P -->|Oui| T + Q -->|Oui| T + S --> T + R --> U([Fin - Échec]) + end + + subgraph BackupProd["💾 Sauvegarde pré-déploiement"] + T --> V[Créer snapshot DB] + V --> W[Backup images Docker actuelles] + W --> X[Sauvegarder config] + X --> Y[Enregistrer état rollback] + end + + subgraph BuildProd["🐳 Build Docker"] + Y --> Z["Lire .env.production"] + Z --> AA["docker build --build-arg NEXT_PUBLIC_*"] + AA --> AB["docker tag → prod-latest"] + AB --> AC["docker tag → version semver"] + end + + subgraph DeployProd["🚀 Déploiement PROD"] + AC --> AD[docker login registry] + AD --> AE["docker push :prod-latest"] + AE --> AF["cd /srv/devops/the-tip-top/prod"] + AF --> AG[docker compose pull] + AG --> AH[docker compose up -d --force-recreate] + end + + subgraph HealthProd["🩺 Vérification Approfondie"] + AH --> AI["curl https://api.dsp5-archi-o24a-15m-g3.fr/health"] + AI --> AJ{HTTP 200 ?} + AJ -->|Non| AK[Retry 10x] + AK --> AL{Échec définitif ?} + AL -->|Oui| AM[🔄 Rollback automatique] + AM --> AN[Restaurer version précédente] + AN --> U + AL -->|Non| AI + AJ -->|Oui| AO[Tests E2E basiques] + AO --> AP{E2E OK ?} + AP -->|Non| AM + AP -->|Oui| AQ[✅ PROD déployé] + AQ --> AR([Fin - Succès]) + end + + style TriggerProd fill:#ffebee + style BackupProd fill:#e8eaf6 + style DeployProd fill:#e8f5e9 +``` + +--- + +## 3. Procédure de sauvegarde du workflow Production + +### 3.1 Sauvegarde automatique quotidienne + +```mermaid +flowchart TD + subgraph Trigger["⏰ Déclencheur"] + A([Cron: 02:00 UTC]) --> B[Démarrer job sauvegarde] + end + + subgraph PreBackup["📋 Pré-sauvegarde"] + B --> C[Vérifier espace disque] + C --> D{Espace suffisant ?} + D -->|Non| E[🚨 Alerte admin] + E --> F[Nettoyer anciennes sauvegardes] + F --> D + D -->|Oui| G[Créer dossier backup timestampé] + end + + subgraph DBBackup["🗄️ Sauvegarde Base de Données"] + G --> H[Connexion PostgreSQL prod] + H --> I["pg_dump --format=custom"] + I --> J[Compresser dump .gz] + J --> K[Calculer checksum SHA256] + K --> L[Vérifier intégrité dump] + L --> M{Dump valide ?} + M -->|Non| N[🚨 Alerte - Retry] + N --> I + M -->|Oui| O[✅ DB sauvegardée] + end + + subgraph DockerBackup["🐳 Sauvegarde Images Docker"] + O --> P[Lister images en production] + P --> Q["docker save backend:prod-latest"] + Q --> R["docker save frontend:prod-latest"] + R --> S[Compresser archives .tar.gz] + end + + subgraph ConfigBackup["⚙️ Sauvegarde Configuration"] + S --> T[Backup docker-compose.yml] + T --> U[Backup .env.production] + U --> V[Backup nginx.conf] + V --> W[Backup certificats SSL] + W --> X[Backup scripts déploiement] + end + + subgraph JenkinsBackup["🔧 Sauvegarde Jenkins"] + X --> Y[Export jobs Jenkins] + Y --> Z[Backup credentials chiffrés] + Z --> AA[Backup plugins list] + AA --> AB[Backup config globale] + end + + subgraph Upload["☁️ Upload Distant"] + AB --> AC[Chiffrer archive complète] + AC --> AD{Destination} + AD --> AE["Upload vers S3/GCS"] + AD --> AF["Copie vers serveur backup"] + AE --> AG[Vérifier upload] + AF --> AG + AG --> AH{Upload OK ?} + AH -->|Non| AI[🚨 Alerte - Retry] + AI --> AC + AH -->|Oui| AJ[✅ Backup distant OK] + end + + subgraph Cleanup["🧹 Nettoyage"] + AJ --> AK[Supprimer backups > 30 jours locaux] + AK --> AL[Supprimer backups > 90 jours distants] + AL --> AM[Générer rapport backup] + AM --> AN[Envoyer notification succès] + AN --> AO([Fin]) + end + + style DBBackup fill:#e3f2fd + style Upload fill:#e8f5e9 +``` + +### 3.2 Sauvegarde avant déploiement (Pre-deployment backup) + +```mermaid +flowchart TD + subgraph Trigger["🔔 Déclencheur"] + A([Déploiement PROD initié]) --> B[Stage: Pre-deployment backup] + end + + subgraph Snapshot["📸 Snapshot rapide"] + B --> C[Créer tag timestamp] + C --> D["TAG = backup-YYYYMMDD-HHMMSS"] + D --> E[docker tag backend:prod-latest → backend:TAG] + E --> F[docker tag frontend:prod-latest → frontend:TAG] + F --> G[Pousser tags vers registry] + end + + subgraph DBSnapshot["🗄️ Snapshot DB"] + G --> H[Créer snapshot PostgreSQL] + H --> I["pg_dump rapide → backup_pre_deploy.sql"] + I --> J[Stocker localement] + end + + subgraph StateRecord["📝 Enregistrement État"] + J --> K[Enregistrer versions actuelles] + K --> L["versions.json: {backend, frontend, db}"] + L --> M[Enregistrer docker-compose.yml actuel] + M --> N[Enregistrer timestamp rollback point] + end + + subgraph Validation["✅ Validation"] + N --> O{Tous backups créés ?} + O -->|Non| P[❌ Annuler déploiement] + P --> Q([Fin - Échec]) + O -->|Oui| R[✅ Continuer déploiement] + R --> S([Fin - Prêt]) + end + + style Snapshot fill:#fff3e0 + style StateRecord fill:#e8eaf6 +``` + +### 3.3 Politique de rétention des sauvegardes + +```mermaid +flowchart LR + subgraph Retention["📅 Politique de Rétention"] + A[Backups quotidiens] --> B[Conserver 7 jours] + C[Backups hebdomadaires] --> D[Conserver 4 semaines] + E[Backups mensuels] --> F[Conserver 12 mois] + G[Backups pre-deploy] --> H[Conserver 5 derniers] + end + + subgraph Storage["💾 Stockage"] + B --> I[Local: /srv/backups/daily/] + D --> J[Local: /srv/backups/weekly/] + F --> K[Distant: S3/GCS] + H --> L[Registry: tags backup-*] + end +``` + +--- + +## 4. Procédure de restauration + +### 4.1 Restauration DEV + +```mermaid +flowchart TD + subgraph Trigger["🔔 Déclencheur"] + A([Incident DEV détecté]) --> B{Type de problème} + B -->|Code cassé| C[Restaurer code] + B -->|Container crash| D[Restaurer container] + B -->|DB corrompue| E[Restaurer DB] + end + + subgraph CodeRestore["📦 Restauration Code"] + C --> F[git log - identifier commit stable] + F --> G[git revert ou git reset] + G --> H[Push vers branche dev] + H --> I[Pipeline automatique redéploie] + end + + subgraph ContainerRestore["🐳 Restauration Container"] + D --> J[Identifier dernière image stable] + J --> K["docker pull backend:dev-latest"] + K --> L[docker compose up -d --force-recreate] + L --> M[Vérifier logs] + end + + subgraph DBRestore["🗄️ Restauration DB DEV"] + E --> N[Lister backups disponibles] + N --> O[Sélectionner backup récent] + O --> P[Stopper services dépendants] + P --> Q["psql < backup_dev.sql"] + Q --> R[Redémarrer services] + end + + subgraph Validation["✅ Validation"] + I --> S[Health check] + M --> S + R --> S + S --> T{Service OK ?} + T -->|Non| U[Escalade vers équipe] + T -->|Oui| V[✅ DEV restauré] + U --> W([Fin - Manuel requis]) + V --> X([Fin - Succès]) + end + + style CodeRestore fill:#e3f2fd + style ContainerRestore fill:#fff3e0 + style DBRestore fill:#fce4ec +``` + +### 4.2 Restauration PREPROD + +```mermaid +flowchart TD + subgraph Trigger["🔔 Déclencheur"] + A([Incident PREPROD]) --> B[Évaluer impact] + B --> C{Sévérité} + C -->|Mineure| D[Fix forward - nouveau déploiement] + C -->|Majeure| E[Rollback nécessaire] + end + + subgraph Analysis["🔍 Analyse"] + E --> F[Identifier cause root] + F --> G{Cause identifiée} + G -->|Code| H[Rollback code] + G -->|Config| I[Rollback config] + G -->|DB| J[Rollback DB] + G -->|Infra| K[Rollback infra] + end + + subgraph CodeRollback["📦 Rollback Code PREPROD"] + H --> L[Identifier dernier tag stable] + L --> M["docker pull backend:preprod-{version}"] + M --> N[Mettre à jour docker-compose] + N --> O[docker compose up -d] + end + + subgraph ConfigRollback["⚙️ Rollback Config"] + I --> P[Restaurer .env.preprod backup] + P --> Q[Restaurer docker-compose.yml backup] + Q --> R[Redéployer avec config précédente] + end + + subgraph DBRollback["🗄️ Rollback DB PREPROD"] + J --> S[Mettre en maintenance] + S --> T[Stopper backend/frontend] + T --> U[Restaurer dump PostgreSQL] + U --> V["pg_restore -d thetiptop_preprod"] + V --> W[Redémarrer services] + end + + subgraph InfraRollback["🖥️ Rollback Infra"] + K --> X[Vérifier état serveur] + X --> Y[Restaurer config nginx] + Y --> Z[Renouveler certificats si nécessaire] + Z --> AA[Redémarrer services système] + end + + subgraph Validation["✅ Validation Post-Rollback"] + O --> AB[Health check API] + R --> AB + W --> AB + AA --> AB + AB --> AC[Tests de fumée] + AC --> AD{Tous tests OK ?} + AD -->|Non| AE[🚨 Escalade urgente] + AD -->|Oui| AF[✅ PREPROD restauré] + AE --> AG([Fin - Intervention manuelle]) + AF --> AH[Documenter incident] + AH --> AI([Fin - Succès]) + end + + style Analysis fill:#fff3e0 + style Validation fill:#e8f5e9 +``` + +### 4.3 Restauration PROD (Procédure complète) + +```mermaid +flowchart TD + subgraph Alert["🚨 Alerte Production"] + A([Incident PROD détecté]) --> B[Activer procédure incident] + B --> C[Notifier équipe on-call] + C --> D[Évaluer impact business] + end + + subgraph Triage["🔍 Triage"] + D --> E{Niveau de sévérité} + E -->|P1 - Critique| F[Activation immédiate rollback] + E -->|P2 - Majeur| G[Analyse rapide 15min max] + E -->|P3 - Mineur| H[Fix forward si possible] + G --> I{Fix rapide possible ?} + I -->|Oui| J[Appliquer hotfix] + I -->|Non| F + H --> K[Planifier fix] + end + + subgraph RollbackDecision["📋 Décision Rollback"] + F --> L[Récupérer point de rollback] + L --> M[versions.json du dernier backup] + M --> N{Type de rollback} + N -->|Complet| O[Rollback full stack] + N -->|Partiel Backend| P[Rollback backend seul] + N -->|Partiel Frontend| Q[Rollback frontend seul] + N -->|DB seulement| R[Rollback DB] + end + + subgraph FullRollback["🔄 Rollback Complet PROD"] + O --> S[Activer page maintenance] + S --> T["Notifier: Maintenance en cours"] + T --> U[Stopper tous les services] + U --> V[Restaurer images Docker backup] + V --> W["docker pull backend:backup-TIMESTAMP"] + W --> X["docker pull frontend:backup-TIMESTAMP"] + X --> Y[Restaurer DB depuis snapshot] + Y --> Z["pg_restore --clean -d thetiptop_prod"] + Z --> AA[Restaurer fichiers config] + AA --> AB[Redémarrer stack complète] + AB --> AC[Désactiver maintenance] + end + + subgraph PartialBackend["🔄 Rollback Backend"] + P --> AD["docker pull backend:backup-TIMESTAMP"] + AD --> AE[docker compose up -d backend] + end + + subgraph PartialFrontend["🔄 Rollback Frontend"] + Q --> AF["docker pull frontend:backup-TIMESTAMP"] + AF --> AG[docker compose up -d frontend] + end + + subgraph DBRollback["🗄️ Rollback DB PROD"] + R --> AH[Évaluer perte de données] + AH --> AI{Perte acceptable ?} + AI -->|Non| AJ[Récupération point-in-time] + AI -->|Oui| AK[Restauration standard] + AJ --> AL[WAL replay jusqu'au point] + AK --> AM["pg_restore backup quotidien"] + AL --> AN[Synchroniser avec backup] + AM --> AN + end + + subgraph Verification["✅ Vérification Post-Rollback"] + AC --> AO[Health checks complets] + AE --> AO + AG --> AO + AN --> AO + AO --> AP["Test: /health endpoint"] + AP --> AQ["Test: Login utilisateur"] + AQ --> AR["Test: Participation jeu"] + AR --> AS["Test: Validation employé"] + AS --> AT{Tous tests OK ?} + AT -->|Non| AU[🚨 Escalade niveau 2] + AT -->|Oui| AV[✅ PROD restauré] + end + + subgraph PostIncident["📝 Post-Incident"] + AV --> AW[Confirmer stabilité 30min] + AW --> AX[Notifier fin incident] + AX --> AY[Créer rapport incident] + AY --> AZ[Planifier post-mortem] + AZ --> BA([Fin]) + AU --> BB[Intervention manuelle équipe] + BB --> BC([Fin - Escalade]) + end + + style Alert fill:#ffebee + style FullRollback fill:#fff3e0 + style Verification fill:#e8f5e9 +``` + +### 4.4 Matrice de décision de restauration + +```mermaid +flowchart LR + subgraph Decision["📊 Matrice de Décision"] + A[Symptôme] --> B{API down ?} + B -->|Oui| C{Frontend OK ?} + C -->|Oui| D[Rollback Backend] + C -->|Non| E[Rollback Full] + B -->|Non| F{Erreurs 500 ?} + F -->|Oui| G{Récent déploiement ?} + G -->|Oui| H[Rollback dernier déploiement] + G -->|Non| I[Analyser logs] + F -->|Non| J{Données corrompues ?} + J -->|Oui| K[Rollback DB] + J -->|Non| L[Investiguer plus] + end +``` + +--- + +## Justification des processus + +### Pourquoi ces processus ? + +| Processus | Justification | +|-----------|---------------| +| **Déploiement multi-environnement** | Permet de tester les changements progressivement (dev → preprod → prod) avant la mise en production, réduisant les risques | +| **Tests automatisés avant déploiement** | Garantit que le code déployé respecte les standards de qualité et n'introduit pas de régressions | +| **Health checks post-déploiement** | Détecte rapidement les problèmes après un déploiement, permettant un rollback automatique si nécessaire | +| **Sauvegarde pré-déploiement** | Crée un point de restauration fiable avant chaque changement en production | +| **Sauvegarde quotidienne** | Protège contre la perte de données avec une fenêtre de perte maximale de 24h | +| **Procédures de rollback documentées** | Réduit le temps de restauration (RTO) en cas d'incident grâce à des procédures claires | +| **Politique de rétention** | Optimise l'espace de stockage tout en conservant l'historique nécessaire pour les audits | + +### Indicateurs clés (KPIs) + +| KPI | Objectif | Mesure | +|-----|----------|--------| +| **RTO** (Recovery Time Objective) | < 30 minutes | Temps pour restaurer le service | +| **RPO** (Recovery Point Objective) | < 24 heures | Perte de données maximale acceptable | +| **Fréquence de déploiement** | Quotidienne | Nombre de déploiements par jour | +| **Taux de succès déploiement** | > 95% | Déploiements réussis / Total | +| **MTTR** (Mean Time To Recovery) | < 1 heure | Temps moyen de récupération | + +--- + +## Outils recommandés pour visualiser ces diagrammes + +1. **Mermaid Live Editor**: https://mermaid.live/ +2. **draw.io**: Importer le code Mermaid +3. **VS Code**: Extension "Mermaid Preview" +4. **GitLab/GitHub**: Rendu natif dans les fichiers Markdown + +--- + +*Document généré pour le projet Thé Tip Top - Novembre 2024* diff --git a/scripts/apply-is-active-migration.js b/scripts/apply-is-active-migration.js new file mode 100644 index 00000000..fd4c69fb --- /dev/null +++ b/scripts/apply-is-active-migration.js @@ -0,0 +1,40 @@ +/** + * Script pour appliquer la migration is_active aux utilisateurs + */ +import { pool } from '../db.js'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +async function applyMigration() { + const client = await pool.connect(); + + try { + console.log('🚀 Début de la migration is_active...'); + + // Lire le fichier SQL + const sqlPath = path.join(__dirname, '../database/migrations/add-is-active-to-users.sql'); + const sql = fs.readFileSync(sqlPath, 'utf8'); + + // Exécuter la migration + await client.query(sql); + + console.log('✅ Migration is_active appliquée avec succès!'); + + // Vérifier le résultat + const result = await client.query('SELECT COUNT(*) as total, COUNT(CASE WHEN is_active = true THEN 1 END) as active FROM users'); + console.log(`📊 Utilisateurs: ${result.rows[0].total} total, ${result.rows[0].active} actifs`); + + } catch (error) { + console.error('❌ Erreur lors de la migration:', error.message); + throw error; + } finally { + client.release(); + await pool.end(); + } +} + +applyMigration(); diff --git a/src/controllers/admin.controller.js b/src/controllers/admin.controller.js index 5104511b..5ac58b9c 100644 --- a/src/controllers/admin.controller.js +++ b/src/controllers/admin.controller.js @@ -359,10 +359,10 @@ export const deletePrize = asyncHandler(async (req, res, next) => { * GET /api/admin/users */ export const getAllUsers = asyncHandler(async (req, res) => { - const { page = 1, limit = 10, role } = req.query; + const { page = 1, limit = 10, role, isActive } = req.query; const offset = (page - 1) * limit; - // Construction de la requête avec filtrage optionnel par rôle + // Construction de la requête avec filtrage optionnel let queryText = ` SELECT u.id, @@ -372,6 +372,7 @@ export const getAllUsers = asyncHandler(async (req, res) => { u.phone, u.role, u.is_verified, + u.is_active, u.created_at, COUNT(t.id) as tickets_count FROM users u @@ -379,26 +380,52 @@ export const getAllUsers = asyncHandler(async (req, res) => { `; const queryParams = []; + const whereClauses = []; let paramIndex = 1; // Ajouter le filtre par rôle si présent if (role) { - queryText += ` WHERE u.role = $${paramIndex}`; + whereClauses.push(`u.role = $${paramIndex}`); queryParams.push(role); paramIndex++; } + // Ajouter le filtre par statut actif si présent + if (isActive !== undefined) { + whereClauses.push(`u.is_active = $${paramIndex}`); + queryParams.push(isActive === 'true'); + paramIndex++; + } + + if (whereClauses.length > 0) { + queryText += ` WHERE ${whereClauses.join(' AND ')}`; + } + queryText += ` GROUP BY u.id ORDER BY u.created_at DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`; queryParams.push(limit, offset); const result = await pool.query(queryText, queryParams); - // Compter le total avec le même filtre + // Compter le total avec les mêmes filtres let countQuery = 'SELECT COUNT(*) FROM users'; + const countWhereClauses = []; let countParams = []; + let countParamIndex = 1; + if (role) { - countQuery += ' WHERE role = $1'; + countWhereClauses.push(`role = $${countParamIndex}`); countParams.push(role); + countParamIndex++; + } + + if (isActive !== undefined) { + countWhereClauses.push(`is_active = $${countParamIndex}`); + countParams.push(isActive === 'true'); + countParamIndex++; + } + + if (countWhereClauses.length > 0) { + countQuery += ` WHERE ${countWhereClauses.join(' AND ')}`; } const countResult = await pool.query(countQuery, countParams); @@ -469,6 +496,7 @@ export const getUserById = asyncHandler(async (req, res, next) => { u.postal_code, u.role, u.is_verified, + u.is_active, u.created_at, u.date_of_birth, u.gender, @@ -498,7 +526,7 @@ export const getUserById = asyncHandler(async (req, res, next) => { */ export const updateUser = asyncHandler(async (req, res, next) => { const { id } = req.params; - const { role, isVerified } = req.body; + const { role, isVerified, isActive } = req.body; // Construire la requête dynamiquement const updates = []; @@ -513,6 +541,10 @@ export const updateUser = asyncHandler(async (req, res, next) => { updates.push(`is_verified = $${paramCount++}`); values.push(isVerified); } + if (isActive !== undefined) { + updates.push(`is_active = $${paramCount++}`); + values.push(isActive); + } if (updates.length === 0) { return next(new AppError('Aucune modification à apporter', 400)); @@ -524,7 +556,7 @@ export const updateUser = asyncHandler(async (req, res, next) => { UPDATE users SET ${updates.join(', ')} WHERE id = $${paramCount} - RETURNING id, email, first_name, last_name, role, is_verified + RETURNING id, email, first_name, last_name, role, is_verified, is_active `; const result = await pool.query(query, values); diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index da22352d..a975b5c9 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -13,7 +13,7 @@ export const getProfile = asyncHandler(async (req, res) => { const userId = req.user.id; const result = await pool.query( - `SELECT id, email, first_name, last_name, phone, address, city, postal_code, role, is_verified, created_at, updated_at + `SELECT id, email, first_name, last_name, phone, address, city, postal_code, role, is_verified, is_active, created_at, updated_at FROM users WHERE id = $1`, [userId] ); @@ -33,6 +33,7 @@ export const getProfile = asyncHandler(async (req, res) => { postalCode: user.postal_code, role: user.role, isVerified: user.is_verified, + isActive: user.is_active, createdAt: user.created_at, updatedAt: user.updated_at, }, @@ -45,7 +46,7 @@ export const getProfile = asyncHandler(async (req, res) => { */ export const updateProfile = asyncHandler(async (req, res) => { const userId = req.user.id; - const { firstName, lastName, phone, address, city, postalCode } = req.body; + const { firstName, lastName, phone, address, city, postalCode, isActive } = req.body; // Construire la requête dynamiquement const updates = []; @@ -76,6 +77,10 @@ export const updateProfile = asyncHandler(async (req, res) => { updates.push(`postal_code = $${paramCount++}`); values.push(postalCode); } + if (isActive !== undefined) { + updates.push(`is_active = $${paramCount++}`); + values.push(isActive); + } if (updates.length === 0) { return res.json({ @@ -92,7 +97,7 @@ export const updateProfile = asyncHandler(async (req, res) => { UPDATE users SET ${updates.join(', ')} WHERE id = $${paramCount} - RETURNING id, email, first_name, last_name, phone, address, city, postal_code, role, is_verified, created_at, updated_at + RETURNING id, email, first_name, last_name, phone, address, city, postal_code, role, is_verified, is_active, created_at, updated_at `; const result = await pool.query(query, values); @@ -112,6 +117,7 @@ export const updateProfile = asyncHandler(async (req, res) => { postalCode: user.postal_code, role: user.role, isVerified: user.is_verified, + isActive: user.is_active, createdAt: user.created_at, updatedAt: user.updated_at, },