feat: add user archiving (soft delete) with is_active field
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
fa0f2579ba
commit
9d836eeaac
14
database/migrations/add-is-active-to-users.sql
Normal file
14
database/migrations/add-is-active-to-users.sql
Normal file
|
|
@ -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;
|
||||
834
docs/BPMN_DIAGRAMS.md
Normal file
834
docs/BPMN_DIAGRAMS.md
Normal file
|
|
@ -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*
|
||||
40
scripts/apply-is-active-migration.js
Normal file
40
scripts/apply-is-active-migration.js
Normal file
|
|
@ -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();
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user