Compare commits

..

1 Commits
dev ... main

Author SHA1 Message Date
451cd67455 feat: add HTTP metrics middleware for Prometheus
- Add src/middleware/metrics.js with http_requests_total, http_errors_total, etc.
- Import and use metricsMiddleware in index.js

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 18:10:21 +01:00
9025 changed files with 1166520 additions and 20935 deletions

25
.env
View File

@ -1,28 +1,15 @@
DB_HOST=51.75.24.29
DB_PORT=5433
DB_HOST=the-tip-top-db
DB_USER=postgres
DB_PASS=postgres
DB_NAME=thetiptop_dev
DB_NAME=thetiptop
PORT=4000
JWT_SECRET=thetiptopsecret
GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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
FACEBOOK_APP_ID=836681122652445
FACEBOOK_APP_SECRET=e6889f4339d140c218f1df177149893f
GOOGLE_CLIENT_ID=546665126481-nmfcsbrjm6an7tbnv2jbspihgtu72ifn.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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
FACEBOOK_APP_ID=ton_app_id_facebook
FACEBOOK_APP_SECRET=ton_secret_facebook
JWT_SECRET=thetiptopsecret
SESSION_SECRET=thetiptopsessionsecret
# Frontend URL (pour les liens dans les emails)
FRONTEND_URL=http://localhost:3000
# Email Configuration (SMTP)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=thetiptopgr3@gmail.com
SMTP_PASS=xydqvyrxcwwsiups
EMAIL_FROM=thetiptopgr3@gmail.com
# reCAPTCHA v2 (obtenir les clés sur https://www.google.com/recaptcha/admin)
RECAPTCHA_SECRET_KEY=6Le2Gx8sAAAAABh58WaZuajmkQsf4imY8ees_Zaq

View File

@ -1,32 +0,0 @@
# Environnement DEV
NODE_ENV=development
DB_HOST=51.75.24.29
DB_PORT=5433
DB_USER=postgres
DB_PASS=postgres
DB_NAME=thetiptop_dev
PORT=4000
# URLs Frontend/Backend
FRONTEND_URL=https://dev.dsp5-archi-o24a-15m-g3.fr
BACKEND_URL=https://api.dev.dsp5-archi-o24a-15m-g3.fr
# JWT
JWT_SECRET=thetiptopsecret_dev
SESSION_SECRET=thetiptopsessionsecret_dev
# OAuth (à configurer avec les URLs de dev)
GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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
FACEBOOK_APP_ID=836681122652445
FACEBOOK_APP_SECRET=e6889f4339d140c218f1df177149893f
# Email Configuration (SMTP)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=thetiptopgr3@gmail.com
SMTP_PASS=xydqvyrxcwwsiups
EMAIL_FROM=thetiptopgr3@gmail.com
# reCAPTCHA v2
RECAPTCHA_SECRET_KEY=6Le2Gx8sAAAAABh58WaZuajmkQsf4imY8ees_Zaq

View File

@ -1,32 +0,0 @@
# Environnement PREPROD
NODE_ENV=preproduction
DB_HOST=51.75.24.29
DB_PORT=5434
DB_USER=postgres
DB_PASS=postgres
DB_NAME=thetiptop_preprod
PORT=4000
# URLs Frontend/Backend
FRONTEND_URL=https://preprod.dsp5-archi-o24a-15m-g3.fr
BACKEND_URL=https://api.preprod.dsp5-archi-o24a-15m-g3.fr
# JWT (Secrets générés automatiquement)
JWT_SECRET=koN619HNfIgBKrCRl6o1feaVg30GPHQR+4DvbCoxmlA=
SESSION_SECRET=DXgw+RHWLjW40G33fe5NqPnzwMkKf7bVxx3wZROkypM=
# OAuth
GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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
FACEBOOK_APP_ID=836681122652445
FACEBOOK_APP_SECRET=e6889f4339d140c218f1df177149893f
# Email Configuration (SMTP)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=thetiptopgr3@gmail.com
SMTP_PASS=xydqvyrxcwwsiups
EMAIL_FROM=thetiptopgr3@gmail.com
# reCAPTCHA v2
RECAPTCHA_SECRET_KEY=6Le2Gx8sAAAAABh58WaZuajmkQsf4imY8ees_Zaq

View File

@ -1,32 +0,0 @@
# Environnement PRODUCTION
NODE_ENV=production
DB_HOST=51.75.24.29
DB_PORT=5432
DB_USER=postgres
DB_PASS=postgres
DB_NAME=thetiptop
PORT=4000
# URLs Frontend/Backend
FRONTEND_URL=https://dsp5-archi-o24a-15m-g3.fr
BACKEND_URL=https://api.dsp5-archi-o24a-15m-g3.fr
# JWT (Secrets générés automatiquement)
JWT_SECRET=v0vIxcahaOdJMzqoKjRpCGkRFyVTVDJyk7s2F1LIXu0=
SESSION_SECRET=BzTTnx+GxsBpdVMcBKH5Vo6gH7h+/7tlZ67nppNduoY=
# OAuth
GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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
FACEBOOK_APP_ID=836681122652445
FACEBOOK_APP_SECRET=e6889f4339d140c218f1df177149893f
# Email Configuration (SMTP)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=thetiptopgr3@gmail.com
SMTP_PASS=xydqvyrxcwwsiups
EMAIL_FROM=thetiptopgr3@gmail.com
# reCAPTCHA v2
RECAPTCHA_SECRET_KEY=6Le2Gx8sAAAAABh58WaZuajmkQsf4imY8ees_Zaq

35
.gitignore vendored
View File

@ -1,35 +0,0 @@
# Dependencies
node_modules/
# Build
dist/
build/
# Environment files
.env
.env.local
.env.*.local
.env.prod
.env.dev
.env.preprod
# Logs
logs/
*.log
npm-debug.log*
# Coverage
coverage/
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Test
*.test.js.snap

View File

