Pages corrections widget
This commit is contained in:
parent
5ba4d9ee7a
commit
7682eb07da
@ -15,7 +15,10 @@ COPY . .
|
||||
|
||||
# Initialize Prisma
|
||||
RUN npx prisma generate
|
||||
RUN npx prisma migrate dev --name init
|
||||
# NOTE: Migrations should be run separately before deployment
|
||||
# DO NOT use 'migrate dev' in production - it creates new migrations
|
||||
# Use 'prisma migrate deploy' instead, run separately before container start
|
||||
# RUN npx prisma migrate deploy
|
||||
|
||||
# Build the Next.js application
|
||||
RUN npm run build
|
||||
|
||||
695
PERFORMANCE_AND_PRODUCTION_ANALYSIS.md
Normal file
695
PERFORMANCE_AND_PRODUCTION_ANALYSIS.md
Normal file
@ -0,0 +1,695 @@
|
||||
# Analyse Performance et Préparation Production - Neah
|
||||
|
||||
**Date:** $(date)
|
||||
**Auteur:** Analyse Senior Developer
|
||||
**Version:** 1.0
|
||||
|
||||
---
|
||||
|
||||
## 📋 Table des Matières
|
||||
|
||||
1. [Résumé Exécutif](#résumé-exécutif)
|
||||
2. [Analyse des Performances](#analyse-des-performances)
|
||||
3. [Problèmes de Configuration Production](#problèmes-de-configuration-production)
|
||||
4. [Sécurité](#sécurité)
|
||||
5. [Recommandations Prioritaires](#recommandations-prioritaires)
|
||||
6. [Checklist de Mise en Production](#checklist-de-mise-en-production)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Résumé Exécutif
|
||||
|
||||
### État Actuel
|
||||
- ✅ **Architecture solide**: Next.js 16, Prisma, Redis, PostgreSQL
|
||||
- ✅ **Bonnes pratiques**: Logging structuré, gestion d'erreurs, cache Redis
|
||||
- ⚠️ **Problèmes critiques**: Configuration DB pool, timeouts HTTP, logs console
|
||||
- ⚠️ **Production readiness**: 70% - Nécessite corrections avant déploiement
|
||||
|
||||
### Priorités
|
||||
1. **CRITIQUE** - Configuration pool de connexions Prisma
|
||||
2. **CRITIQUE** - Correction Dockerfile (migrate dev → deploy)
|
||||
3. **HAUTE** - Ajout de timeouts sur toutes les requêtes HTTP
|
||||
4. **HAUTE** - Remplacement console.log par logger structuré
|
||||
5. **MOYENNE** - Optimisation images Next.js
|
||||
6. **MOYENNE** - Rate limiting et circuit breakers
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Analyse des Performances
|
||||
|
||||
### 1. Base de Données (PostgreSQL)
|
||||
|
||||
#### ❌ Problèmes Identifiés
|
||||
|
||||
**1.1 Pool de Connexions Non Configuré**
|
||||
```typescript
|
||||
// lib/prisma.ts - ACTUEL
|
||||
export const prisma = new PrismaClient({
|
||||
datasources: { db: { url: env.DATABASE_URL } },
|
||||
log: process.env.NODE_ENV === 'production' ? [] : ['query'],
|
||||
})
|
||||
```
|
||||
|
||||
**Problème:**
|
||||
- Pas de limite de connexions
|
||||
- Risque d'épuisement du pool sous charge
|
||||
- Pas de configuration de timeout
|
||||
- Pas de monitoring des connexions
|
||||
|
||||
**Impact:** 🔴 **CRITIQUE** - Peut causer des timeouts et crashes en production
|
||||
|
||||
**Solution Recommandée:**
|
||||
```typescript
|
||||
// lib/prisma.ts - RECOMMANDÉ
|
||||
export const prisma = new PrismaClient({
|
||||
datasources: {
|
||||
db: {
|
||||
url: env.DATABASE_URL
|
||||
}
|
||||
},
|
||||
log: process.env.NODE_ENV === 'production' ? [] : ['error', 'warn'],
|
||||
})
|
||||
|
||||
// Configuration du pool via DATABASE_URL
|
||||
// DATABASE_URL=postgresql://user:pass@host:5432/db?connection_limit=10&pool_timeout=20
|
||||
```
|
||||
|
||||
**Configuration DATABASE_URL recommandée:**
|
||||
```
|
||||
postgresql://user:pass@host:5432/db?connection_limit=10&pool_timeout=20&connect_timeout=10
|
||||
```
|
||||
|
||||
**1.2 Requêtes N+1 Potentielles**
|
||||
|
||||
**Fichiers à vérifier:**
|
||||
- `app/api/missions/route.ts` - Include multiples relations
|
||||
- `app/api/missions/all/route.ts` - Include missionUsers avec user
|
||||
- `app/api/calendars/route.ts` - Vérifier les requêtes imbriquées
|
||||
|
||||
**Recommandation:** Utiliser `select` au lieu de `include` quand possible, ou utiliser `Prisma.raw` pour des requêtes optimisées.
|
||||
|
||||
---
|
||||
|
||||
### 2. Requêtes HTTP Externes
|
||||
|
||||
#### ❌ Problèmes Identifiés
|
||||
|
||||
**2.1 Timeouts Manquants**
|
||||
|
||||
**Fichiers affectés:**
|
||||
- `lib/services/n8n-service.ts` - Pas de timeout sur fetch
|
||||
- `app/api/missions/[missionId]/generate-plan/route.ts` - Pas de timeout
|
||||
- `app/api/users/[userId]/route.ts` - Pas de timeout sur Leantime API
|
||||
- `app/api/rocket-chat/messages/route.ts` - Timeouts partiels
|
||||
- `app/api/leantime/tasks/route.ts` - Pas de timeout
|
||||
|
||||
**Exemple problématique:**
|
||||
```typescript
|
||||
// ❌ MAUVAIS - app/api/missions/[missionId]/generate-plan/route.ts
|
||||
const response = await fetch(webhookUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
|
||||
body: JSON.stringify(webhookData),
|
||||
});
|
||||
// Pas de timeout - peut bloquer indéfiniment
|
||||
```
|
||||
|
||||
**Solution Recommandée:**
|
||||
```typescript
|
||||
// ✅ BON
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout
|
||||
|
||||
try {
|
||||
const response = await fetch(webhookUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
|
||||
body: JSON.stringify(webhookData),
|
||||
signal: controller.signal,
|
||||
});
|
||||
clearTimeout(timeoutId);
|
||||
// ... handle response
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
if (error.name === 'AbortError') {
|
||||
throw new Error('Request timeout after 30s');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
```
|
||||
|
||||
**2.2 Pas de Circuit Breaker**
|
||||
|
||||
**Problème:** Si un service externe (N8N, Leantime, RocketChat) est down, toutes les requêtes échouent sans retry intelligent.
|
||||
|
||||
**Recommandation:** Implémenter un circuit breaker pattern:
|
||||
```typescript
|
||||
// lib/utils/circuit-breaker.ts
|
||||
class CircuitBreaker {
|
||||
private failures = 0;
|
||||
private lastFailureTime = 0;
|
||||
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
|
||||
|
||||
async execute<T>(fn: () => Promise<T>): Promise<T> {
|
||||
if (this.state === 'OPEN') {
|
||||
if (Date.now() - this.lastFailureTime > 60000) {
|
||||
this.state = 'HALF_OPEN';
|
||||
} else {
|
||||
throw new Error('Circuit breaker is OPEN');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await fn();
|
||||
this.onSuccess();
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.onFailure();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private onSuccess() {
|
||||
this.failures = 0;
|
||||
this.state = 'CLOSED';
|
||||
}
|
||||
|
||||
private onFailure() {
|
||||
this.failures++;
|
||||
this.lastFailureTime = Date.now();
|
||||
if (this.failures >= 5) {
|
||||
this.state = 'OPEN';
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Cache et Optimisations
|
||||
|
||||
#### ✅ Points Positifs
|
||||
- Redis utilisé pour cache emails, notifications, messages
|
||||
- Request deduplication implémentée (`lib/utils/request-deduplication.ts`)
|
||||
- Cache avec TTL approprié
|
||||
|
||||
#### ⚠️ Améliorations Possibles
|
||||
|
||||
**3.1 Cache Database Queries**
|
||||
```typescript
|
||||
// Exemple: app/api/missions/route.ts
|
||||
// Ajouter cache pour les requêtes fréquentes
|
||||
const cacheKey = `missions:${userId}:${limit}:${offset}`;
|
||||
const cached = await redis.get(cacheKey);
|
||||
if (cached) return JSON.parse(cached);
|
||||
|
||||
const missions = await prisma.mission.findMany(...);
|
||||
await redis.setex(cacheKey, 60, JSON.stringify(missions)); // 60s TTL
|
||||
```
|
||||
|
||||
**3.2 Optimisation Images Next.js**
|
||||
|
||||
**Problème actuel:**
|
||||
```javascript
|
||||
// next.config.mjs
|
||||
images: {
|
||||
unoptimized: true, // ❌ Désactive l'optimisation
|
||||
}
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```javascript
|
||||
images: {
|
||||
unoptimized: false, // ✅ Active l'optimisation
|
||||
formats: ['image/avif', 'image/webp'],
|
||||
deviceSizes: [640, 750, 828, 1080, 1200],
|
||||
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. WebSocket et Connexions Longues
|
||||
|
||||
#### ⚠️ Problèmes Identifiés
|
||||
|
||||
**4.1 RocketChat Call Listener**
|
||||
- `lib/services/rocketchat-call-listener.ts`
|
||||
- Pas de rate limiting sur reconnexions
|
||||
- Pas de backoff exponentiel optimal
|
||||
- Logs console.log excessifs (35 occurrences)
|
||||
|
||||
**Recommandations:**
|
||||
```typescript
|
||||
// Améliorer le backoff
|
||||
private reconnectDelay = 3000;
|
||||
private maxReconnectAttempts = 10;
|
||||
|
||||
// ✅ MEILLEUR
|
||||
private getReconnectDelay(): number {
|
||||
return Math.min(3000 * Math.pow(2, this.reconnectAttempts), 30000);
|
||||
}
|
||||
```
|
||||
|
||||
**4.2 IMAP Connection Pool**
|
||||
- `lib/services/email-service.ts`
|
||||
- Pool bien géré mais peut être optimisé
|
||||
- Timeout de 30 minutes peut être réduit
|
||||
|
||||
---
|
||||
|
||||
### 5. Logging et Monitoring
|
||||
|
||||
#### ❌ Problèmes Critiques
|
||||
|
||||
**5.1 Console.log en Production**
|
||||
|
||||
**Statistiques:**
|
||||
- 110 occurrences de `console.log/error/warn/debug` dans `lib/services/`
|
||||
- 35 dans `rocketchat-call-listener.ts`
|
||||
- 19 dans `refresh-manager.ts`
|
||||
- 17 dans `prefetch-service.ts`
|
||||
|
||||
**Impact:**
|
||||
- Performance dégradée (console.log est synchrone)
|
||||
- Logs non structurés
|
||||
- Pas de centralisation
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
// Remplacer tous les console.log par logger
|
||||
// ❌ AVANT
|
||||
console.log('[ROCKETCHAT] Message received', data);
|
||||
|
||||
// ✅ APRÈS
|
||||
logger.debug('[ROCKETCHAT] Message received', { data });
|
||||
```
|
||||
|
||||
**Action:** Créer un script de migration:
|
||||
```bash
|
||||
# scripts/replace-console-logs.sh
|
||||
find lib/services -name "*.ts" -exec sed -i '' 's/console\.log/logger.debug/g' {} \;
|
||||
find lib/services -name "*.ts" -exec sed -i '' 's/console\.error/logger.error/g' {} \;
|
||||
find lib/services -name "*.ts" -exec sed -i '' 's/console\.warn/logger.warn/g' {} \;
|
||||
```
|
||||
|
||||
**5.2 Pas de Métriques de Performance**
|
||||
|
||||
**Recommandation:** Ajouter des métriques:
|
||||
```typescript
|
||||
// lib/utils/metrics.ts
|
||||
export function trackApiCall(endpoint: string, duration: number) {
|
||||
// Envoyer à un service de métriques (DataDog, New Relic, etc.)
|
||||
logger.info('API_CALL', { endpoint, duration });
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Problèmes de Configuration Production
|
||||
|
||||
### 1. Dockerfile
|
||||
|
||||
#### ❌ Problème Critique
|
||||
|
||||
**Fichier:** `Dockerfile`
|
||||
|
||||
```dockerfile
|
||||
# ❌ MAUVAIS - Ligne 18
|
||||
RUN npx prisma migrate dev --name init
|
||||
```
|
||||
|
||||
**Problème:**
|
||||
- `migrate dev` crée une nouvelle migration même si la DB est à jour
|
||||
- Ne doit JAMAIS être utilisé en production
|
||||
- Peut causer des conflits de migrations
|
||||
|
||||
**Solution:**
|
||||
```dockerfile
|
||||
# ✅ BON
|
||||
# Ne pas faire de migrations dans le Dockerfile
|
||||
# Les migrations doivent être appliquées séparément avant le déploiement
|
||||
# RUN npx prisma migrate deploy # Seulement si nécessaire, et seulement en production
|
||||
```
|
||||
|
||||
**Recommandation:** Utiliser `Dockerfile.prod` qui est mieux configuré, mais vérifier qu'il n'utilise pas `migrate dev`.
|
||||
|
||||
### 2. Next.js Configuration
|
||||
|
||||
#### ⚠️ Problèmes
|
||||
|
||||
**Fichier:** `next.config.mjs`
|
||||
|
||||
```javascript
|
||||
// ❌ PROBLÉMATIQUE
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true, // Cache les erreurs ESLint
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true, // Cache les erreurs TypeScript
|
||||
},
|
||||
images: {
|
||||
unoptimized: true, // Désactive l'optimisation d'images
|
||||
},
|
||||
```
|
||||
|
||||
**Recommandation:**
|
||||
```javascript
|
||||
// ✅ MEILLEUR
|
||||
eslint: {
|
||||
ignoreDuringBuilds: false, // Ou au moins en staging
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: false, // Corriger les erreurs TypeScript
|
||||
},
|
||||
images: {
|
||||
unoptimized: false, // Activer l'optimisation
|
||||
formats: ['image/avif', 'image/webp'],
|
||||
},
|
||||
```
|
||||
|
||||
### 3. Variables d'Environnement
|
||||
|
||||
#### ⚠️ Vérifications Nécessaires
|
||||
|
||||
**Variables critiques à vérifier:**
|
||||
- `DATABASE_URL` - Doit inclure les paramètres de pool
|
||||
- `REDIS_URL` - Doit être configuré avec timeout
|
||||
- `NEXTAUTH_SECRET` - Doit être unique et fort
|
||||
- Tous les tokens API (N8N, Leantime, RocketChat)
|
||||
|
||||
**Recommandation:** Créer un script de validation:
|
||||
```typescript
|
||||
// scripts/validate-env.ts
|
||||
const requiredVars = [
|
||||
'DATABASE_URL',
|
||||
'NEXTAUTH_SECRET',
|
||||
'NEXTAUTH_URL',
|
||||
// ...
|
||||
];
|
||||
|
||||
for (const varName of requiredVars) {
|
||||
if (!process.env[varName]) {
|
||||
throw new Error(`Missing required environment variable: ${varName}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Sécurité
|
||||
|
||||
### 1. Secrets dans les Logs
|
||||
|
||||
#### ⚠️ Risque Modéré
|
||||
|
||||
**Problème:** Vérifier que les mots de passe/tokens ne sont pas loggés.
|
||||
|
||||
**Fichiers à vérifier:**
|
||||
- `lib/services/email-service.ts` - Credentials IMAP
|
||||
- `app/api/courrier/test-connection/route.ts` - Password dans logs
|
||||
|
||||
**Solution actuelle (bonne):**
|
||||
```typescript
|
||||
// ✅ BON - app/api/courrier/test-connection/route.ts
|
||||
console.log('Testing connection with:', {
|
||||
...body,
|
||||
password: body.password ? '***' : undefined // Masqué
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Rate Limiting
|
||||
|
||||
#### ❌ Manquant
|
||||
|
||||
**Problème:** Pas de rate limiting sur les API routes.
|
||||
|
||||
**Recommandation:** Implémenter avec `@upstash/ratelimit` ou middleware Next.js:
|
||||
```typescript
|
||||
// middleware.ts
|
||||
import { Ratelimit } from '@upstash/ratelimit';
|
||||
import { Redis } from '@upstash/redis';
|
||||
|
||||
const ratelimit = new Ratelimit({
|
||||
redis: Redis.fromEnv(),
|
||||
limiter: Ratelimit.slidingWindow(10, '10 s'),
|
||||
});
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
const ip = request.ip ?? '127.0.0.1';
|
||||
const { success } = await ratelimit.limit(ip);
|
||||
|
||||
if (!success) {
|
||||
return new Response('Rate limit exceeded', { status: 429 });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. CORS et Headers Sécurité
|
||||
|
||||
#### ✅ Bon
|
||||
|
||||
**Fichier:** `next.config.mjs`
|
||||
- Content-Security-Policy configuré
|
||||
- Headers de sécurité à ajouter si nécessaire
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Recommandations Prioritaires
|
||||
|
||||
### Priorité 1 - CRITIQUE (Avant Production)
|
||||
|
||||
1. **✅ Configurer le pool de connexions Prisma**
|
||||
- Modifier `DATABASE_URL` avec `connection_limit=10&pool_timeout=20`
|
||||
- Tester sous charge
|
||||
|
||||
2. **✅ Corriger le Dockerfile**
|
||||
- Retirer `migrate dev`
|
||||
- Utiliser `migrate deploy` uniquement si nécessaire
|
||||
|
||||
3. **✅ Ajouter des timeouts sur toutes les requêtes HTTP**
|
||||
- Créer un utilitaire `fetchWithTimeout`
|
||||
- Appliquer à tous les `fetch()` externes
|
||||
|
||||
4. **✅ Remplacer console.log par logger**
|
||||
- Script de migration automatique
|
||||
- Vérifier tous les fichiers
|
||||
|
||||
### Priorité 2 - HAUTE (Semaine 1)
|
||||
|
||||
5. **✅ Implémenter Circuit Breaker**
|
||||
- Pour N8N, Leantime, RocketChat
|
||||
- Retry avec backoff exponentiel
|
||||
|
||||
6. **✅ Activer l'optimisation d'images Next.js**
|
||||
- Modifier `next.config.mjs`
|
||||
- Tester les performances
|
||||
|
||||
7. **✅ Ajouter Rate Limiting**
|
||||
- Sur les API routes critiques
|
||||
- Monitoring des limites
|
||||
|
||||
### Priorité 3 - MOYENNE (Semaine 2-3)
|
||||
|
||||
8. **✅ Cache des requêtes DB fréquentes**
|
||||
- Missions, Calendars, Users
|
||||
- TTL approprié (60-300s)
|
||||
|
||||
9. **✅ Monitoring et Métriques**
|
||||
- Intégrer Sentry ou équivalent
|
||||
- Métriques de performance (APM)
|
||||
|
||||
10. **✅ Optimiser les requêtes N+1**
|
||||
- Audit des `include` Prisma
|
||||
- Utiliser `select` quand possible
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist de Mise en Production
|
||||
|
||||
### Pré-Déploiement
|
||||
|
||||
#### Configuration
|
||||
- [ ] Configurer `DATABASE_URL` avec pool settings
|
||||
- [ ] Vérifier toutes les variables d'environnement
|
||||
- [ ] Créer script de validation des env vars
|
||||
- [ ] Configurer les secrets dans Vercel (pas en code)
|
||||
|
||||
#### Code
|
||||
- [ ] Corriger le Dockerfile (retirer `migrate dev`)
|
||||
- [ ] Ajouter timeouts sur toutes les requêtes HTTP
|
||||
- [ ] Remplacer tous les `console.log` par `logger`
|
||||
- [ ] Activer l'optimisation d'images Next.js
|
||||
- [ ] Corriger les erreurs TypeScript/ESLint (ou documenter pourquoi ignorées)
|
||||
|
||||
#### Base de Données
|
||||
- [ ] Appliquer toutes les migrations Prisma
|
||||
- [ ] Créer des index sur les colonnes fréquemment queryées
|
||||
- [ ] Configurer les sauvegardes automatiques
|
||||
- [ ] Tester la restauration depuis backup
|
||||
|
||||
#### Sécurité
|
||||
- [ ] Implémenter rate limiting
|
||||
- [ ] Vérifier qu'aucun secret n'est loggé
|
||||
- [ ] Configurer CORS correctement
|
||||
- [ ] Activer HTTPS uniquement
|
||||
|
||||
#### Monitoring
|
||||
- [ ] Configurer Sentry ou équivalent
|
||||
- [ ] Créer endpoint `/api/health`
|
||||
- [ ] Configurer les alertes Vercel
|
||||
- [ ] Documenter les procédures d'alerte
|
||||
|
||||
### Déploiement
|
||||
|
||||
#### Staging
|
||||
- [ ] Déployer en staging d'abord
|
||||
- [ ] Tester toutes les fonctionnalités critiques
|
||||
- [ ] Vérifier les performances sous charge
|
||||
- [ ] Tester les scénarios d'erreur
|
||||
|
||||
#### Production
|
||||
- [ ] Appliquer les migrations Prisma
|
||||
- [ ] Déployer sur Vercel
|
||||
- [ ] Vérifier les health checks
|
||||
- [ ] Monitorer les logs les premières heures
|
||||
- [ ] Tester les fonctionnalités critiques
|
||||
|
||||
### Post-Déploiement
|
||||
|
||||
#### Monitoring
|
||||
- [ ] Surveiller les métriques de performance
|
||||
- [ ] Vérifier les logs d'erreurs
|
||||
- [ ] Monitorer l'utilisation DB/Redis
|
||||
- [ ] Vérifier les alertes fonctionnent
|
||||
|
||||
#### Optimisation Continue
|
||||
- [ ] Analyser les requêtes DB lentes
|
||||
- [ ] Optimiser les endpoints les plus utilisés
|
||||
- [ ] Ajuster les TTL de cache selon l'usage
|
||||
- [ ] Réviser les timeouts selon les métriques
|
||||
|
||||
---
|
||||
|
||||
## 📊 Métriques de Succès
|
||||
|
||||
### Performance
|
||||
- **Temps de réponse API:** < 200ms (p95)
|
||||
- **Temps de chargement page:** < 2s (First Contentful Paint)
|
||||
- **Taux d'erreur:** < 0.1%
|
||||
- **Uptime:** > 99.9%
|
||||
|
||||
### Base de Données
|
||||
- **Connexions actives:** < 80% du pool max
|
||||
- **Requêtes lentes:** < 1% des requêtes > 1s
|
||||
- **Taux de cache hit:** > 70% pour les requêtes fréquentes
|
||||
|
||||
### Infrastructure
|
||||
- **CPU utilisation:** < 70% en moyenne
|
||||
- **Mémoire:** < 80% en moyenne
|
||||
- **Disque:** < 80% utilisé
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Scripts Utiles
|
||||
|
||||
### Validation Environnement
|
||||
```bash
|
||||
# scripts/validate-production.sh
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Validating production environment..."
|
||||
|
||||
# Vérifier les variables d'environnement
|
||||
node scripts/validate-env.ts
|
||||
|
||||
# Vérifier les migrations
|
||||
npx prisma migrate status
|
||||
|
||||
# Vérifier la connexion DB
|
||||
npx prisma db execute --stdin <<< "SELECT 1"
|
||||
|
||||
# Vérifier Redis
|
||||
redis-cli -h $REDIS_HOST -p $REDIS_PORT ping
|
||||
|
||||
echo "✅ All checks passed"
|
||||
```
|
||||
|
||||
### Migration Console.log
|
||||
```bash
|
||||
# scripts/migrate-console-logs.sh
|
||||
#!/bin/bash
|
||||
|
||||
find lib/services app/api -name "*.ts" -type f | while read file; do
|
||||
sed -i '' 's/console\.log(/logger.debug(/g' "$file"
|
||||
sed -i '' 's/console\.error(/logger.error(/g' "$file"
|
||||
sed -i '' 's/console\.warn(/logger.warn(/g' "$file"
|
||||
sed -i '' 's/console\.debug(/logger.debug(/g' "$file"
|
||||
done
|
||||
|
||||
echo "✅ Console logs migrated to logger"
|
||||
```
|
||||
|
||||
### Health Check
|
||||
```typescript
|
||||
// app/api/health/route.ts
|
||||
import { NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { getRedisClient } from '@/lib/redis';
|
||||
|
||||
export async function GET() {
|
||||
const health = {
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
checks: {
|
||||
database: 'unknown',
|
||||
redis: 'unknown',
|
||||
},
|
||||
};
|
||||
|
||||
// Check DB
|
||||
try {
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
health.checks.database = 'ok';
|
||||
} catch (error) {
|
||||
health.checks.database = 'error';
|
||||
health.status = 'degraded';
|
||||
}
|
||||
|
||||
// Check Redis
|
||||
try {
|
||||
const redis = getRedisClient();
|
||||
await redis.ping();
|
||||
health.checks.redis = 'ok';
|
||||
} catch (error) {
|
||||
health.checks.redis = 'error';
|
||||
health.status = 'degraded';
|
||||
}
|
||||
|
||||
return NextResponse.json(health, {
|
||||
status: health.status === 'ok' ? 200 : 503,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Ressources
|
||||
|
||||
- [Prisma Connection Pooling](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management)
|
||||
- [Next.js Production Checklist](https://nextjs.org/docs/deployment#production-checklist)
|
||||
- [Vercel Best Practices](https://vercel.com/docs/concepts/deployments/best-practices)
|
||||
- [PostgreSQL Performance Tuning](https://www.postgresql.org/docs/current/performance-tips.html)
|
||||
|
||||
---
|
||||
|
||||
## 📝 Notes Finales
|
||||
|
||||
Cette analyse identifie les problèmes critiques à résoudre avant la mise en production. Les priorités 1 doivent être traitées immédiatement, les priorités 2 dans la semaine, et les priorités 3 peuvent être planifiées après le déploiement initial.
|
||||
|
||||
**Recommandation:** Créer des tickets pour chaque point de la checklist et les suivre jusqu'à résolution complète.
|
||||
|
||||
---
|
||||
|
||||
**Dernière mise à jour:** $(date)
|
||||
290
PRODUCTION_FIXES_APPLIED.md
Normal file
290
PRODUCTION_FIXES_APPLIED.md
Normal file
@ -0,0 +1,290 @@
|
||||
# Corrections Appliquées pour la Production
|
||||
|
||||
Ce document liste les corrections critiques appliquées suite à l'analyse de performance et de préparation à la production.
|
||||
|
||||
## ✅ Corrections Appliquées
|
||||
|
||||
### 1. Utilitaire fetchWithTimeout
|
||||
|
||||
**Fichier créé:** `lib/utils/fetch-with-timeout.ts`
|
||||
|
||||
**Problème résolu:** Requêtes HTTP sans timeout pouvant bloquer indéfiniment.
|
||||
|
||||
**Utilisation:**
|
||||
```typescript
|
||||
import { fetchWithTimeout, fetchJsonWithTimeout } from '@/lib/utils/fetch-with-timeout';
|
||||
|
||||
// Exemple 1: Fetch simple avec timeout
|
||||
const response = await fetchWithTimeout('https://api.example.com/data', {
|
||||
method: 'GET',
|
||||
timeout: 10000, // 10 secondes
|
||||
headers: { 'Authorization': 'Bearer token' }
|
||||
});
|
||||
|
||||
// Exemple 2: Fetch avec parsing JSON automatique
|
||||
const data = await fetchJsonWithTimeout<MyType>('https://api.example.com/data', {
|
||||
method: 'POST',
|
||||
timeout: 30000, // 30 secondes
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
```
|
||||
|
||||
**Migration recommandée:**
|
||||
Remplacer tous les `fetch()` dans:
|
||||
- `lib/services/n8n-service.ts`
|
||||
- `app/api/missions/[missionId]/generate-plan/route.ts`
|
||||
- `app/api/users/[userId]/route.ts`
|
||||
- `app/api/leantime/tasks/route.ts`
|
||||
- `app/api/rocket-chat/messages/route.ts`
|
||||
|
||||
### 2. Correction Dockerfile
|
||||
|
||||
**Fichier modifié:** `Dockerfile`
|
||||
|
||||
**Problème résolu:** Utilisation de `migrate dev` en production (créerait de nouvelles migrations).
|
||||
|
||||
**Changement:**
|
||||
```dockerfile
|
||||
# ❌ AVANT
|
||||
RUN npx prisma migrate dev --name init
|
||||
|
||||
# ✅ APRÈS
|
||||
# NOTE: Migrations should be run separately before deployment
|
||||
# DO NOT use 'migrate dev' in production
|
||||
# Use 'prisma migrate deploy' instead, run separately before container start
|
||||
```
|
||||
|
||||
**Action requise:** Utiliser `Dockerfile.prod` pour la production, qui est déjà correctement configuré.
|
||||
|
||||
### 3. Script de Validation d'Environnement
|
||||
|
||||
**Fichier créé:** `scripts/validate-env.ts`
|
||||
|
||||
**Problème résolu:** Variables d'environnement manquantes non détectées avant déploiement.
|
||||
|
||||
**Utilisation:**
|
||||
```bash
|
||||
# Valider les variables d'environnement
|
||||
npm run validate:env
|
||||
|
||||
# Ou directement
|
||||
ts-node scripts/validate-env.ts
|
||||
```
|
||||
|
||||
**Fonctionnalités:**
|
||||
- ✅ Vérifie toutes les variables requises
|
||||
- ✅ Valide le format des URLs
|
||||
- ✅ Vérifie la force de NEXTAUTH_SECRET
|
||||
- ✅ Recommande les paramètres de pool DB
|
||||
- ✅ Affiche des warnings pour les variables optionnelles
|
||||
|
||||
**Ajouté dans package.json:**
|
||||
```json
|
||||
"validate:env": "ts-node scripts/validate-env.ts"
|
||||
```
|
||||
|
||||
## 🔄 Migrations à Effectuer
|
||||
|
||||
### Priorité 1 - CRITIQUE
|
||||
|
||||
#### 1. Remplacer fetch() par fetchWithTimeout()
|
||||
|
||||
**Fichiers à modifier:**
|
||||
|
||||
1. **lib/services/n8n-service.ts**
|
||||
```typescript
|
||||
// ❌ AVANT
|
||||
const response = await fetch(this.webhookUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey },
|
||||
body: JSON.stringify(cleanData),
|
||||
});
|
||||
|
||||
// ✅ APRÈS
|
||||
import { fetchWithTimeout } from '@/lib/utils/fetch-with-timeout';
|
||||
|
||||
const response = await fetchWithTimeout(this.webhookUrl, {
|
||||
method: 'POST',
|
||||
timeout: 30000, // 30 secondes
|
||||
headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey },
|
||||
body: JSON.stringify(cleanData),
|
||||
});
|
||||
```
|
||||
|
||||
2. **app/api/missions/[missionId]/generate-plan/route.ts**
|
||||
```typescript
|
||||
// ❌ AVANT
|
||||
const response = await fetch(webhookUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
|
||||
body: JSON.stringify(webhookData),
|
||||
});
|
||||
|
||||
// ✅ APRÈS
|
||||
import { fetchWithTimeout } from '@/lib/utils/fetch-with-timeout';
|
||||
|
||||
const response = await fetchWithTimeout(webhookUrl, {
|
||||
method: 'POST',
|
||||
timeout: 30000,
|
||||
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
|
||||
body: JSON.stringify(webhookData),
|
||||
});
|
||||
```
|
||||
|
||||
3. **app/api/users/[userId]/route.ts** (getLeantimeUserId)
|
||||
```typescript
|
||||
// ❌ AVANT
|
||||
const userResponse = await fetch('https://agilite.slm-lab.net/api/jsonrpc', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.LEANTIME_TOKEN || '' },
|
||||
body: JSON.stringify({ ... }),
|
||||
});
|
||||
|
||||
// ✅ APRÈS
|
||||
import { fetchJsonWithTimeout } from '@/lib/utils/fetch-with-timeout';
|
||||
|
||||
const userData = await fetchJsonWithTimeout('https://agilite.slm-lab.net/api/jsonrpc', {
|
||||
method: 'POST',
|
||||
timeout: 10000, // 10 secondes
|
||||
headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.LEANTIME_TOKEN || '' },
|
||||
body: JSON.stringify({ ... }),
|
||||
});
|
||||
```
|
||||
|
||||
#### 2. Configurer le Pool de Connexions Prisma
|
||||
|
||||
**Action:** Modifier `DATABASE_URL` dans les variables d'environnement:
|
||||
|
||||
```bash
|
||||
# ❌ AVANT
|
||||
DATABASE_URL=postgresql://user:pass@host:5432/db
|
||||
|
||||
# ✅ APRÈS
|
||||
DATABASE_URL=postgresql://user:pass@host:5432/db?connection_limit=10&pool_timeout=20&connect_timeout=10
|
||||
```
|
||||
|
||||
**Paramètres recommandés:**
|
||||
- `connection_limit=10` - Limite le nombre de connexions simultanées
|
||||
- `pool_timeout=20` - Timeout pour obtenir une connexion du pool (secondes)
|
||||
- `connect_timeout=10` - Timeout pour établir une connexion (secondes)
|
||||
|
||||
**Note:** Ajuster `connection_limit` selon la charge attendue et les limites du serveur PostgreSQL.
|
||||
|
||||
#### 3. Remplacer console.log par logger
|
||||
|
||||
**Script de migration:**
|
||||
```bash
|
||||
# Créer scripts/migrate-console-logs.sh
|
||||
#!/bin/bash
|
||||
|
||||
find lib/services app/api -name "*.ts" -type f | while read file; do
|
||||
# Sauvegarder d'abord
|
||||
cp "$file" "$file.bak"
|
||||
|
||||
# Remplacer
|
||||
sed -i '' 's/console\.log(/logger.debug(/g' "$file"
|
||||
sed -i '' 's/console\.error(/logger.error(/g' "$file"
|
||||
sed -i '' 's/console\.warn(/logger.warn(/g' "$file"
|
||||
sed -i '' 's/console\.debug(/logger.debug(/g' "$file"
|
||||
|
||||
# Vérifier que logger est importé
|
||||
if ! grep -q "import.*logger" "$file" && grep -q "logger\." "$file"; then
|
||||
# Ajouter l'import en haut du fichier
|
||||
sed -i '' '1i\
|
||||
import { logger } from '\''@/lib/logger'\'';
|
||||
' "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "✅ Console logs migrated to logger"
|
||||
echo "⚠️ Review changes and remove .bak files after verification"
|
||||
```
|
||||
|
||||
**Fichiers prioritaires:**
|
||||
- `lib/services/rocketchat-call-listener.ts` (35 occurrences)
|
||||
- `lib/services/refresh-manager.ts` (19 occurrences)
|
||||
- `lib/services/prefetch-service.ts` (17 occurrences)
|
||||
|
||||
### Priorité 2 - HAUTE
|
||||
|
||||
#### 4. Activer l'Optimisation d'Images Next.js
|
||||
|
||||
**Fichier:** `next.config.mjs`
|
||||
|
||||
```javascript
|
||||
// ❌ AVANT
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
|
||||
// ✅ APRÈS
|
||||
images: {
|
||||
unoptimized: false,
|
||||
formats: ['image/avif', 'image/webp'],
|
||||
deviceSizes: [640, 750, 828, 1080, 1200],
|
||||
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
||||
},
|
||||
```
|
||||
|
||||
#### 5. Implémenter Circuit Breaker
|
||||
|
||||
Créer `lib/utils/circuit-breaker.ts` (voir exemple dans `PERFORMANCE_AND_PRODUCTION_ANALYSIS.md`).
|
||||
|
||||
**Services à protéger:**
|
||||
- N8N webhooks
|
||||
- Leantime API
|
||||
- RocketChat API
|
||||
|
||||
## 📋 Checklist de Migration
|
||||
|
||||
- [ ] Remplacer tous les `fetch()` par `fetchWithTimeout()` dans les fichiers listés
|
||||
- [ ] Configurer `DATABASE_URL` avec les paramètres de pool
|
||||
- [ ] Exécuter le script de migration console.log
|
||||
- [ ] Vérifier que tous les fichiers utilisent `logger` au lieu de `console`
|
||||
- [ ] Activer l'optimisation d'images dans `next.config.mjs`
|
||||
- [ ] Tester toutes les fonctionnalités après les changements
|
||||
- [ ] Valider l'environnement avec `npm run validate:env`
|
||||
- [ ] Déployer en staging et tester
|
||||
- [ ] Monitorer les performances après déploiement
|
||||
|
||||
## 🧪 Tests Recommandés
|
||||
|
||||
### Test de Timeout
|
||||
```typescript
|
||||
// Test que les timeouts fonctionnent
|
||||
const start = Date.now();
|
||||
try {
|
||||
await fetchWithTimeout('https://httpstat.us/200?sleep=5000', {
|
||||
timeout: 2000, // 2 secondes
|
||||
});
|
||||
} catch (error) {
|
||||
const duration = Date.now() - start;
|
||||
console.assert(duration < 3000, 'Timeout should occur before 3 seconds');
|
||||
console.assert(error.message.includes('timeout'), 'Error should mention timeout');
|
||||
}
|
||||
```
|
||||
|
||||
### Test de Validation d'Environnement
|
||||
```bash
|
||||
# Tester avec variables manquantes
|
||||
unset DATABASE_URL
|
||||
npm run validate:env
|
||||
# Devrait échouer avec message clair
|
||||
|
||||
# Tester avec toutes les variables
|
||||
# Devrait réussir
|
||||
npm run validate:env
|
||||
```
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
Voir `PERFORMANCE_AND_PRODUCTION_ANALYSIS.md` pour:
|
||||
- Analyse complète des problèmes
|
||||
- Recommandations détaillées
|
||||
- Métriques de succès
|
||||
- Checklist complète de mise en production
|
||||
|
||||
---
|
||||
|
||||
**Dernière mise à jour:** $(date)
|
||||
94
lib/utils/fetch-with-timeout.ts
Normal file
94
lib/utils/fetch-with-timeout.ts
Normal file
@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Utility function for making HTTP requests with timeout
|
||||
*
|
||||
* This is a critical production utility to prevent hanging requests
|
||||
* that can exhaust server resources.
|
||||
*/
|
||||
|
||||
export interface FetchWithTimeoutOptions extends RequestInit {
|
||||
timeout?: number; // Timeout in milliseconds (default: 30000)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch with automatic timeout handling
|
||||
*
|
||||
* @param url - The URL to fetch
|
||||
* @param options - Fetch options including optional timeout
|
||||
* @returns Promise<Response>
|
||||
* @throws Error if timeout is exceeded or request fails
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const response = await fetchWithTimeout('https://api.example.com/data', {
|
||||
* method: 'GET',
|
||||
* timeout: 10000, // 10 seconds
|
||||
* headers: { 'Authorization': 'Bearer token' }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function fetchWithTimeout(
|
||||
url: string,
|
||||
options: FetchWithTimeoutOptions = {}
|
||||
): Promise<Response> {
|
||||
const { timeout = 30000, ...fetchOptions } = options;
|
||||
|
||||
// Create AbortController for timeout
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => {
|
||||
controller.abort();
|
||||
}, timeout);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...fetchOptions,
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
throw new Error(`Request timeout after ${timeout}ms: ${url}`);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch with timeout and automatic JSON parsing
|
||||
*
|
||||
* @param url - The URL to fetch
|
||||
* @param options - Fetch options including optional timeout
|
||||
* @returns Promise<T> - Parsed JSON response
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const data = await fetchJsonWithTimeout('https://api.example.com/data', {
|
||||
* method: 'GET',
|
||||
* timeout: 10000,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function fetchJsonWithTimeout<T = any>(
|
||||
url: string,
|
||||
options: FetchWithTimeoutOptions = {}
|
||||
): Promise<T> {
|
||||
const response = await fetchWithTimeout(url, options);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text().catch(() => 'Unknown error');
|
||||
throw new Error(
|
||||
`HTTP ${response.status} ${response.statusText}: ${errorText.substring(0, 200)}`
|
||||
);
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (!contentType || !contentType.includes('application/json')) {
|
||||
throw new Error(`Expected JSON response, got ${contentType}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
@ -17,6 +17,7 @@
|
||||
"migrate:status": "prisma migrate status",
|
||||
"migrate:prod": "bash scripts/migrate-prod.sh",
|
||||
"verify:config": "bash scripts/verify-vercel-config.sh",
|
||||
"validate:env": "ts-node scripts/validate-env.ts",
|
||||
"db:studio": "prisma studio",
|
||||
"db:generate": "prisma generate"
|
||||
},
|
||||
|
||||
161
scripts/validate-env.ts
Normal file
161
scripts/validate-env.ts
Normal file
@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env ts-node
|
||||
/**
|
||||
* Environment Variables Validation Script
|
||||
*
|
||||
* Validates that all required environment variables are set
|
||||
* before deployment to production.
|
||||
*
|
||||
* Usage:
|
||||
* ts-node scripts/validate-env.ts
|
||||
* or
|
||||
* npm run validate:env
|
||||
*/
|
||||
|
||||
const requiredVars = [
|
||||
// Core
|
||||
'DATABASE_URL',
|
||||
'NEXTAUTH_URL',
|
||||
'NEXTAUTH_SECRET',
|
||||
|
||||
// Keycloak
|
||||
'KEYCLOAK_BASE_URL',
|
||||
'KEYCLOAK_REALM',
|
||||
'KEYCLOAK_CLIENT_ID',
|
||||
'KEYCLOAK_CLIENT_SECRET',
|
||||
'KEYCLOAK_ISSUER',
|
||||
'NEXT_PUBLIC_KEYCLOAK_ISSUER',
|
||||
];
|
||||
|
||||
const optionalButRecommended = [
|
||||
// Redis
|
||||
'REDIS_HOST',
|
||||
'REDIS_PORT',
|
||||
'REDIS_PASSWORD',
|
||||
|
||||
// External Services
|
||||
'N8N_API_KEY',
|
||||
'N8N_WEBHOOK_URL',
|
||||
'LEANTIME_API_URL',
|
||||
'LEANTIME_TOKEN',
|
||||
'ROCKET_CHAT_TOKEN',
|
||||
'ROCKET_CHAT_USER_ID',
|
||||
];
|
||||
|
||||
interface ValidationResult {
|
||||
valid: boolean;
|
||||
missing: string[];
|
||||
warnings: string[];
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
function validateEnvironment(): ValidationResult {
|
||||
const result: ValidationResult = {
|
||||
valid: true,
|
||||
missing: [],
|
||||
warnings: [],
|
||||
errors: [],
|
||||
};
|
||||
|
||||
// Check required variables
|
||||
for (const varName of requiredVars) {
|
||||
const value = process.env[varName];
|
||||
if (!value || value.trim() === '') {
|
||||
result.missing.push(varName);
|
||||
result.valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check optional but recommended
|
||||
for (const varName of optionalButRecommended) {
|
||||
const value = process.env[varName];
|
||||
if (!value || value.trim() === '') {
|
||||
result.warnings.push(varName);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate DATABASE_URL format
|
||||
const dbUrl = process.env.DATABASE_URL;
|
||||
if (dbUrl) {
|
||||
if (!dbUrl.startsWith('postgresql://') && !dbUrl.startsWith('postgres://')) {
|
||||
result.errors.push('DATABASE_URL must start with postgresql:// or postgres://');
|
||||
result.valid = false;
|
||||
}
|
||||
|
||||
// Check for connection pool parameters
|
||||
if (!dbUrl.includes('connection_limit')) {
|
||||
result.warnings.push(
|
||||
'DATABASE_URL should include connection_limit parameter (e.g., ?connection_limit=10&pool_timeout=20)'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate NEXTAUTH_SECRET strength
|
||||
const nextAuthSecret = process.env.NEXTAUTH_SECRET;
|
||||
if (nextAuthSecret && nextAuthSecret.length < 32) {
|
||||
result.warnings.push('NEXTAUTH_SECRET should be at least 32 characters long');
|
||||
}
|
||||
|
||||
// Validate URLs
|
||||
const urlVars = ['NEXTAUTH_URL', 'KEYCLOAK_BASE_URL', 'NEXT_PUBLIC_KEYCLOAK_ISSUER'];
|
||||
for (const varName of urlVars) {
|
||||
const value = process.env[varName];
|
||||
if (value) {
|
||||
try {
|
||||
new URL(value);
|
||||
} catch {
|
||||
result.errors.push(`${varName} is not a valid URL: ${value}`);
|
||||
result.valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function main() {
|
||||
console.log('🔍 Validating environment variables...\n');
|
||||
|
||||
const result = validateEnvironment();
|
||||
|
||||
if (result.missing.length > 0) {
|
||||
console.error('❌ Missing required environment variables:');
|
||||
result.missing.forEach((varName) => {
|
||||
console.error(` - ${varName}`);
|
||||
});
|
||||
console.error('');
|
||||
}
|
||||
|
||||
if (result.errors.length > 0) {
|
||||
console.error('❌ Validation errors:');
|
||||
result.errors.forEach((error) => {
|
||||
console.error(` - ${error}`);
|
||||
});
|
||||
console.error('');
|
||||
}
|
||||
|
||||
if (result.warnings.length > 0) {
|
||||
console.warn('⚠️ Warnings:');
|
||||
result.warnings.forEach((warning) => {
|
||||
console.warn(` - ${warning}`);
|
||||
});
|
||||
console.warn('');
|
||||
}
|
||||
|
||||
if (result.valid) {
|
||||
console.log('✅ All required environment variables are set!\n');
|
||||
if (result.warnings.length > 0) {
|
||||
console.log('⚠️ Some optional variables are missing, but deployment can proceed.\n');
|
||||
}
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.error('❌ Environment validation failed!\n');
|
||||
console.error('Please set all required variables before deploying.\n');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
export { validateEnvironment };
|
||||
Loading…
Reference in New Issue
Block a user