@ -1,64 +0,0 @@
# Dependencies
node_modules/
**/node_modules/**
# Build outputs
dist/
build/
# Testing
coverage/
.nyc_output/
# Database
database/
*.sql
# Scripts
scripts/
*.sh
*.bat
*.ps1
# Configuration files
*.config.js
jest.config.js
eslint.config.js
# Environment files
.env*
# Logs
logs/
*.log
npm-debug.log*
backend.log
# Test files
test-*.js
*.test.js
*.spec.js
# IDE
.vscode/
.idea/
# Docker
Dockerfile
docker-compose*.yml
# CI/CD
Jenkinsfile
.github/
# Documentation
*.md
postman-collection.json
# Public files
public/
# Temporary files
*.tmp
*.bak
*.backup

File diff suppressed because it is too large Load Diff

View File

@ -1,197 +0,0 @@
# 🚀 Workflow de Déploiement - Thé Tip Top
## 📊 Vue d'ensemble des environnements
| Environnement | Branche | Base de données | Port DB | URLs |
|---------------|---------|-----------------|---------|------|
| **DEV** | `dev` | `thetiptop_dev` | 5433 | https://dev.dsp5-archi-o24a-15m-g3.fr<br>https://api.dev.dsp5-archi-o24a-15m-g3.fr |
| **PREPROD** | `preprod` | `thetiptop_preprod` | 5434 | https://preprod.dsp5-archi-o24a-15m-g3.fr<br>https://api.preprod.dsp5-archi-o24a-15m-g3.fr |
| **PROD** | `main` | `thetiptop` | 5432 | https://dsp5-archi-o24a-15m-g3.fr<br>https://api.dsp5-archi-o24a-15m-g3.fr |
---
## 🔄 Workflow Git
```
dev → preprod → main
↓ ↓ ↓
DEV PREPROD PROD
```
---
## 📝 Procédure de déploiement
### 1⃣ **Développement sur DEV**
```bash
# Vous êtes sur la branche dev
git checkout dev
# Faire vos modifications...
# Commit et push vers dev
git add .
git commit -m "feat: description de la fonctionnalité"
git push origin dev
```
**Déploiement DEV :**
```bash
# Sur le serveur DEV
cd /path/to/backend
git pull origin dev
cp .env.dev .env
npm install
npm run dev
```
---
### 2⃣ **Déploiement vers PREPROD**
Après avoir testé sur DEV et validé les fonctionnalités :
```bash
# Merger dev vers preprod
git checkout preprod
git pull origin preprod
git merge dev
git push origin preprod
```
**Déploiement PREPROD :**
```bash
# Sur le serveur PREPROD
cd /path/to/backend
git pull origin preprod
cp .env.preprod .env
npm install
npm run dev
```
**⚠️ Tests à effectuer en PREPROD :**
- [ ] Toutes les fonctionnalités principales
- [ ] Authentification (Google, Facebook)
- [ ] Jeu et attribution des lots
- [ ] Interface admin
- [ ] Tirage au sort
- [ ] Performance et sécurité
---
### 3⃣ **Déploiement en PRODUCTION**
Après validation complète en PREPROD :
```bash
# Merger preprod vers main
git checkout main
git pull origin main
git merge preprod
git push origin main
```
**Déploiement PRODUCTION :**
```bash
# Sur le serveur PRODUCTION
cd /path/to/backend
git pull origin main
cp .env.production .env
# ⚠️ IMPORTANT : Vérifier les secrets en production
nano .env # Vérifier JWT_SECRET, SESSION_SECRET, etc.
npm install
npm run build # Si nécessaire
npm start
```
---
## 🗄️ Gestion des bases de données
### Migration de données DEV → PREPROD
```bash
# Dump de la base DEV
pg_dump -h 51.75.24.29 -p 5433 -U postgres -d thetiptop_dev > dev_dump.sql
# Restaurer en PREPROD
psql -h 51.75.24.29 -p 5434 -U postgres -d thetiptop_preprod < dev_dump.sql
```
### Migration de données PREPROD → PROD
```bash
# Dump de la base PREPROD
pg_dump -h 51.75.24.29 -p 5434 -U postgres -d thetiptop_preprod > preprod_dump.sql
# ⚠️ BACKUP PROD avant restauration !
pg_dump -h 51.75.24.29 -p 5432 -U postgres -d thetiptop > prod_backup_$(date +%Y%m%d).sql
# Restaurer en PROD
psql -h 51.75.24.29 -p 5432 -U postgres -d thetiptop < preprod_dump.sql
```
---
## 🔐 Configuration des secrets
### Secrets à changer ABSOLUMENT en PRODUCTION :
- `JWT_SECRET` - Générer avec : `openssl rand -base64 32`
- `SESSION_SECRET` - Générer avec : `openssl rand -base64 32`
- `NEXTAUTH_SECRET` - Générer avec : `openssl rand -base64 32`
### OAuth en production :
1. **Google OAuth** : Créer un nouveau projet sur Google Cloud Console avec les URLs de prod
2. **Facebook OAuth** : Créer une nouvelle app Facebook avec les URLs de prod
---
## ✅ Checklist avant déploiement PRODUCTION
- [ ] Tous les tests passent en PREPROD
- [ ] Les secrets ont été changés (JWT, SESSION)
- [ ] OAuth configuré avec les URLs de production
- [ ] Backup de la base de données PROD effectué
- [ ] Variables d'environnement vérifiées
- [ ] CORS configuré avec les domaines de production
- [ ] Logs configurés pour la production
- [ ] Plan de rollback préparé
---
## 🚨 Rollback en cas de problème
### Si problème en PRODUCTION :
```bash
# 1. Revenir à la version précédente
git checkout main
git reset --hard <commit-précédent>
git push origin main --force
# 2. Restaurer le backup de la base de données
psql -h 51.75.24.29 -p 5432 -U postgres -d thetiptop < prod_backup_YYYYMMDD.sql
# 3. Redémarrer l'application
npm start
```
---
## 📞 Support
En cas de problème, vérifier :
1. Les logs du serveur
2. Les logs de la base de données
3. Les variables d'environnement
4. La configuration CORS
5. Les certificats SSL
---
**Dernière mise à jour** : $(date +%Y-%m-%d)

View File

@ -1,16 +1,11 @@
# Utilise une image Node légère
FROM node:18-alpine
WORKDIR /app
# Copie les fichiers
COPY package*.json ./
RUN npm install --omit=dev
RUN npm ci --omit=dev
COPY . .
# Définit la variable denvironnement
ENV NODE_ENV=production
EXPOSE 4000
CMD ["npm", "start"]

View File

@ -1,95 +0,0 @@
# Configuration du Declenchement Automatique Jenkins
## Probleme Actuel
Le pipeline Jenkins ne se lance pas automatiquement apres un push Git.
## Solutions Possibles
### Solution 1: Verifier le Plugin Generic Webhook Trigger
1. Aller dans Jenkins > Manage Jenkins > Manage Plugins
2. Chercher "Generic Webhook Trigger Plugin"
3. Si pas installe, l'installer et redemarrer Jenkins
### Solution 2: Configuration du Webhook dans Gitea
#### Etape 1: Creer le webhook dans Gitea
1. Aller sur https://gitea.wk-archi-o24a-15m-g3.fr/wkadmin/the-tip-top-backend
2. Settings > Webhooks > Add Webhook > Gitea
3. Configurer:
- **Target URL**: `http://jenkins-url/generic-webhook-trigger/invoke?token=the-tip-top-backend-token`
- **HTTP Method**: POST
- **POST Content Type**: application/json
- **Secret**: (laisser vide)
- **Trigger On**: Push events
- **Branch filter**: `dev` (ou `*` pour toutes les branches)
- **Active**: Coche
4. Cliquer sur "Add Webhook"
#### Etape 2: Tester le webhook
1. Faire un commit et push
2. Dans Gitea, aller dans Settings > Webhooks
3. Cliquer sur le webhook cree
4. Scroller vers le bas pour voir "Recent Deliveries"
5. Verifier que la reponse est 200 OK
### Solution 3: Verifier la Configuration SCM dans Jenkins
1. Aller dans le job Jenkins: `the-tip-top-backend`
2. Configure > Build Triggers
3. Verifier que "Poll SCM" est coche avec: `* * * * *`
4. Verifier que "Generic Webhook Trigger" est configure
### Solution 4: Forcer un Scan Manuel (Temporaire)
En attendant que le webhook fonctionne:
1. Aller dans le job Jenkins
2. Cliquer sur "Build with Parameters"
3. Choisir ENV = dev
4. Cliquer sur "Build"
### Solution 5: Utiliser un Simple Webhook au lieu de Generic Webhook
Si le plugin Generic Webhook Trigger pose probleme, modifier le Jenkinsfile:
```groovy
triggers {
// Polling SCM toutes les minutes
pollSCM('* * * * *')
// OU utiliser le trigger Gitea (plus simple)
// Pas besoin de plugin supplementaire
}
properties([
pipelineTriggers([
[$class: 'GitHubPushTrigger']
])
])
```
Puis dans Gitea webhook, utiliser:
- **Target URL**: `http://jenkins-url/git/notifyCommit?url=https://gitea.wk-archi-o24a-15m-g3.fr/wkadmin/the-tip-top-backend.git`
## Verification
Pour verifier que le trigger fonctionne:
1. Faire un petit changement (ex: ajouter un commentaire dans README.md)
2. Commit et push
3. Attendre 1 minute maximum
4. Verifier dans Jenkins si un nouveau build demarre
## Logs de Debug
Si le probleme persiste, verifier les logs Jenkins:
1. Jenkins > Manage Jenkins > System Log
2. Chercher les erreurs liees a "SCM" ou "webhook"
## Contact
Si aucune solution ne fonctionne:
- Verifier que Jenkins a acces au repo Git (credentials)
- Verifier les firewalls entre Gitea et Jenkins
- Verifier que l'URL du webhook est accessible depuis Gitea

View File

@ -1,169 +0,0 @@
# Configuration du Webhook Jenkins pour Auto-déploiement (Backend)
## 📋 Vue d'ensemble
Le Jenkinsfile a été configuré avec deux méthodes de déclenchement automatique :
1. **Polling SCM** (Simple mais moins efficace)
2. **Webhook Gitea** (Recommandé - plus rapide et efficace)
---
## ⚙️ Option 1 : Polling SCM (Déjà actif)
Le pipeline vérifie automatiquement les changements **toutes les minutes**.
**Avantages :**
- ✅ Aucune configuration supplémentaire nécessaire
- ✅ Fonctionne immédiatement
**Inconvénients :**
- ❌ Délai de détection (jusqu'à 1 minute)
- ❌ Charge sur le serveur Git
---
## 🚀 Option 2 : Webhook Gitea (Recommandé)
### Configuration dans Gitea
1. **Accéder aux paramètres du dépôt :**
- Aller sur : `https://gitea.wk-archi-o24a-15m-g3.fr/wkadmin/the-tip-top-backend`
- Cliquer sur **Paramètres** (Settings) → **Webhooks**
2. **Créer un nouveau webhook :**
- Cliquer sur **Ajouter un Webhook** → **Gitea**
- Remplir les champs suivants :
```
URL cible : http://<JENKINS_URL>/generic-webhook-trigger/invoke?token=the-tip-top-backend-token
Méthode HTTP : POST
Type de contenu : application/json
Secret : (laisser vide ou utiliser un secret personnalisé)
Événements déclencheurs :
☑ Push
☐ Create
☐ Delete
☐ Fork
☐ Issues
☐ Pull Request
☐ Release
Actif : ☑ Oui
```
3. **Remplacer `<JENKINS_URL>` par :**
- L'URL de votre serveur Jenkins (ex: `jenkins.wk-archi-o24a-15m-g3.fr`)
4. **Tester le webhook :**
- Après création, cliquer sur le webhook
- Cliquer sur **Tester la livraison**
- Vérifier que le statut est **200 OK**
---
## 🔐 Sécurité
Le token `the-tip-top-backend-token` dans le Jenkinsfile protège le webhook contre les déclenchements non autorisés.
Pour changer le token :
1. Modifier la ligne dans `Jenkinsfile` :
```groovy
token: 'votre-nouveau-token-secret'
```
2. Mettre à jour l'URL du webhook dans Gitea
---
## 🧪 Tester le déclenchement automatique
1. Faire un commit et push :
```bash
git add .
git commit -m "test: trigger auto deployment"
git push origin dev
```
2. Vérifier dans Jenkins :
- Le build devrait démarrer automatiquement
- Consulter les logs pour voir le message : `Triggered by Gitea push`
---
## 📊 Branches et Environnements
Le pipeline détecte automatiquement l'environnement selon la branche :
| Branche | Environnement | Tag Docker |
|-----------|---------------|---------------|
| `dev` | dev | dev-latest |
| `preprod` | preprod | preprod-latest|
| `main` | prod | prod-latest |
---
## 🛠️ Plugin Jenkins requis
Pour utiliser le webhook avec `GenericTrigger`, assurez-vous que le plugin suivant est installé :
- **Generic Webhook Trigger Plugin**
Installation :
1. Jenkins → Manage Jenkins → Manage Plugins
2. Onglet "Available" → Rechercher "Generic Webhook Trigger"
3. Installer et redémarrer Jenkins
---
## 📝 Notes
- Le polling SCM (`* * * * *`) restera actif comme solution de secours
- Vous pouvez le désactiver une fois le webhook configuré en commentant la ligne `pollSCM`
- Les deux méthodes peuvent coexister sans problème
---
## ⚡ Étapes de déploiement automatique
Lorsqu'un push est détecté, le pipeline exécute automatiquement :
1. **Init** - Détection de l'environnement (dev/preprod/prod)
2. **Checkout** - Récupération du code
3. **Tests & Qualité** - Linting et tests unitaires
4. **Build Docker** - Construction de l'image Docker
5. **Push Registry** - Envoi vers le registre Docker
6. **Backup** - Sauvegarde de l'environnement actuel
7. **Deploy** - Déploiement de la nouvelle version
---
## ❓ Troubleshooting
### Le webhook ne se déclenche pas
1. Vérifier les logs du webhook dans Gitea (Paramètres → Webhooks → Livraisons)
2. Vérifier que Jenkins est accessible depuis le serveur Gitea
3. Vérifier que le token correspond entre Jenkinsfile et l'URL du webhook
4. Vérifier les logs Jenkins pour les erreurs
### Le build démarre mais échoue
1. Vérifier les credentials Docker registry
2. Vérifier les permissions d'accès au répertoire de déploiement
3. Vérifier la connexion à la base de données
4. Consulter les logs détaillés du build dans Jenkins
### La base de données n'est pas accessible
Si vous voyez l'erreur `connect ETIMEDOUT 51.75.24.29:5433` :
1. Vérifier que PostgreSQL est démarré sur le serveur
2. Vérifier les règles de firewall (port 5433)
3. Vérifier les credentials dans le fichier `.env`
---
Généré avec [Claude Code](https://claude.com/claude-code)

296
Jenkinsfile vendored
View File

@ -1,75 +1,25 @@
/**
* ============================================================================
* PIPELINE CI/CD - THÉ TIP TOP BACKEND
* ============================================================================
*
* Ce pipeline automatise le processus de déploiement continu du backend.
*
* ÉTAPES DU PIPELINE :
* 1. Init - Détection de l'environnement (dev/preprod/prod)
* 2. Checkout - Récupération du code source depuis Git
* 3. Install - Installation des dépendances Node.js
* 4. Lint & Tests - ESLint + Jest avec couverture de code
* 5. SonarQube - Analyse statique avec rapport de couverture
* 6. Build - Construction de l'image Docker
* 7. Push - Envoi de l'image vers le registre Docker privé
* 8. Backup - Sauvegarde de la base de données (prod uniquement)
* 9. Deploy - Déploiement sur le serveur cible
*
* ENVIRONNEMENTS :
* - dev : Développement (branche dev)
* - preprod : Pré-production (branche preprod)
* - prod : Production (branche main)
*
* ============================================================================
*/
pipeline {
agent any
// =========================================================================
// DÉCLENCHEUR : Vérifie les changements Git chaque minute
// =========================================================================
triggers {
pollSCM('* * * * *')
}
// =========================================================================
// PARAMÈTRES : Permet de choisir manuellement l'environnement
// =========================================================================
parameters {
choice(
name: 'ENV',
choices: ['dev', 'preprod', 'prod'],
description: 'Choisir l environnement de deploiement'
description: 'Choisir lenvironnement de déploiement'
)
}
// =========================================================================
// VARIABLES D'ENVIRONNEMENT GLOBALES
// =========================================================================
environment {
REGISTRY_URL = "registry.wk-archi-o24a-15m-g3.fr"
IMAGE_NAME = "the-tip-top-backend"
NPM_CACHE = "/var/jenkins_home/npm-cache"
}
stages {
// =====================================================================
// ÉTAPE 1 : INITIALISATION
// ---------------------------------------------------------------------
// But : Détecter automatiquement l'environnement selon la branche Git
// - Branche 'dev' → Environnement dev
// - Branche 'preprod' → Environnement preprod
// - Branche 'main' → Environnement prod
// =====================================================================
stage('🧭 Init - Détection environnement') {
stage('Init') {
steps {
script {
// Utiliser BRANCH_NAME (Multibranch Pipeline) ou GIT_BRANCH (fallback)
// git rev-parse --abbrev-ref HEAD retourne "HEAD" en detached HEAD mode
def currentBranch = env.BRANCH_NAME ?: env.GIT_BRANCH?.replaceAll('origin/', '') ?: sh(script: "git rev-parse --abbrev-ref HEAD", returnStdout: true).trim()
def currentBranch = sh(script: "git rev-parse --abbrev-ref HEAD", returnStdout: true).trim()
echo "🧭 Branche détectée : ${currentBranch}"
if (["dev", "preprod", "main"].contains(currentBranch)) {
@ -82,143 +32,53 @@ pipeline {
env.DEPLOY_PATH = "/srv/devops/the-tip-top/${env.ENV}"
echo """
╔══════════════════════════════════════════╗
║ CONFIGURATION PIPELINE ║
╠══════════════════════════════════════════╣
║ 🌍 Environnement : ${env.ENV.padRight(18)} ║
║ 🏷️ Tag Docker : ${env.TAG.padRight(18)} ║
║ 📂 Chemin déploie. : ${env.DEPLOY_PATH.take(18).padRight(18)} ║
╚══════════════════════════════════════════╝
🌍 Environnement = ${env.ENV}
🏷️ Tag Docker = ${env.TAG}
📂 Chemin de déploiement = ${env.DEPLOY_PATH}
"""
sh "ls -l ${env.DEPLOY_PATH} || echo '⚠️ Dossier non accessible depuis Jenkins'"
}
}
}
// =====================================================================
// ÉTAPE 2 : CHECKOUT
// ---------------------------------------------------------------------
// But : Récupérer le code source depuis le dépôt Gitea
// Action : Clone ou pull du code selon l'état du workspace
// =====================================================================
stage('📦 Checkout - Récupération code source') {
stage('Checkout') {
steps {
echo "📦 Récupération du code source depuis Gitea..."
echo "📦 Récupération du code source..."
checkout scm
echo "✅ Code source récupéré avec succès"
}
}
// =====================================================================
// ÉTAPE 3 : INSTALLATION DES DÉPENDANCES
// ---------------------------------------------------------------------
// But : Installer les packages Node.js nécessaires
// Container : node:18-alpine (léger et rapide)
// Commande : npm ci (installation propre depuis package-lock.json)
// Cache : Utilise un cache NPM partagé pour accélérer les builds
// =====================================================================
stage('📦 Install - Dépendances Node.js') {
stage('Tests & Qualité') {
agent {
docker {
image 'node:18-alpine'
args "-u root -v ${NPM_CACHE}:/root/.npm"
}
}
steps {
echo "📦 Installation des dépendances Node.js..."
sh 'npm ci --prefer-offline'
stash includes: 'node_modules/**', name: 'node_modules'
echo "✅ Dépendances installées avec succès"
}
}
// =====================================================================
// ÉTAPE 4 : SONARQUBE - Qualité de code
// ---------------------------------------------------------------------
// Analyse statique du code pour détecter :
// - Bugs potentiels
// - Vulnérabilités de sécurité
// - Code smells (mauvaises pratiques)
// - Duplications de code
// =====================================================================
stage('📊 SonarQube Analysis') {
agent {
docker {
image 'sonarsource/sonar-scanner-cli:latest'
args '-u root'
}
}
steps {
echo "📊 Analyse SonarQube - Qualité de code..."
withSonarQubeEnv('SonarQube') {
sh """
sonar-scanner
"""
}
echo "✅ Analyse SonarQube terminée"
echo "🧪 Lancement des tests et analyse..."
sh '''
npm ci
npm run lint || echo "⚠️ Erreurs de lint détectées"
npm test || echo "⚠️ Tests échoués — ignorés pour le déploiement"
'''
}
}
// =====================================================================
// ÉTAPE 5 : LINT & TESTS
// ---------------------------------------------------------------------
// ESLint : Vérifie le style et les erreurs de code
// Jest : Exécute les tests unitaires et d'intégration
// Couverture : Génère un rapport de couverture (lcov)
// =====================================================================
stage('🧪 Lint & Tests') {
agent {
docker {
image 'node:18-alpine'
args "-u root -v ${NPM_CACHE}:/root/.npm"
}
}
stage('Build Docker image') {
steps {
unstash 'node_modules'
echo "🧪 Lancement ESLint et Jest..."
script {
def lintStatus = sh(script: 'npm run lint', returnStatus: true)
def testStatus = sh(script: 'npm test -- --coverage', returnStatus: true)
if (lintStatus != 0) {
error "❌ ESLint a échoué - Corrigez les erreurs de style"
}
if (testStatus != 0) {
error "❌ Les tests ont échoué - Vérifiez les tests unitaires"
}
echo "✅ Lint et tests passés avec succès"
}
}
}
// =====================================================================
// ÉTAPE 6 : BUILD IMAGE DOCKER
// ---------------------------------------------------------------------
// But : Construire l'image Docker du backend
// Tags créés :
// - {env}-latest (ex: dev-latest, prod-latest)
// - latest
// =====================================================================
stage('🐳 Build - Image Docker') {
steps {
echo "🐳 Construction de l'image Docker backend..."
echo "🐳 Construction de limage Docker backend..."
sh """
docker build -t ${REGISTRY_URL}/${IMAGE_NAME}:${TAG} .
docker tag ${REGISTRY_URL}/${IMAGE_NAME}:${TAG} ${REGISTRY_URL}/${IMAGE_NAME}:latest
"""
echo "✅ Image Docker construite : ${REGISTRY_URL}/${IMAGE_NAME}:${TAG}"
}
}
// =====================================================================
// ÉTAPE 7 : PUSH VERS LE REGISTRE
// ---------------------------------------------------------------------
// But : Envoyer l'image Docker vers le registre privé
// Registre : registry.wk-archi-o24a-15m-g3.fr
// Authentification : Credentials Jenkins sécurisés
// =====================================================================
stage('📤 Push - Registre Docker') {
stage('Push to Registry') {
steps {
echo "📤 Envoi de l'image vers le registre Docker privé..."
echo "📤 Envoi de limage vers le registre..."
withCredentials([usernamePassword(credentialsId: 'registry-credentials', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS')]) {
sh """
echo "$REG_PASS" | docker login ${REGISTRY_URL} -u "$REG_USER" --password-stdin
@ -226,109 +86,51 @@ pipeline {
docker push ${REGISTRY_URL}/${IMAGE_NAME}:latest
"""
}
echo "✅ Image envoyée avec succès vers ${REGISTRY_URL}"
}
}
// =====================================================================
// ÉTAPE 8 : BACKUP BASE DE DONNÉES (Production uniquement)
// ---------------------------------------------------------------------
// But : Sauvegarder la base de données avant le déploiement
// Condition : Seulement en environnement de production
// Script : /srv/devops/the-tip-top/backup.sh
// Sécurité : Continue même si le backup échoue (warning)
// =====================================================================
stage('💾 Backup - Base de données') {
when {
expression { env.ENV == 'prod' }
}
stage('Deploy') {
steps {
echo "💾 Sauvegarde de la base de données PostgreSQL..."
sh """
/srv/devops/the-tip-top/backup.sh || echo "⚠️ Backup échoué mais on continue"
"""
echo "✅ Backup de la base de données terminé"
}
}
// =====================================================================
// ÉTAPE 9 : DÉPLOIEMENT
// ---------------------------------------------------------------------
// But : Déployer le backend sur le serveur cible
// Actions :
// 1. Pull de la nouvelle image depuis le registre
// 2. Recréation du container avec la nouvelle image
// Chemins : /srv/devops/the-tip-top/{dev|preprod|prod}
// =====================================================================
stage('🚀 Deploy - Mise en production') {
steps {
echo "🚀 Déploiement du backend sur l'environnement ${env.ENV}..."
echo "🚀 Déploiement sur ${env.ENV}..."
sh """
cd "${DEPLOY_PATH}"
docker compose pull backend
docker compose up -d --force-recreate backend
"""
echo "✅ Backend déployé avec succès sur ${env.ENV}"
}
}
stage('Health Check') {
steps {
echo "🩺 Vérification de létat du backend..."
script {
def domain = (env.ENV == 'dev') ? "api.dev.dsp5-archi-o24a-15m-g3.fr" :
(env.ENV == 'preprod') ? "api.preprod.dsp5-archi-o24a-15m-g3.fr" :
"api.dsp5-archi-o24a-15m-g3.fr"
def statusCode = "000"
for (int i = 1; i <= 10; i++) {
statusCode = sh(script: "curl -k -s -o /dev/null -w '%{http_code}' https://${domain}/health || echo 000", returnStdout: true).trim()
if (statusCode in ['200', '301', '302']) {
echo "✅ Backend ${env.ENV} opérationnel (HTTP ${statusCode})"
return
}
echo "⏳ Tentative ${i}/10 → HTTP ${statusCode}"
sleep 5
}
error("❌ Health check échoué - code HTTP ${statusCode}")
}
}
}
}
// =========================================================================
// ACTIONS POST-PIPELINE
// =========================================================================
post {
success {
echo """
╔══════════════════════════════════════════╗
║ ✅ PIPELINE TERMINÉ AVEC SUCCÈS ║
╠══════════════════════════════════════════╣
║ Environnement : ${env.ENV.padRight(23)} ║
║ Image : ${IMAGE_NAME.padRight(23)} ║
║ Tag : ${env.TAG.padRight(23)} ║
╚══════════════════════════════════════════╝
"""
emailext(
to: 'soufiane.baali99@gmail.com',
subject: "✅ Pipeline Backend SUCCÈS - ${env.ENV}",
body: """
<h2 style="color: green;">✅ Pipeline Backend terminé avec succès</h2>
<table border="1" cellpadding="10" style="border-collapse: collapse;">
<tr><td><strong>Projet</strong></td><td>${env.JOB_NAME}</td></tr>
<tr><td><strong>Build</strong></td><td>#${env.BUILD_NUMBER}</td></tr>
<tr><td><strong>Environnement</strong></td><td>${env.ENV}</td></tr>
<tr><td><strong>Image</strong></td><td>${IMAGE_NAME}:${env.TAG}</td></tr>
<tr><td><strong>Durée</strong></td><td>${currentBuild.durationString}</td></tr>
</table>
<p>🔗 <a href="${env.BUILD_URL}">Voir les détails du build</a></p>
""",
mimeType: 'text/html'
)
echo "✅ Pipeline ${env.ENV} terminé avec succès !"
}
failure {
echo """
╔══════════════════════════════════════════╗
║ ❌ ÉCHEC DU PIPELINE ║
╠══════════════════════════════════════════╣
║ Environnement : ${env.ENV.padRight(23)} ║
║ Vérifiez les logs pour plus de détails ║
╚══════════════════════════════════════════╝
"""
emailext(
to: 'soufiane.baali99@gmail.com',
subject: "❌ Pipeline Backend ÉCHEC - ${env.ENV}",
body: """
<h2 style="color: red;">❌ Pipeline Backend a échoué</h2>
<table border="1" cellpadding="10" style="border-collapse: collapse;">
<tr><td><strong>Projet</strong></td><td>${env.JOB_NAME}</td></tr>
<tr><td><strong>Build</strong></td><td>#${env.BUILD_NUMBER}</td></tr>
<tr><td><strong>Environnement</strong></td><td>${env.ENV}</td></tr>
<tr><td><strong>Durée</strong></td><td>${currentBuild.durationString}</td></tr>
</table>
<p>🔗 <a href="${env.BUILD_URL}">Voir les logs du build</a></p>
<p>🔗 <a href="${env.BUILD_URL}console">Console Output</a></p>
""",
mimeType: 'text/html'
)
echo "❌ Échec du pipeline ${env.ENV}."
}
}
}

View File

@ -1,126 +0,0 @@
# Newsletter Feature - Installation Guide
## Vue d'ensemble
La fonctionnalité newsletter a été ajoutée au backend et frontend. Elle permet aux utilisateurs de s'abonner à la newsletter depuis le footer du site.
## Backend
### Fichiers créés :
1. **Migration de base de données** : `database/migrations/add-newsletter-table.sql`
2. **Controller** : `src/controllers/newsletter.controller.js`
3. **Service** : `src/services/newsletter.service.js`
4. **Routes** : `src/routes/newsletter.routes.js`
5. **Validation** : `src/validations/newsletter.validation.js`
### Endpoints API :
- `POST /api/newsletter/subscribe` - S'abonner (public)
- `POST /api/newsletter/unsubscribe` - Se désabonner (public)
- `GET /api/newsletter/subscribers` - Liste des abonnés (Admin seulement)
- `GET /api/newsletter/count` - Nombre d'abonnés actifs (Admin seulement)
## Frontend
### Fichiers modifiés/créés :
1. **Service** : `services/newsletter.service.ts`
2. **Constants** : `utils/constants.ts` (ajout des endpoints newsletter)
3. **Footer** : `components/Footer.tsx` (ajout du formulaire d'inscription)
## Installation
### 1. Appliquer la migration de base de données
Exécutez le script SQL pour créer la table `newsletters` :
```bash
# Depuis le répertoire backend
psql -h 51.75.24.29 -U postgres -d thetiptop_dev -p 5433 -f database/migrations/add-newsletter-table.sql
```
Ou connectez-vous à votre base de données et exécutez manuellement le contenu du fichier `database/migrations/add-newsletter-table.sql`.
### 2. Redémarrer le backend
```bash
# Dans le répertoire backend
npm run dev
```
### 3. Redémarrer le frontend
```bash
# Dans le répertoire frontend
npm run dev
```
## Fonctionnalités
### Pour les utilisateurs :
- Formulaire d'inscription à la newsletter dans le footer
- Validation d'email en temps réel
- Messages de confirmation/erreur
- Email de confirmation d'abonnement automatique
- Possibilité de se désabonner
### Pour les administrateurs :
- Voir la liste complète des abonnés
- Voir le nombre d'abonnés actifs
- API endpoints protégés par authentification et rôle ADMIN
## Emails
Les emails de confirmation sont envoyés automatiquement lors de l'inscription. Configuration SMTP requise dans le fichier `.env` :
```env
SMTP_HOST=your-smtp-host
SMTP_PORT=587
SMTP_USER=your-smtp-user
SMTP_PASS=your-smtp-password
EMAIL_FROM=noreply@thetiptop.fr
```
En mode développement sans configuration SMTP, les emails sont affichés dans la console.
## Test
### Test manuel :
1. Ouvrez le frontend (http://localhost:3000)
2. Scrollez jusqu'au footer
3. Entrez votre email dans le champ "Newsletter"
4. Cliquez sur "S'inscrire"
5. Vérifiez le message de confirmation
### Test API avec curl :
```bash
# S'abonner
curl -X POST http://localhost:4000/api/newsletter/subscribe \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com"}'
# Nombre d'abonnés (nécessite un token admin)
curl -X GET http://localhost:4000/api/newsletter/count \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"
```
## Sécurité
- Les endpoints publics (subscribe/unsubscribe) ne nécessitent pas d'authentification
- Les endpoints admin (subscribers/count) nécessitent un token JWT et le rôle ADMIN
- Validation Zod sur les entrées
- Protection contre les doublons d'email
- Sanitisation des données
## Prochaines étapes possibles
- Ajout d'une page de gestion de newsletter pour les admins
- Export de la liste des abonnés en CSV
- Système de campagnes email
- Segmentation des abonnés
- Statistiques d'engagement

159
README.md
View File

@ -1,159 +0,0 @@
# The Tip Top Backend API
API backend pour le jeu-concours The Tip Top.
## Configuration
### Variables d'environnement
Le fichier `.env` contient la configuration de la base de données et de l'application :
```env
DB_HOST=51.75.24.29
DB_PORT=5433
DB_USER=postgres
DB_PASS=postgres
DB_NAME=thetiptop_dev
PORT=4000
JWT_SECRET=thetiptopsecret
SESSION_SECRET=thetiptopsessionsecret
```
**Note** : La base de données PostgreSQL est hébergée sur le serveur distant `51.75.24.29:5433`.
## Installation
```bash
# Installer les dépendances
npm install
# Créer le schéma de la base de données
psql -U postgres -d thetiptop_dev -p 5433 -f database/schema.sql
# Insérer les données de test
npm run db:seed
```
## Démarrage
### Démarrage normal
```bash
npm start
```
### Démarrage en mode développement (avec nodemon)
```bash
npm run dev
```
### Arrêter le serveur
**Sur Windows :**
1. Ouvrez le Gestionnaire des tâches (Ctrl + Shift + Esc)
2. Cherchez les processus "Node.js"
3. Terminez-les tous
**Ou via PowerShell :**
```powershell
Get-Process node | Stop-Process -Force
```
## Structure du projet
```
the-tip-top-backend/
├── database/
│ ├── schema.sql # Schéma de la base de données
│ ├── seed.js # Script de seed
│ └── README.md # Documentation DB
├── src/
│ ├── config/
│ │ └── env.js # Configuration environnement
│ ├── controllers/ # Contrôleurs (logique métier)
│ ├── middleware/ # Middlewares (auth, validation, erreurs)
│ ├── routes/ # Définition des routes
│ ├── services/ # Services (email, etc.)
│ ├── utils/ # Utilitaires
│ └── validations/ # Schémas de validation Zod
├── db.js # Configuration PostgreSQL
├── index.js # Point d'entrée de l'application
└── .env # Variables d'environnement
```
## Routes API
### Authentification (`/api/auth`)
- `POST /register` - Inscription
- `POST /login` - Connexion
- `GET /verify-email/:token` - Vérification email
- `POST /forgot-password` - Demande de réinitialisation
- `POST /reset-password` - Réinitialisation du mot de passe
### Utilisateur (`/api/users`)
- `GET /profile` - Récupérer le profil
- `PUT /profile` - Mettre à jour le profil
- `POST /change-password` - Changer le mot de passe
### Jeu (`/api/game`)
- `POST /play` - Jouer et obtenir un ticket
- `GET /my-tickets` - Mes tickets
- `GET /ticket/:code` - Détails d'un ticket
### Employé (`/api/employee`)
- `POST /validate-ticket` - Valider un ticket
- `GET /pending-tickets` - Tickets en attente
### Admin (`/api/admin`)
- `GET /statistics` - Statistiques globales
- `GET /prizes` - Liste des prix
- `POST /prizes` - Créer un prix
- `PUT /prizes/:id` - Modifier un prix
- `DELETE /prizes/:id` - Supprimer un prix
- `GET /users` - Liste des utilisateurs
- `POST /employees` - Créer un employé
- `PUT /users/:id` - Modifier un utilisateur
- `DELETE /users/:id` - Supprimer un utilisateur
- `GET /tickets` - Liste des tickets
## Comptes de test
Après avoir exécuté le seed :
| Email | Mot de passe | Rôle |
|-------|-------------|------|
| admin@thetiptop.com | Admin123! | ADMIN |
| employee1@thetiptop.com | Employee123! | EMPLOYEE |
| employee2@thetiptop.com | Employee123! | EMPLOYEE |
| client1@example.com | Client123! | CLIENT |
| client2-5@example.com | Client123! | CLIENT |
## Tests
```bash
# Tests unitaires
npm test
# Tests d'intégration
npm run test:integration
```
## Endpoints de monitoring
- `GET /` - Health check
- `GET /db-check` - Vérification connexion DB
- `GET /metrics` - Métriques Prometheus
## Technologies
- **Express** - Framework web
- **PostgreSQL** - Base de données
- **JWT** - Authentification
- **Bcrypt** - Hashage de mots de passe
- **Zod** - Validation des données
- **Nodemailer** - Envoi d'emails
- **Helmet** - Sécurité HTTP
- **CORS** - Gestion des origines
- **Morgan** - Logs HTTP
- **Prometheus** - Métriques

View File

@ -1,44 +0,0 @@
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " RESTART BACKEND AND FRONTEND" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "Step 1: Stopping all Node.js processes..." -ForegroundColor Yellow
$nodeProcesses = Get-Process node -ErrorAction SilentlyContinue
if ($nodeProcesses) {
$nodeProcesses | Stop-Process -Force
Write-Host "[OK] Stopped $($nodeProcesses.Count) Node.js process(es)" -ForegroundColor Green
} else {
Write-Host "[INFO] No Node.js processes running" -ForegroundColor Gray
}
Write-Host ""
Write-Host "Waiting 3 seconds for ports to be released..." -ForegroundColor Yellow
Start-Sleep -Seconds 3
Write-Host ""
Write-Host "Step 2: Starting Backend..." -ForegroundColor Yellow
$backendPath = "C:\Users\LENOVO\Back\the-tip-top-backend"
Start-Process powershell -ArgumentList "-NoExit", "-Command", "cd '$backendPath'; npm start" -WindowStyle Normal
Write-Host "[OK] Backend starting..." -ForegroundColor Green
Write-Host ""
Write-Host "Waiting 5 seconds for backend to initialize..." -ForegroundColor Yellow
Start-Sleep -Seconds 5
Write-Host ""
Write-Host "Step 3: Starting Frontend..." -ForegroundColor Yellow
$frontendPath = "C:\Users\LENOVO\front\the-tip-top-frontend"
Start-Process powershell -ArgumentList "-NoExit", "-Command", "cd '$frontendPath'; npm run dev" -WindowStyle Normal
Write-Host "[OK] Frontend starting..." -ForegroundColor Green
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " RESTART COMPLETE" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "Backend: http://localhost:4000" -ForegroundColor White
Write-Host "Frontend: http://localhost:3004" -ForegroundColor White
Write-Host ""
Write-Host "Press any key to close this window..." -ForegroundColor Gray
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

View File

@ -1,39 +0,0 @@
@echo off
cls
echo.
echo ========================================
echo ARRET FORCE DE TOUS LES NODE.JS
echo ========================================
echo.
echo Etape 1: Arret de TOUS les processus Node...
echo.
REM Tuer tous les processus Node
taskkill /F /IM node.exe /T 2>nul
if %errorlevel% equ 0 (
echo [OK] Processus Node arretes
) else (
echo [INFO] Aucun processus Node trouve
)
echo.
echo Attente de 5 secondes...
timeout /t 5 /nobreak >nul
echo.
echo ========================================
echo DEMARRAGE DU BACKEND
echo ========================================
echo.
echo Dossier: C:\Users\LENOVO\Back\the-tip-top-backend
echo Commande: npm start
echo.
echo Le backend va demarrer dans 3 secondes...
timeout /t 3 /nobreak >nul
cd /d "C:\Users\LENOVO\Back\the-tip-top-backend"
echo.
echo *** BACKEND EN COURS DE DEMARRAGE ***
echo.
npm start

View File

@ -1,14 +0,0 @@
> backend@1.0.0 start
> node index.js
[dotenv@17.2.3] injecting env (12) from .env -- tip: ⚙️ specify custom .env file path with { path: '/custom/path/.env' }
🧩 DB Config → {
host: '51.75.24.29',
port: '5433',
user: 'postgres',
pass: '***',
database: 'thetiptop_dev'
}
[dotenv@17.2.3] injecting env (0) from .env -- tip: ⚙️ suppress all logs with { quiet: true }
🚀 Backend lancé sur 0.0.0.0:4000 ✅

View File

@ -1,10 +0,0 @@
-- Script pour créer des utilisateurs de test avec différents rôles
-- À exécuter dans PostgreSQL
-- Promouvoir les utilisateurs existants
UPDATE users SET role = 'ADMIN' WHERE email = 'admin@test.com';
UPDATE users SET role = 'EMPLOYEE' WHERE email = 'employee@test.com';
-- Vérifier les utilisateurs
SELECT email, role, is_verified FROM users
WHERE email IN ('test-client@test.com', 'employee@test.com', 'admin@test.com');

View File

@ -1,113 +0,0 @@
# Database Setup
Ce dossier contient le schéma SQL et le script de seed pour la base de données PostgreSQL.
## Structure
- `schema.sql` - Schéma complet de la base de données (tables, index, triggers, vues)
- `seed.js` - Script pour insérer des données de test
## Installation
### 1. Créer le schéma
Exécutez le fichier SQL pour créer toutes les tables et insérer les données initiales (prix, configuration du jeu) :
```bash
psql -U postgres -d thetiptop -f database/schema.sql
```
Ou utilisez le script npm :
```bash
npm run db:schema
```
### 2. Insérer les données de test
Exécutez le script de seed pour créer des utilisateurs et tickets de test :
```bash
npm run db:seed
```
## Données de test créées
Le script de seed crée :
### Utilisateurs
| Email | Mot de passe | Rôle |
|-------|-------------|------|
| admin@thetiptop.com | Admin123! | ADMIN |
| employee1@thetiptop.com | Employee123! | EMPLOYEE |
| employee2@thetiptop.com | Employee123! | EMPLOYEE |
| client1@example.com | Client123! | CLIENT |
| client2@example.com | Client123! | CLIENT |
| client3@example.com | Client123! | CLIENT |
| client4@example.com | Client123! | CLIENT |
| client5@example.com | Client123! | CLIENT |
### Tickets
- Environ 15-25 tickets répartis entre les clients
- 80% en statut PENDING
- 10% en statut CLAIMED
- 10% en statut REJECTED
- Prix distribués aléatoirement
## Schéma de la base de données
### Tables principales
#### users
Stocke tous les utilisateurs (clients, employés, admins)
- Authentification par email/password
- Vérification par email
- Récupération de mot de passe
#### prizes
Les différents prix disponibles dans le jeu-concours
- 5 types de prix (INFUSEUR, THE_SIGNATURE, COFFRET_DECOUVERTE, COFFRET_PRESTIGE, THE_GRATUIT)
- Stock et probabilités configurables
- Gestion du statut actif/inactif
#### tickets
Les tickets de jeu générés pour chaque participation
- Code unique
- Statuts: PENDING, CLAIMED, REJECTED
- Lié à un utilisateur et un prix
- Traçabilité de la validation
#### game_settings
Configuration générale du jeu-concours
- Dates de début et fin
- Nombre total de tickets
- Statut actif/inactif
### Vues utiles
#### prize_statistics
Statistiques agrégées par prix (stock initial, utilisé, restant, etc.)
#### pending_tickets
Liste des tickets en attente de validation avec toutes les informations nécessaires
## Réinitialisation
Pour réinitialiser complètement la base de données :
```bash
# 1. Supprimer et recréer le schéma
npm run db:schema
# 2. Insérer les données de test
npm run db:seed
```
## Notes
- Le schéma utilise des UUID pour tous les IDs
- Les triggers mettent automatiquement à jour les champs `updated_at`
- Les index sont créés sur les colonnes fréquemment utilisées pour optimiser les performances
- Les contraintes CASCADE assurent l'intégrité référentielle

View File

@ -1,49 +0,0 @@
# Migrations de Base de Données
Ce dossier contient les migrations pour la base de données The Tip Top.
## Migration: Ajout des champs démographiques
### Fichier: `add-demographics-fields.sql`
Cette migration ajoute les champs nécessaires pour les statistiques démographiques:
- `gender` (ENUM: MALE, FEMALE, OTHER, NOT_SPECIFIED)
- `date_of_birth` (DATE)
### Comment exécuter la migration
#### Option 1: Via psql
```bash
psql -h 51.75.24.29 -U postgres -d thetiptop_dev -p 5433 -f database/migrations/add-demographics-fields.sql
```
#### Option 2: Via un client SQL (DBeaver, pgAdmin, etc.)
1. Connectez-vous à votre base de données
2. Ouvrez le fichier `add-demographics-fields.sql`
3. Exécutez le script
### Vérification
Pour vérifier que la migration a été appliquée correctement:
```sql
-- Vérifier les nouvelles colonnes
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'users'
AND column_name IN ('gender', 'date_of_birth');
-- Vérifier le type ENUM gender_type
SELECT typname, enumlabel
FROM pg_type
JOIN pg_enum ON pg_type.oid = pg_enum.enumtypid
WHERE typname = 'gender_type';
```
## Notes importantes
- Cette migration est idempotente (peut être exécutée plusieurs fois sans erreur)
- Les valeurs par défaut sont définies pour ne pas affecter les données existantes
- Les index sont créés automatiquement pour améliorer les performances des requêtes de statistiques

View File

@ -1,38 +0,0 @@
-- ============================================
-- MIGRATION: Ajout des champs démographiques
-- ============================================
-- Créer le type ENUM pour le genre
DO $$ BEGIN
CREATE TYPE gender_type AS ENUM ('MALE', 'FEMALE', 'OTHER', 'NOT_SPECIFIED');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Ajouter les colonnes gender et date_of_birth à la table users si elles n'existent pas
DO $$
BEGIN
-- Ajouter la colonne gender si elle n'existe pas
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'users' AND column_name = 'gender'
) THEN
ALTER TABLE users ADD COLUMN gender gender_type DEFAULT 'NOT_SPECIFIED';
END IF;
-- Ajouter la colonne date_of_birth si elle n'existe pas
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'users' AND column_name = 'date_of_birth'
) THEN
ALTER TABLE users ADD COLUMN date_of_birth DATE;
END IF;
END $$;
-- Créer un index sur gender pour améliorer les performances des requêtes de statistiques
CREATE INDEX IF NOT EXISTS idx_users_gender ON users(gender);
CREATE INDEX IF NOT EXISTS idx_users_date_of_birth ON users(date_of_birth);
-- Commentaires
COMMENT ON COLUMN users.gender IS 'Genre de l''utilisateur: MALE, FEMALE, OTHER, NOT_SPECIFIED';
COMMENT ON COLUMN users.date_of_birth IS 'Date de naissance de l''utilisateur pour les statistiques démographiques';

View File

@ -1,73 +0,0 @@
-- Migration: Système de campagnes email
-- Date: 2025-11-14
-- Description: Tables pour gérer les campagnes d'emailing
-- Table des campagnes email
CREATE TABLE IF NOT EXISTS email_campaigns (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
subject VARCHAR(500) NOT NULL,
template_html TEXT NOT NULL,
template_text TEXT,
created_by UUID NOT NULL REFERENCES users(id),
status VARCHAR(50) DEFAULT 'DRAFT', -- DRAFT, SCHEDULED, SENT, CANCELLED
scheduled_at TIMESTAMP,
sent_at TIMESTAMP,
recipient_count INTEGER DEFAULT 0,
opened_count INTEGER DEFAULT 0,
clicked_count INTEGER DEFAULT 0,
criteria JSONB, -- Critères de ciblage
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Table des destinataires (pour tracking individuel)
CREATE TABLE IF NOT EXISTS email_campaign_recipients (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
campaign_id UUID NOT NULL REFERENCES email_campaigns(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id),
email VARCHAR(255) NOT NULL,
status VARCHAR(50) DEFAULT 'PENDING', -- PENDING, SENT, FAILED, BOUNCED
sent_at TIMESTAMP,
opened_at TIMESTAMP,
clicked_at TIMESTAMP,
unsubscribed_at TIMESTAMP,
error_message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Table des templates prédéfinis
CREATE TABLE IF NOT EXISTS email_templates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
description TEXT,
subject VARCHAR(500),
html_content TEXT NOT NULL,
text_content TEXT,
category VARCHAR(100), -- welcome, promotion, notification, etc.
is_active BOOLEAN DEFAULT TRUE,
created_by UUID REFERENCES users(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Index pour performance
CREATE INDEX IF NOT EXISTS idx_email_campaigns_status ON email_campaigns(status);
CREATE INDEX IF NOT EXISTS idx_email_campaigns_created_by ON email_campaigns(created_by);
CREATE INDEX IF NOT EXISTS idx_email_campaigns_scheduled_at ON email_campaigns(scheduled_at);
CREATE INDEX IF NOT EXISTS idx_email_campaign_recipients_campaign ON email_campaign_recipients(campaign_id);
CREATE INDEX IF NOT EXISTS idx_email_campaign_recipients_user ON email_campaign_recipients(user_id);
CREATE INDEX IF NOT EXISTS idx_email_campaign_recipients_status ON email_campaign_recipients(status);
CREATE INDEX IF NOT EXISTS idx_email_templates_category ON email_templates(category);
CREATE INDEX IF NOT EXISTS idx_email_templates_is_active ON email_templates(is_active);
-- Commentaires
COMMENT ON TABLE email_campaigns IS 'Campagnes email marketing';
COMMENT ON TABLE email_campaign_recipients IS 'Destinataires individuels avec tracking';
COMMENT ON TABLE email_templates IS 'Templates email reutilisables';
COMMENT ON COLUMN email_campaigns.criteria IS 'Criteres JSON de ciblage des destinataires';
COMMENT ON COLUMN email_campaigns.status IS 'DRAFT=Brouillon, SCHEDULED=Planifie, SENT=Envoye, CANCELLED=Annule';

View File

@ -1,49 +0,0 @@
-- Migration: Ajout de la table pour les tirages au sort du gros lot
-- Date: 2025-11-13
-- Table pour stocker les tirages au sort
CREATE TABLE IF NOT EXISTS grand_prize_draws (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Informations du tirage
draw_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
conducted_by UUID NOT NULL REFERENCES users(id), -- Admin qui a lancé le tirage
-- Gagnant
winner_id UUID NOT NULL REFERENCES users(id),
winner_email VARCHAR(255) NOT NULL,
winner_name VARCHAR(255) NOT NULL,
-- Détails du lot
prize_name VARCHAR(255) NOT NULL,
prize_value VARCHAR(100),
-- Participants
total_participants INTEGER NOT NULL,
eligible_participants INTEGER NOT NULL,
-- Critères d'éligibilité utilisés
criteria JSONB, -- Stocke les critères du tirage
-- Statut
status VARCHAR(50) DEFAULT 'COMPLETED', -- COMPLETED, NOTIFIED, CLAIMED
notified_at TIMESTAMP,
claimed_at TIMESTAMP,
-- Metadata
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT unique_grand_prize_draw UNIQUE (draw_date)
);
-- Index pour les recherches
CREATE INDEX IF NOT EXISTS idx_grand_prize_draws_winner ON grand_prize_draws(winner_id);
CREATE INDEX IF NOT EXISTS idx_grand_prize_draws_date ON grand_prize_draws(draw_date);
CREATE INDEX IF NOT EXISTS idx_grand_prize_draws_status ON grand_prize_draws(status);
-- Commentaires
COMMENT ON TABLE grand_prize_draws IS 'Stocke les tirages au sort du gros lot final';
COMMENT ON COLUMN grand_prize_draws.conducted_by IS 'Admin qui a effectué le tirage';
COMMENT ON COLUMN grand_prize_draws.criteria IS 'Critères d''éligibilité en JSON (ex: {minTickets: 1, verified: true})';
COMMENT ON COLUMN grand_prize_draws.status IS 'COMPLETED: tirage effectué, NOTIFIED: gagnant notifié, CLAIMED: lot récupéré';

View File

@ -1,31 +0,0 @@
-- Migration: Ajouter le type GRAND_PRIZE pour le gros lot du tirage final
-- Date: 2025-11-14
-- Ajouter le type GRAND_PRIZE à l'enum prize_type
ALTER TYPE prize_type ADD VALUE IF NOT EXISTS 'GRAND_PRIZE';
-- Créer le gros lot "An de thé" (360€)
INSERT INTO prizes (
type,
name,
description,
value,
stock,
probability,
is_active
) VALUES (
'GRAND_PRIZE',
'An de thé',
'Gros lot du tirage final - Un an de thé d''exception',
'360.00',
1,
0,
TRUE
)
ON CONFLICT (name) DO UPDATE SET
type = 'GRAND_PRIZE',
description = 'Gros lot du tirage final - Un an de thé d''exception',
value = '360.00',
stock = 1,
probability = 0,
is_active = TRUE;

View File

@ -1,14 +0,0 @@
-- ============================================
-- 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;

View File

@ -1,26 +0,0 @@
-- ============================================
-- MIGRATION: Add Newsletter Table
-- ============================================
-- Create newsletters table
CREATE TABLE IF NOT EXISTS newsletters (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
subscribed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
unsubscribed_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create indexes for better performance
CREATE INDEX idx_newsletters_email ON newsletters(email);
CREATE INDEX idx_newsletters_is_active ON newsletters(is_active);
-- Add trigger for updated_at
CREATE TRIGGER update_newsletters_updated_at BEFORE UPDATE ON newsletters
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Add comment
COMMENT ON TABLE newsletters IS 'Table des abonnements à la newsletter';
COMMENT ON COLUMN newsletters.is_active IS 'TRUE si abonné, FALSE si désabonné';

View File

@ -1,26 +0,0 @@
-- Migration: Ajout du tracking de remise physique des lots
-- Date: 2025-11-14
-- Description: Ajoute les colonnes pour suivre la remise physique des lots
-- Ajouter la colonne delivered_at pour marquer la date de remise physique
ALTER TABLE tickets
ADD COLUMN IF NOT EXISTS delivered_at TIMESTAMP DEFAULT NULL;
-- Ajouter la colonne delivered_by pour tracer qui a remis le lot
ALTER TABLE tickets
ADD COLUMN IF NOT EXISTS delivered_by UUID DEFAULT NULL REFERENCES users(id);
-- Ajouter la colonne delivery_notes pour des notes sur la remise
ALTER TABLE tickets
ADD COLUMN IF NOT EXISTS delivery_notes TEXT DEFAULT NULL;
-- Créer un index sur delivered_at pour les requêtes de suivi
CREATE INDEX IF NOT EXISTS idx_tickets_delivered_at ON tickets(delivered_at);
-- Créer un index sur delivered_by
CREATE INDEX IF NOT EXISTS idx_tickets_delivered_by ON tickets(delivered_by);
-- Commentaires
COMMENT ON COLUMN tickets.delivered_at IS 'Date de remise physique du lot au gagnant';
COMMENT ON COLUMN tickets.delivered_by IS 'Employé/Admin qui a remis le lot';
COMMENT ON COLUMN tickets.delivery_notes IS 'Notes sur la remise (lieu, conditions, etc.)';

View File

@ -1,27 +0,0 @@
-- Migration pour corriger le schéma de la table tickets
-- Permet de créer des tickets non assignés (status=NULL, user_id=NULL)
-- 1. Modifier la colonne user_id pour accepter NULL
ALTER TABLE tickets
ALTER COLUMN user_id DROP NOT NULL;
-- 2. Modifier la colonne status pour ne pas avoir de valeur par défaut
ALTER TABLE tickets
ALTER COLUMN status DROP DEFAULT;
-- 3. Modifier la colonne played_at pour ne pas avoir de valeur par défaut
ALTER TABLE tickets
ALTER COLUMN played_at DROP DEFAULT;
-- 4. Mettre à jour les tickets existants qui ont été mal créés
-- (tickets avec PENDING mais sans user_id ou avec played_at par défaut)
UPDATE tickets
SET
status = NULL,
user_id = NULL,
played_at = NULL
WHERE user_id IS NULL OR played_at = created_at;
COMMENT ON COLUMN tickets.user_id IS 'NULL = ticket non joué, UUID = ticket joué par cet utilisateur';
COMMENT ON COLUMN tickets.status IS 'NULL = ticket non joué, PENDING/CLAIMED/REJECTED = statut après jeu';
COMMENT ON COLUMN tickets.played_at IS 'NULL = ticket non joué, TIMESTAMP = date de jeu';

View File

@ -1,10 +0,0 @@
-- Rendre le champ user_id nullable pour permettre les tickets non utilisés
-- Les 500,000 tickets du jeu-concours existent avant d'être attribués à un utilisateur
ALTER TABLE tickets
ALTER COLUMN user_id DROP NOT NULL;
-- Créer un index pour les tickets non utilisés pour optimiser les requêtes
CREATE INDEX IF NOT EXISTS idx_tickets_unused ON tickets(user_id) WHERE user_id IS NULL;
COMMENT ON COLUMN tickets.user_id IS 'ID de l''utilisateur - NULL pour les tickets non encore utilisés';

View File

@ -1,271 +0,0 @@
-- ============================================
-- SCHEMA SQL - THE TIP TOP DATABASE
-- ============================================
-- Drop tables if they exist
DROP TABLE IF EXISTS tickets CASCADE;
DROP TABLE IF EXISTS prizes CASCADE;
DROP TABLE IF EXISTS users CASCADE;
DROP TABLE IF EXISTS game_settings CASCADE;
-- Drop types if they exist
DROP TYPE IF EXISTS user_role CASCADE;
DROP TYPE IF EXISTS ticket_status CASCADE;
DROP TYPE IF EXISTS prize_type CASCADE;
-- ============================================
-- ENUMS
-- ============================================
CREATE TYPE user_role AS ENUM ('CLIENT', 'EMPLOYEE', 'ADMIN');
CREATE TYPE ticket_status AS ENUM ('PENDING', 'CLAIMED', 'REJECTED');
CREATE TYPE prize_type AS ENUM (
'INFUSEUR',
'THE_SIGNATURE',
'COFFRET_DECOUVERTE',
'COFFRET_PRESTIGE',
'THE_GRATUIT'
);
-- ============================================
-- TABLE: users
-- ============================================
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
phone VARCHAR(20),
address TEXT,
city VARCHAR(100),
postal_code VARCHAR(10),
role user_role DEFAULT 'CLIENT',
is_active BOOLEAN DEFAULT TRUE,
is_verified BOOLEAN DEFAULT FALSE,
verification_token VARCHAR(255),
reset_token VARCHAR(255),
reset_token_expiry TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Index pour améliorer les performances
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_role ON users(role);
CREATE INDEX idx_users_verification_token ON users(verification_token);
CREATE INDEX idx_users_reset_token ON users(reset_token);
-- ============================================
-- TABLE: prizes
-- ============================================
CREATE TABLE prizes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
type prize_type NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
value DECIMAL(10, 2) NOT NULL,
stock INTEGER DEFAULT 0,
probability DECIMAL(5, 4) NOT NULL CHECK (probability >= 0 AND probability <= 1),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Index pour améliorer les performances
CREATE INDEX idx_prizes_type ON prizes(type);
CREATE INDEX idx_prizes_is_active ON prizes(is_active);
-- ============================================
-- TABLE: tickets
-- ============================================
CREATE TABLE tickets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(50) UNIQUE NOT NULL,
user_id UUID REFERENCES users(id) ON DELETE CASCADE, -- NULL = ticket non joué
prize_id UUID NOT NULL REFERENCES prizes(id) ON DELETE RESTRICT,
status ticket_status, -- NULL = ticket non joué, PENDING/CLAIMED/REJECTED après jeu
played_at TIMESTAMP, -- NULL = ticket non joué, TIMESTAMP = date de jeu
claimed_at TIMESTAMP,
validated_by UUID REFERENCES users(id) ON DELETE SET NULL,
validated_at TIMESTAMP,
rejection_reason TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Index pour améliorer les performances
CREATE INDEX idx_tickets_code ON tickets(code);
CREATE INDEX idx_tickets_user_id ON tickets(user_id);
CREATE INDEX idx_tickets_status ON tickets(status);
CREATE INDEX idx_tickets_prize_id ON tickets(prize_id);
CREATE INDEX idx_tickets_validated_by ON tickets(validated_by);
-- ============================================
-- TABLE: game_settings
-- ============================================
CREATE TABLE game_settings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
start_date TIMESTAMP NOT NULL,
end_date TIMESTAMP NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
total_tickets INTEGER NOT NULL,
tickets_generated INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- ============================================
-- TRIGGERS
-- ============================================
-- Trigger pour mettre à jour updated_at automatiquement
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_prizes_updated_at BEFORE UPDATE ON prizes
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_tickets_updated_at BEFORE UPDATE ON tickets
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_game_settings_updated_at BEFORE UPDATE ON game_settings
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================
-- INITIAL DATA
-- ============================================
-- Configuration du jeu-concours Thé Tip Top
-- Période de participation : 1 décembre 2025 au 31 décembre 2025 (30 jours)
-- Période de récupération des lots : jusqu'au 31 janvier 2026 (60 jours)
-- Tirage au sort : 1 février 2026
INSERT INTO game_settings (start_date, end_date, is_active, total_tickets)
VALUES ('2025-12-01 00:00:00', '2025-12-31 23:59:59', TRUE, 500000);
-- Prix avec probabilités respectant la distribution demandée
-- Total: 500,000 tickets
-- 1. Infuseur à thé (60% = 300,000)
INSERT INTO prizes (type, name, description, value, stock, probability, is_active)
VALUES (
'INFUSEUR',
'Infuseur à thé',
'Un infuseur à thé de qualité supérieure en inox',
39.00,
300000,
0.60,
TRUE
);
-- 2. Boîte de 100g signature (20% = 100,000)
INSERT INTO prizes (type, name, description, value, stock, probability, is_active)
VALUES (
'THE_SIGNATURE',
'Thé Signature 100g',
'Boîte de thé signature 100g - Mélange exclusif Thé Tip Top',
49.00,
100000,
0.20,
TRUE
);
-- 3. Coffret découverte (10% = 50,000)
INSERT INTO prizes (type, name, description, value, stock, probability, is_active)
VALUES (
'COFFRET_DECOUVERTE',
'Coffret Découverte',
'Coffret découverte avec assortiment de 5 thés (39€)',
39.00,
50000,
0.10,
TRUE
);
-- 4. Coffret prestige (6% = 30,000)
INSERT INTO prizes (type, name, description, value, stock, probability, is_active)
VALUES (
'COFFRET_PRESTIGE',
'Coffret Prestige',
'Coffret prestige avec sélection premium de thés rares (69€)',
69.00,
30000,
0.06,
TRUE
);
-- 5. Thé gratuit (4% = 20,000)
INSERT INTO prizes (type, name, description, value, stock, probability, is_active)
VALUES (
'THE_GRATUIT',
'Thé Infusion Gratuit',
'Un thé infusion de votre choix offert en magasin',
0.00,
20000,
0.04,
TRUE
);
-- ============================================
-- VIEWS UTILES
-- ============================================
-- Vue pour les statistiques des prix
CREATE OR REPLACE VIEW prize_statistics AS
SELECT
p.id,
p.name,
p.type,
p.stock AS stock_initial,
COUNT(t.id) AS tickets_used,
(p.stock - COUNT(t.id)) AS stock_remaining,
COUNT(CASE WHEN t.status = 'CLAIMED' THEN 1 END) AS prizes_claimed,
COUNT(CASE WHEN t.status = 'PENDING' THEN 1 END) AS prizes_pending,
COUNT(CASE WHEN t.status = 'REJECTED' THEN 1 END) AS prizes_rejected
FROM prizes p
LEFT JOIN tickets t ON p.id = t.prize_id
GROUP BY p.id, p.name, p.type, p.stock;
-- Vue pour les tickets en attente de validation
CREATE OR REPLACE VIEW pending_tickets AS
SELECT
t.id,
t.code,
t.status,
t.played_at,
u.email AS user_email,
u.first_name || ' ' || u.last_name AS user_name,
u.phone AS user_phone,
p.name AS prize_name,
p.type AS prize_type,
p.value AS prize_value
FROM tickets t
JOIN users u ON t.user_id = u.id
JOIN prizes p ON t.prize_id = p.id
WHERE t.status = 'PENDING'
ORDER BY t.played_at ASC;
-- ============================================
-- COMMENTAIRES
-- ============================================
COMMENT ON TABLE users IS 'Table des utilisateurs (clients, employés, admins)';
COMMENT ON TABLE prizes IS 'Table des prix disponibles dans le jeu';
COMMENT ON TABLE tickets IS 'Table des tickets de jeu joués par les utilisateurs';
COMMENT ON TABLE game_settings IS 'Configuration générale du jeu-concours';
COMMENT ON COLUMN users.role IS 'Rôle: CLIENT, EMPLOYEE, ou ADMIN';
COMMENT ON COLUMN prizes.probability IS 'Probabilité de gagner ce prix (0.0 à 1.0)';
COMMENT ON COLUMN tickets.status IS 'Statut: PENDING (en attente), CLAIMED (réclamé), REJECTED (rejeté)';

View File

@ -1,247 +0,0 @@
/**
* Script de seed pour insérer des données de test
* Usage: node database/seed.js
*/
import bcrypt from 'bcrypt';
import { pool } from '../db.js';
import { randomUUID } from 'crypto';
const SALT_ROUNDS = 10;
async function seedDatabase() {
try {
console.log('🌱 Démarrage du seed de la base de données...\n');
// ============================================
// 1. SEED USERS
// ============================================
console.log('👥 Création des utilisateurs...');
// Hash des mots de passe
const adminPassword = await bcrypt.hash('Admin123!', SALT_ROUNDS);
const employeePassword = await bcrypt.hash('Employee123!', SALT_ROUNDS);
const clientPassword = await bcrypt.hash('Client123!', SALT_ROUNDS);
// Admin
const adminResult = await pool.query(
`INSERT INTO users (email, password, first_name, last_name, phone, role, is_verified)
VALUES ($1, $2, $3, $4, $5, 'ADMIN', TRUE)
ON CONFLICT (email) DO UPDATE SET role = 'ADMIN'
RETURNING id, email, role`,
['admin@thetiptop.com', adminPassword, 'Admin', 'Principal', '+33123456789']
);
console.log('✅ Admin créé:', adminResult.rows[0].email);
// Employé 1
const employee1Result = await pool.query(
`INSERT INTO users (email, password, first_name, last_name, phone, role, is_verified)
VALUES ($1, $2, $3, $4, $5, 'EMPLOYEE', TRUE)
ON CONFLICT (email) DO UPDATE SET role = 'EMPLOYEE'
RETURNING id, email, role`,
['employee1@thetiptop.com', employeePassword, 'Marie', 'Dupont', '+33198765432']
);
console.log('✅ Employé 1 créé:', employee1Result.rows[0].email);
// Employé 2
const employee2Result = await pool.query(
`INSERT INTO users (email, password, first_name, last_name, phone, role, is_verified)
VALUES ($1, $2, $3, $4, $5, 'EMPLOYEE', TRUE)
ON CONFLICT (email) DO UPDATE SET role = 'EMPLOYEE'
RETURNING id, email, role`,
['employee2@thetiptop.com', employeePassword, 'Pierre', 'Martin', '+33187654321']
);
console.log('✅ Employé 2 créé:', employee2Result.rows[0].email);
// Clients
const clients = [
{
email: 'client1@example.com',
firstName: 'Jean',
lastName: 'Dupuis',
phone: '+33612345678',
},
{
email: 'client2@example.com',
firstName: 'Sophie',
lastName: 'Bernard',
phone: '+33623456789',
},
{
email: 'client3@example.com',
firstName: 'Luc',
lastName: 'Lefevre',
phone: '+33634567890',
},
{
email: 'client4@example.com',
firstName: 'Emma',
lastName: 'Petit',
phone: '+33645678901',
},
{
email: 'client5@example.com',
firstName: 'Thomas',
lastName: 'Robert',
phone: '+33656789012',
},
];
const clientIds = [];
for (const client of clients) {
const result = await pool.query(
`INSERT INTO users (email, password, first_name, last_name, phone, role, is_verified)
VALUES ($1, $2, $3, $4, $5, 'CLIENT', TRUE)
ON CONFLICT (email) DO UPDATE SET role = 'CLIENT'
RETURNING id`,
[
client.email,
clientPassword,
client.firstName,
client.lastName,
client.phone,
]
);
clientIds.push(result.rows[0].id);
console.log(`✅ Client créé: ${client.email}`);
}
console.log(`\n${clients.length + 3} utilisateurs créés avec succès\n`);
// ============================================
// 2. SEED TICKETS
// ============================================
console.log('🎟️ Création des tickets de test...');
// Récupérer les IDs des prix
const prizesResult = await pool.query(
'SELECT id, type FROM prizes ORDER BY type'
);
const prizes = prizesResult.rows;
if (prizes.length === 0) {
console.log('⚠️ Aucun prix trouvé. Exécutez d\'abord le schema.sql');
return;
}
// Créer des tickets avec différents statuts pour chaque client
const _ticketStatuses = ['PENDING', 'CLAIMED', 'REJECTED']; // eslint-disable-line no-unused-vars
let ticketCount = 0;
for (let i = 0; i < clientIds.length; i++) {
const clientId = clientIds[i];
// Chaque client obtient 3-5 tickets
const numTickets = Math.floor(Math.random() * 3) + 3;
for (let j = 0; j < numTickets; j++) {
// Sélectionner un prix aléatoire
const randomPrize = prizes[Math.floor(Math.random() * prizes.length)];
// Sélectionner un statut (80% PENDING, 10% CLAIMED, 10% REJECTED)
const rand = Math.random();
let status;
if (rand < 0.8) {
status = 'PENDING';
} else if (rand < 0.9) {
status = 'CLAIMED';
} else {
status = 'REJECTED';
}
// Générer un code unique
const ticketCode = `TT-${Date.now()}-${randomUUID().substring(0, 8).toUpperCase()}`;
// Créer le ticket
const insertQuery = `
INSERT INTO tickets (code, user_id, prize_id, status, played_at, claimed_at, validated_by, validated_at, rejection_reason)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
`;
const playedAt = new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000); // Dans les 30 derniers jours
let claimedAt = null;
let validatedBy = null;
let validatedAt = null;
let rejectionReason = null;
if (status === 'CLAIMED') {
claimedAt = new Date(playedAt.getTime() + Math.random() * 7 * 24 * 60 * 60 * 1000); // 0-7 jours après
validatedBy = employee1Result.rows[0].id;
validatedAt = claimedAt;
} else if (status === 'REJECTED') {
claimedAt = new Date(playedAt.getTime() + Math.random() * 7 * 24 * 60 * 60 * 1000);
validatedBy = employee1Result.rows[0].id;
validatedAt = claimedAt;
rejectionReason = 'Ticket non conforme ou expiré';
}
await pool.query(insertQuery, [
ticketCode,
clientId,
randomPrize.id,
status,
playedAt,
claimedAt,
validatedBy,
validatedAt,
rejectionReason,
]);
ticketCount++;
}
}
console.log(`${ticketCount} tickets créés avec succès\n`);
// ============================================
// 3. AFFICHER LES STATISTIQUES
// ============================================
console.log('📊 Statistiques de la base de données:\n');
const usersStats = await pool.query(`
SELECT
COUNT(*) as total,
COUNT(CASE WHEN role = 'ADMIN' THEN 1 END) as admins,
COUNT(CASE WHEN role = 'EMPLOYEE' THEN 1 END) as employees,
COUNT(CASE WHEN role = 'CLIENT' THEN 1 END) as clients
FROM users
`);
const ticketsStats = await pool.query(`
SELECT
COUNT(*) as total,
COUNT(CASE WHEN status = 'PENDING' THEN 1 END) as pending,
COUNT(CASE WHEN status = 'CLAIMED' THEN 1 END) as claimed,
COUNT(CASE WHEN status = 'REJECTED' THEN 1 END) as rejected
FROM tickets
`);
console.log('👥 Utilisateurs:');
console.log(` Total: ${usersStats.rows[0].total}`);
console.log(` Admins: ${usersStats.rows[0].admins}`);
console.log(` Employés: ${usersStats.rows[0].employees}`);
console.log(` Clients: ${usersStats.rows[0].clients}`);
console.log('\n🎟 Tickets:');
console.log(` Total: ${ticketsStats.rows[0].total}`);
console.log(` En attente: ${ticketsStats.rows[0].pending}`);
console.log(` Réclamés: ${ticketsStats.rows[0].claimed}`);
console.log(` Rejetés: ${ticketsStats.rows[0].rejected}`);
console.log('\n✅ Seed terminé avec succès!');
console.log('\n🔐 Comptes de test créés:');
console.log(' Admin: admin@thetiptop.com / Admin123!');
console.log(' Employé 1: employee1@thetiptop.com / Employee123!');
console.log(' Employé 2: employee2@thetiptop.com / Employee123!');
console.log(' Clients: client1@example.com à client5@example.com / Client123!');
} catch (error) {
console.error('❌ Erreur lors du seed:', error);
throw error;
} finally {
// Fermer la connexion
await pool.end();
}
}
// Exécuter le seed
seedDatabase();

View File

@ -1,64 +0,0 @@
-- ============================================
-- MISE À JOUR DES NOMS DES LOTS
-- Correction selon les probabilités correctes
-- ============================================
-- 60% des tickets offrent un infuseur à thé (INFUSEUR)
UPDATE prizes
SET
name = 'Infuseur à thé',
description = 'Un infuseur à thé de qualité supérieure en inox',
value = 39.00
WHERE type = 'INFUSEUR';
-- 20% des tickets offrent une boite de 100g d'un thé détox ou d'infusion (THE_GRATUIT)
UPDATE prizes
SET
name = 'Boîte de 100g thé détox ou infusion',
description = 'Une boîte de 100g d''un thé détox ou d''infusion au choix',
value = 0.00,
stock = 100000,
probability = 0.20
WHERE type = 'THE_GRATUIT';
-- 10% des tickets offrent une boite de 100g d'un thé signature (THE_SIGNATURE)
UPDATE prizes
SET
name = 'Boîte de 100g thé signature',
description = 'Une boîte de 100g d''un thé signature - Mélange exclusif',
value = 49.00,
stock = 50000,
probability = 0.10
WHERE type = 'THE_SIGNATURE';
-- 6% des tickets offrent un coffret découverte d'une valeur de 39€ (COFFRET_DECOUVERTE)
UPDATE prizes
SET
name = 'Coffret découverte 39€',
description = 'Coffret découverte avec assortiment de thés d''une valeur de 39€',
value = 39.00,
stock = 30000,
probability = 0.06
WHERE type = 'COFFRET_DECOUVERTE';
-- 4% des tickets offrent un coffret prestige d'une valeur de 69€ (COFFRET_PRESTIGE)
UPDATE prizes
SET
name = 'Coffret découverte 69€',
description = 'Coffret découverte premium avec sélection de thés rares d''une valeur de 69€',
value = 69.00,
stock = 20000,
probability = 0.04
WHERE type = 'COFFRET_PRESTIGE';
-- Vérification
SELECT
type,
name,
description,
value,
stock,
probability,
(probability * 100) || '%' as percentage
FROM prizes
ORDER BY probability DESC;

View File

@ -1,115 +0,0 @@
/**
* Script pour mettre à jour les noms des lots selon les bonnes probabilités
*/
import { pool } from '../db.js';
async function updatePrizeNames() {
try {
console.log('🔄 Mise à jour des noms des lots...\n');
// 60% - Infuseur à thé
await pool.query(`
UPDATE prizes
SET
name = 'Infuseur à thé',
description = 'Un infuseur à thé de qualité supérieure en inox',
value = 39.00
WHERE type = 'INFUSEUR'
`);
console.log('✅ Infuseur à thé (60%) - Mis à jour');
// 20% - Boîte de 100g thé détox ou infusion
await pool.query(`
UPDATE prizes
SET
name = 'Boîte de 100g thé détox ou infusion',
description = 'Une boîte de 100g d''un thé détox ou d''infusion au choix',
value = 0.00,
stock = 100000,
probability = 0.20
WHERE type = 'THE_GRATUIT'
`);
console.log('✅ Boîte de 100g thé détox ou infusion (20%) - Mis à jour');
// 10% - Boîte de 100g thé signature
await pool.query(`
UPDATE prizes
SET
name = 'Boîte de 100g thé signature',
description = 'Une boîte de 100g d''un thé signature - Mélange exclusif',
value = 49.00,
stock = 50000,
probability = 0.10
WHERE type = 'THE_SIGNATURE'
`);
console.log('✅ Boîte de 100g thé signature (10%) - Mis à jour');
// 6% - Coffret découverte 39€
await pool.query(`
UPDATE prizes
SET
name = 'Coffret découverte 39€',
description = 'Coffret découverte avec assortiment de thés d''une valeur de 39€',
value = 39.00,
stock = 30000,
probability = 0.06
WHERE type = 'COFFRET_DECOUVERTE'
`);
console.log('✅ Coffret découverte 39€ (6%) - Mis à jour');
// 4% - Coffret découverte 69€
await pool.query(`
UPDATE prizes
SET
name = 'Coffret découverte 69€',
description = 'Coffret découverte premium avec sélection de thés rares d''une valeur de 69€',
value = 69.00,
stock = 20000,
probability = 0.04
WHERE type = 'COFFRET_PRESTIGE'
`);
console.log('✅ Coffret découverte 69€ (4%) - Mis à jour');
// Afficher le résultat
console.log('\n📊 Vérification des lots mis à jour:\n');
const result = await pool.query(`
SELECT
type,
name,
description,
value,
stock,
probability,
(probability * 100) || '%' as percentage
FROM prizes
ORDER BY probability DESC
`);
console.log('┌─────────────────────────┬──────────────────────────────────────┬────────┬──────────┬──────────────┐');
console.log('│ TYPE │ NOM │ VALEUR │ STOCK │ PROBABILITÉ │');
console.log('├─────────────────────────┼──────────────────────────────────────┼────────┼──────────┼──────────────┤');
result.rows.forEach(row => {
const type = row.type.padEnd(23);
const name = row.name.substring(0, 36).padEnd(36);
const value = (row.value + '€').padStart(6);
const stock = row.stock.toString().padStart(8);
const prob = row.percentage.padStart(12);
console.log(`${type}${name}${value}${stock}${prob}`);
});
console.log('└─────────────────────────┴──────────────────────────────────────┴────────┴──────────┴──────────────┘');
console.log('\n✅ Mise à jour terminée avec succès!');
} catch (error) {
console.error('❌ Erreur lors de la mise à jour:', error);
throw error;
} finally {
await pool.end();
}
}
// Exécuter la mise à jour
updatePrizeNames();

24
db.js
View File

@ -1,21 +1,23 @@
import dotenv from "dotenv";
import pkg from "pg";
import config from "./src/config/env.js";
// Charger les variables d'environnement depuis .env
dotenv.config();
const { Pool } = pkg;
// Log de debug pour vérifier que les variables sont bien lues
console.log("🧩 DB Config →", {
host: config.db.host,
port: config.db.port,
user: config.db.user,
pass: config.db.password ? '***' : undefined,
database: config.db.database,
host: process.env.DB_HOST,
user: process.env.DB_USER,
pass: process.env.DB_PASS,
name: process.env.DB_NAME,
});
export const pool = new Pool({
host: config.db.host,
port: config.db.port,
user: config.db.user,
password: config.db.password,
database: config.db.database,
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
port: 5432,
});

View File

@ -1,834 +0,0 @@
# 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*

View File

@ -1,52 +1,17 @@
import js from '@eslint/js';
// eslint.config.js
export default [
js.configs.recommended,
{
files: ["**/*.js"],
ignores: ["node_modules/**"],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
globals: {
// Node.js globals
console: 'readonly',
process: 'readonly',
Buffer: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
global: 'readonly',
module: 'readonly',
require: 'readonly',
exports: 'writable',
fetch: 'readonly',
setTimeout: 'readonly',
setInterval: 'readonly',
clearTimeout: 'readonly',
clearInterval: 'readonly',
// Jest globals
describe: 'readonly',
it: 'readonly',
test: 'readonly',
expect: 'readonly',
beforeEach: 'readonly',
afterEach: 'readonly',
beforeAll: 'readonly',
afterAll: 'readonly',
jest: 'readonly',
},
sourceType: "module",
ecmaVersion: "latest",
},
rules: {
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'no-console': 'off',
'no-undef': 'error',
'no-useless-escape': 'warn',
semi: ["error", "always"],
quotes: ["error", "double"],
"no-unused-vars": ["warn"],
"no-console": "off",
},
},
{
ignores: [
'node_modules/**',
'dist/**',
'coverage/**',
'*.config.js',
],
},
];

View File

@ -1,259 +0,0 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 15,
"gradientMode": "opacity",
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
"lineInterpolation": "smooth",
"lineWidth": 2,
"pointSize": 5,
"scaleDistribution": { "type": "linear" },
"showPoints": "never",
"spanNulls": false,
"stacking": { "group": "A", "mode": "none" },
"thresholdsStyle": { "mode": "off" }
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [{ "color": "green", "value": null }]
},
"unit": "reqps"
},
"overrides": [
{
"matcher": { "id": "byRegexp", "options": ".*Frontend.*" },
"properties": [{ "id": "color", "value": { "fixedColor": "blue", "mode": "fixed" } }]
},
{
"matcher": { "id": "byRegexp", "options": ".*Backend.*" },
"properties": [{ "id": "color", "value": { "fixedColor": "green", "mode": "fixed" } }]
}
]
},
"gridPos": { "h": 10, "w": 24, "x": 0, "y": 0 },
"id": 1,
"options": {
"legend": {
"calcs": ["mean", "max", "sum"],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": { "mode": "multi", "sort": "desc" }
},
"targets": [
{
"expr": "sum(rate(http_requests_total{job=\"backend\"}[1m]))",
"legendFormat": "Backend - Total",
"refId": "A"
},
{
"expr": "sum(rate(http_requests_total{job=\"frontend\"}[1m]))",
"legendFormat": "Frontend - Total",
"refId": "B"
}
],
"title": "Historique HTTP - Requêtes/sec",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "bars",
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": { "type": "linear" },
"showPoints": "never",
"spanNulls": false,
"stacking": { "group": "A", "mode": "normal" },
"thresholdsStyle": { "mode": "off" }
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [{ "color": "green", "value": null }]
},
"unit": "short"
},
"overrides": [
{
"matcher": { "id": "byRegexp", "options": ".*2[0-9]{2}.*" },
"properties": [{ "id": "color", "value": { "fixedColor": "green", "mode": "fixed" } }]
},
{
"matcher": { "id": "byRegexp", "options": ".*3[0-9]{2}.*" },
"properties": [{ "id": "color", "value": { "fixedColor": "blue", "mode": "fixed" } }]
},
{
"matcher": { "id": "byRegexp", "options": ".*4[0-9]{2}.*" },
"properties": [{ "id": "color", "value": { "fixedColor": "yellow", "mode": "fixed" } }]
},
{
"matcher": { "id": "byRegexp", "options": ".*5[0-9]{2}.*" },
"properties": [{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }]
}
]
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 10 },
"id": 2,
"options": {
"legend": {
"calcs": ["sum"],
"displayMode": "table",
"placement": "right",
"showLegend": true
},
"tooltip": { "mode": "multi", "sort": "desc" }
},
"targets": [
{
"expr": "sum(increase(http_requests_total{job=\"backend\"}[5m])) by (status_code)",
"legendFormat": "Backend {{status_code}}",
"refId": "A"
}
],
"title": "Backend - HTTP par Status Code",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "bars",
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": { "type": "linear" },
"showPoints": "never",
"spanNulls": false,
"stacking": { "group": "A", "mode": "normal" },
"thresholdsStyle": { "mode": "off" }
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [{ "color": "green", "value": null }]
},
"unit": "short"
},
"overrides": [
{
"matcher": { "id": "byRegexp", "options": ".*2[0-9]{2}.*" },
"properties": [{ "id": "color", "value": { "fixedColor": "green", "mode": "fixed" } }]
},
{
"matcher": { "id": "byRegexp", "options": ".*3[0-9]{2}.*" },
"properties": [{ "id": "color", "value": { "fixedColor": "blue", "mode": "fixed" } }]
},
{
"matcher": { "id": "byRegexp", "options": ".*4[0-9]{2}.*" },
"properties": [{ "id": "color", "value": { "fixedColor": "yellow", "mode": "fixed" } }]
},
{
"matcher": { "id": "byRegexp", "options": ".*5[0-9]{2}.*" },
"properties": [{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }]
}
]
},
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 10 },
"id": 3,
"options": {
"legend": {
"calcs": ["sum"],
"displayMode": "table",
"placement": "right",
"showLegend": true
},
"tooltip": { "mode": "multi", "sort": "desc" }
},
"targets": [
{
"expr": "sum(increase(http_requests_total{job=\"frontend\"}[5m])) by (status_code)",
"legendFormat": "Frontend {{status_code}}",
"refId": "A"
}
],
"title": "Frontend - HTTP par Status Code",
"type": "timeseries"
}
],
"refresh": "10s",
"schemaVersion": 38,
"style": "dark",
"tags": ["http", "thetiptop"],
"templating": {
"list": [
{
"current": {},
"hide": 0,
"includeAll": false,
"label": "Prometheus",
"multi": false,
"name": "DS_PROMETHEUS",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
}
]
},
"time": { "from": "now-1h", "to": "now" },
"timepicker": {},
"timezone": "browser",
"title": "Historique HTTP - TheTipTop",
"uid": "historique-http-thetiptop",
"version": 1
}

View File

@ -1,85 +1,41 @@
import express from "express";
import cors from "cors";
import dotenv from "dotenv";
import helmet from "helmet";
import morgan from "morgan";
import client from "prom-client";
import config from "./src/config/env.js";
import { pool } from "./db.js";
import { errorHandler } from "./src/middleware/errorHandler.js";
import { metricsMiddleware } from "./src/middleware/metrics.js";
// Import routes
import authRoutes from "./src/routes/auth.routes.js";
import userRoutes from "./src/routes/user.routes.js";
import gameRoutes from "./src/routes/game.routes.js";
import employeeRoutes from "./src/routes/employee.routes.js";
import adminRoutes from "./src/routes/admin.routes.js";
import drawRoutes from "./src/routes/draw.routes.js";
import newsletterRoutes from "./src/routes/newsletter.routes.js";
import contactRoutes from "./src/routes/contact.routes.js";
dotenv.config();
const app = express();
// CORS doit être configuré AVANT helmet
app.use(
cors({
origin: function (origin, callback) {
const allowedOrigins = [
// Localhost
"http://localhost:3000",
"http://localhost:3001",
"http://localhost:3002",
"http://localhost:3003",
"http://localhost:3004",
"http://localhost:3005",
// Production
"https://dsp5-archi-o24a-15m-g3.fr",
// Dev
"https://dev.dsp5-archi-o24a-15m-g3.fr",
// Preprod
"https://preprod.dsp5-archi-o24a-15m-g3.fr"
];
// Autoriser les requêtes sans origin (Postman, curl, etc.)
if (!origin) return callback(null, true);
if (allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(null, true); // En dev, on autorise tout
}
},
origin: ["http://localhost:3000", "https://dsp5-archi-o24a-15m-g3.fr"],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
exposedHeaders: ['Content-Length', 'X-Request-Id'],
maxAge: 86400, // 24h
})
);
// Helmet avec configuration moins restrictive
app.use(helmet({
crossOriginResourcePolicy: { policy: "cross-origin" },
crossOriginOpenerPolicy: { policy: "same-origin-allow-popups" },
}));
app.use(helmet());
app.use(morgan("tiny"));
app.use(express.json());
// Middleware de métriques HTTP (doit être avant les routes)
app.use(metricsMiddleware);
// Servir les fichiers statiques depuis le dossier public
app.use('/public', express.static('public'));
app.get("/health", (req, res) => {
res.status(200).json({ status: "ok" });
});
// ✅ Route racine (pour test ou monitoring)
app.get("/", (req, res) => {
res.status(200).json({ message: "✅ API The Tip Top en ligne et opérationnelle -branche dev- !" });
res.status(200).json({ message: "✅ API The Tip Top en ligne et opérationnelle !" });
});
// Vérif base de données
app.get("/db-check", async (req, res) => {
try {
const result = await pool.query("SELECT NOW()");
res.json({ message: "✅ DB connectée branche dev", time: result.rows[0].now });
res.json({ message: "✅ DB connectée", time: result.rows[0].now });
} catch (err) {
res.status(500).json({ error: err.message });
}
@ -93,26 +49,8 @@ app.get("/metrics", async (req, res) => {
res.end(await client.register.metrics());
});
// API Routes
app.use("/api/auth", authRoutes);
app.use("/api/users", userRoutes);
app.use("/api/game", gameRoutes);
app.use("/api/employee", employeeRoutes);
app.use("/api/admin", adminRoutes);
app.use("/api/draw", drawRoutes);
app.use("/api/newsletter", newsletterRoutes);
app.use("/api/contact", contactRoutes);
// Error handler (doit être après les routes)
app.use(errorHandler);
// Export app for testing
export default app;
// Lancement serveur (seulement si pas importé par les tests)
if (process.env.NODE_ENV !== 'test') {
const PORT = config.server.port;
app.listen(PORT, "0.0.0.0", () => {
console.log(`🚀 Backend lancé sur 0.0.0.0:${PORT}`);
});
}
// Lancement serveur
const PORT = process.env.PORT || 4000;
app.listen(PORT, "0.0.0.0", () => {
console.log(`🚀 Backend lancé sur 0.0.0.0:${PORT}`);
});

View File

@ -1,44 +1,6 @@
// jest.config.js
export default {
// Use Node's experimental ESM support
testEnvironment: 'node',
// Setup files to run before tests
setupFiles: ['./jest.setup.js'],
// Transform ES modules
transform: {},
// Module name mapper for ES modules
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
// Test match patterns
testMatch: [
'**/test/**/*.test.js',
'**/__tests__/**/*.js',
],
// Coverage configuration
collectCoverageFrom: [
'src/**/*.js',
'!src/**/*.test.js',
'!**/node_modules/**',
],
// Coverage reporters for SonarQube
coverageReporters: ['text', 'lcov', 'html'],
coverageDirectory: 'coverage',
// Ignore patterns
testPathIgnorePatterns: [
'/node_modules/',
'/dist/',
],
// Verbose output
testEnvironment: "node",
transform: {}, // pas de transpilation Babel ici
verbose: true,
// Test timeout (increase for database operations)
testTimeout: 10000,
};

View File

@ -1,13 +0,0 @@
/**
* Jest setup file - configure test environment
*/
// Set test environment variables
process.env.NODE_ENV = 'test';
process.env.JWT_SECRET = 'test-jwt-secret-key-for-testing-purposes-only';
process.env.JWT_EXPIRES_IN = '1h';
process.env.PORT = '3001';
// Disable console logs during tests (optional)
// console.log = jest.fn();
// console.error = jest.fn();

16
node_modules/.bin/acorn generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../acorn/bin/acorn" "$@"
else
exec node "$basedir/../acorn/bin/acorn" "$@"
fi

17
node_modules/.bin/acorn.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\acorn\bin\acorn" %*

28
node_modules/.bin/acorn.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../acorn/bin/acorn" $args
} else {
& "$basedir/node$exe" "$basedir/../acorn/bin/acorn" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../acorn/bin/acorn" $args
} else {
& "node$exe" "$basedir/../acorn/bin/acorn" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/baseline-browser-mapping generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../baseline-browser-mapping/dist/cli.js" "$@"
else
exec node "$basedir/../baseline-browser-mapping/dist/cli.js" "$@"
fi

17
node_modules/.bin/baseline-browser-mapping.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\baseline-browser-mapping\dist\cli.js" %*

28
node_modules/.bin/baseline-browser-mapping.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../baseline-browser-mapping/dist/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../baseline-browser-mapping/dist/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../baseline-browser-mapping/dist/cli.js" $args
} else {
& "node$exe" "$basedir/../baseline-browser-mapping/dist/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/browserslist generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../browserslist/cli.js" "$@"
else
exec node "$basedir/../browserslist/cli.js" "$@"
fi

17
node_modules/.bin/browserslist.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\browserslist\cli.js" %*

28
node_modules/.bin/browserslist.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../browserslist/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../browserslist/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../browserslist/cli.js" $args
} else {
& "node$exe" "$basedir/../browserslist/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/eslint generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../eslint/bin/eslint.js" "$@"
else
exec node "$basedir/../eslint/bin/eslint.js" "$@"
fi

17
node_modules/.bin/eslint.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\eslint\bin\eslint.js" %*

28
node_modules/.bin/eslint.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../eslint/bin/eslint.js" $args
} else {
& "$basedir/node$exe" "$basedir/../eslint/bin/eslint.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../eslint/bin/eslint.js" $args
} else {
& "node$exe" "$basedir/../eslint/bin/eslint.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/esparse generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../esprima/bin/esparse.js" "$@"
else
exec node "$basedir/../esprima/bin/esparse.js" "$@"
fi

17
node_modules/.bin/esparse.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\esprima\bin\esparse.js" %*

28
node_modules/.bin/esparse.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../esprima/bin/esparse.js" $args
} else {
& "$basedir/node$exe" "$basedir/../esprima/bin/esparse.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../esprima/bin/esparse.js" $args
} else {
& "node$exe" "$basedir/../esprima/bin/esparse.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/esvalidate generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../esprima/bin/esvalidate.js" "$@"
else
exec node "$basedir/../esprima/bin/esvalidate.js" "$@"
fi

17
node_modules/.bin/esvalidate.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\esprima\bin\esvalidate.js" %*

28
node_modules/.bin/esvalidate.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../esprima/bin/esvalidate.js" $args
} else {
& "$basedir/node$exe" "$basedir/../esprima/bin/esvalidate.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../esprima/bin/esvalidate.js" $args
} else {
& "node$exe" "$basedir/../esprima/bin/esvalidate.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/glob generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../glob/dist/esm/bin.mjs" "$@"
else
exec node "$basedir/../glob/dist/esm/bin.mjs" "$@"
fi

17
node_modules/.bin/glob.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\glob\dist\esm\bin.mjs" %*

28
node_modules/.bin/glob.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../glob/dist/esm/bin.mjs" $args
} else {
& "$basedir/node$exe" "$basedir/../glob/dist/esm/bin.mjs" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../glob/dist/esm/bin.mjs" $args
} else {
& "node$exe" "$basedir/../glob/dist/esm/bin.mjs" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/import-local-fixture generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../import-local/fixtures/cli.js" "$@"
else
exec node "$basedir/../import-local/fixtures/cli.js" "$@"
fi

17
node_modules/.bin/import-local-fixture.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\import-local\fixtures\cli.js" %*

28
node_modules/.bin/import-local-fixture.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../import-local/fixtures/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../import-local/fixtures/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../import-local/fixtures/cli.js" $args
} else {
& "node$exe" "$basedir/../import-local/fixtures/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/jest generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../jest/bin/jest.js" "$@"
else
exec node "$basedir/../jest/bin/jest.js" "$@"
fi

17
node_modules/.bin/jest.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\jest\bin\jest.js" %*

28
node_modules/.bin/jest.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../jest/bin/jest.js" $args
} else {
& "$basedir/node$exe" "$basedir/../jest/bin/jest.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../jest/bin/jest.js" $args
} else {
& "node$exe" "$basedir/../jest/bin/jest.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/js-yaml generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../js-yaml/bin/js-yaml.js" "$@"
else
exec node "$basedir/../js-yaml/bin/js-yaml.js" "$@"
fi

17
node_modules/.bin/js-yaml.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\js-yaml\bin\js-yaml.js" %*

28
node_modules/.bin/js-yaml.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../js-yaml/bin/js-yaml.js" $args
} else {
& "$basedir/node$exe" "$basedir/../js-yaml/bin/js-yaml.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../js-yaml/bin/js-yaml.js" $args
} else {
& "node$exe" "$basedir/../js-yaml/bin/js-yaml.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/jsesc generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../jsesc/bin/jsesc" "$@"
else
exec node "$basedir/../jsesc/bin/jsesc" "$@"
fi

17
node_modules/.bin/jsesc.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\jsesc\bin\jsesc" %*

28
node_modules/.bin/jsesc.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../jsesc/bin/jsesc" $args
} else {
& "$basedir/node$exe" "$basedir/../jsesc/bin/jsesc" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../jsesc/bin/jsesc" $args
} else {
& "node$exe" "$basedir/../jsesc/bin/jsesc" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/json5 generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../json5/lib/cli.js" "$@"
else
exec node "$basedir/../json5/lib/cli.js" "$@"
fi

17
node_modules/.bin/json5.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\json5\lib\cli.js" %*

28
node_modules/.bin/json5.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../json5/lib/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../json5/lib/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../json5/lib/cli.js" $args
} else {
& "node$exe" "$basedir/../json5/lib/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/mime generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../mime/cli.js" "$@"
else
exec node "$basedir/../mime/cli.js" "$@"
fi

17
node_modules/.bin/mime.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\mime\cli.js" %*

28
node_modules/.bin/mime.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../mime/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../mime/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../mime/cli.js" $args
} else {
& "node$exe" "$basedir/../mime/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/napi-postinstall generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../napi-postinstall/lib/cli.js" "$@"
else
exec node "$basedir/../napi-postinstall/lib/cli.js" "$@"
fi

17
node_modules/.bin/napi-postinstall.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\napi-postinstall\lib\cli.js" %*

28
node_modules/.bin/napi-postinstall.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../napi-postinstall/lib/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../napi-postinstall/lib/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../napi-postinstall/lib/cli.js" $args
} else {
& "node$exe" "$basedir/../napi-postinstall/lib/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/node-gyp-build generated vendored Executable file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../node-gyp-build/bin.js" "$@"
else
exec node "$basedir/../node-gyp-build/bin.js" "$@"
fi

16
node_modules/.bin/node-gyp-build-optional generated vendored Executable file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../node-gyp-build/optional.js" "$@"
else
exec node "$basedir/../node-gyp-build/optional.js" "$@"
fi

17
node_modules/.bin/node-gyp-build-optional.cmd generated vendored Executable file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\node-gyp-build\optional.js" %*

28
node_modules/.bin/node-gyp-build-optional.ps1 generated vendored Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../node-gyp-build/optional.js" $args
} else {
& "$basedir/node$exe" "$basedir/../node-gyp-build/optional.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../node-gyp-build/optional.js" $args
} else {
& "node$exe" "$basedir/../node-gyp-build/optional.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/node-gyp-build-test generated vendored Executable file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../node-gyp-build/build-test.js" "$@"
else
exec node "$basedir/../node-gyp-build/build-test.js" "$@"
fi

17
node_modules/.bin/node-gyp-build-test.cmd generated vendored Executable file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\node-gyp-build\build-test.js" %*

28
node_modules/.bin/node-gyp-build-test.ps1 generated vendored Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../node-gyp-build/build-test.js" $args
} else {
& "$basedir/node$exe" "$basedir/../node-gyp-build/build-test.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../node-gyp-build/build-test.js" $args
} else {
& "node$exe" "$basedir/../node-gyp-build/build-test.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

17
node_modules/.bin/node-gyp-build.cmd generated vendored Executable file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\node-gyp-build\bin.js" %*

28
node_modules/.bin/node-gyp-build.ps1 generated vendored Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../node-gyp-build/bin.js" $args
} else {
& "$basedir/node$exe" "$basedir/../node-gyp-build/bin.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../node-gyp-build/bin.js" $args
} else {
& "node$exe" "$basedir/../node-gyp-build/bin.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/node-which generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../which/bin/node-which" "$@"
else
exec node "$basedir/../which/bin/node-which" "$@"
fi

17
node_modules/.bin/node-which.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\which\bin\node-which" %*

28
node_modules/.bin/node-which.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../which/bin/node-which" $args
} else {
& "$basedir/node$exe" "$basedir/../which/bin/node-which" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../which/bin/node-which" $args
} else {
& "node$exe" "$basedir/../which/bin/node-which" $args
}
$ret=$LASTEXITCODE
}
exit $ret

1
node_modules/.bin/nodemon generated vendored Symbolic link
View File

@ -0,0 +1 @@
../nodemon/bin/nodemon.js

17
node_modules/.bin/nodemon.cmd generated vendored Executable file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\nodemon\bin\nodemon.js" %*

28
node_modules/.bin/nodemon.ps1 generated vendored Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../nodemon/bin/nodemon.js" $args
} else {
& "$basedir/node$exe" "$basedir/../nodemon/bin/nodemon.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../nodemon/bin/nodemon.js" $args
} else {
& "node$exe" "$basedir/../nodemon/bin/nodemon.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

1
node_modules/.bin/nodetouch generated vendored Symbolic link
View File

@ -0,0 +1 @@
../touch/bin/nodetouch.js

17
node_modules/.bin/nodetouch.cmd generated vendored Executable file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\touch\bin\nodetouch.js" %*

28
node_modules/.bin/nodetouch.ps1 generated vendored Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../touch/bin/nodetouch.js" $args
} else {
& "$basedir/node$exe" "$basedir/../touch/bin/nodetouch.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../touch/bin/nodetouch.js" $args
} else {
& "node$exe" "$basedir/../touch/bin/nodetouch.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

Some files were not shown because too many files have changed in this diff Show More