PreProd
This commit is contained in:
parent
c8364191df
commit
0358c87af3
233
.env.example
Normal file
233
.env.example
Normal file
@ -0,0 +1,233 @@
|
||||
# ============================================
|
||||
# NEAH - Variables d'environnement
|
||||
# ============================================
|
||||
# Ce fichier liste toutes les variables d'environnement nécessaires pour Neah.
|
||||
# Copiez ce fichier vers .env.local (développement) ou configurez-les dans votre plateforme de déploiement (Vercel, Docker, etc.)
|
||||
#
|
||||
# IMPORTANT: Ne commitez JAMAIS de fichiers .env avec des valeurs réelles !
|
||||
|
||||
# ============================================
|
||||
# ENVIRONNEMENT & BASE
|
||||
# ============================================
|
||||
NODE_ENV=development
|
||||
# production | development | test
|
||||
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
# URL publique de l'application (utilisée par NextAuth pour les callbacks)
|
||||
# En production: https://votre-domaine.com
|
||||
# En développement: http://localhost:3000
|
||||
|
||||
NEXTAUTH_SECRET=
|
||||
# Secret pour signer les cookies NextAuth (générer avec: openssl rand -base64 32)
|
||||
# OBLIGATOIRE en production
|
||||
|
||||
# ============================================
|
||||
# BASE DE DONNÉES
|
||||
# ============================================
|
||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/calendar_db
|
||||
# URL de connexion PostgreSQL (format: postgresql://user:password@host:port/database)
|
||||
# Pour production auto-hébergée: postgresql://user:password@votre-serveur:5432/calendar_db
|
||||
# Pour SSL: postgresql://user:password@host:5432/db?sslmode=require
|
||||
|
||||
NEWSDB_URL=postgresql://postgres:postgres@localhost:5432/news_db
|
||||
# Base de données séparée pour les actualités (si utilisée)
|
||||
|
||||
NEWS_API_URL=http://localhost:3000/api/news
|
||||
# URL de l'API des actualités
|
||||
|
||||
# ============================================
|
||||
# KEYCLOAK - AUTHENTIFICATION
|
||||
# ============================================
|
||||
KEYCLOAK_BASE_URL=https://keycloak.example.com
|
||||
# URL de base de Keycloak (sans /realms/...)
|
||||
|
||||
KEYCLOAK_REALM=neah
|
||||
# Nom du realm Keycloak
|
||||
|
||||
KEYCLOAK_CLIENT_ID=neah-app
|
||||
# ID du client Keycloak
|
||||
|
||||
KEYCLOAK_CLIENT_SECRET=
|
||||
# Secret du client Keycloak (OBLIGATOIRE)
|
||||
|
||||
KEYCLOAK_ISSUER=https://keycloak.example.com/realms/neah
|
||||
# URL complète de l'issuer Keycloak (format: {BASE_URL}/realms/{REALM})
|
||||
# Alternative à KEYCLOAK_BASE_URL + KEYCLOAK_REALM
|
||||
|
||||
NEXT_PUBLIC_KEYCLOAK_ISSUER=https://keycloak.example.com/realms/neah
|
||||
# Issuer Keycloak accessible côté client (pour les redirections SSO)
|
||||
|
||||
# Optionnel: Authentification admin Keycloak (si nécessaire pour certaines opérations)
|
||||
KEYCLOAK_ADMIN_USERNAME=admin
|
||||
KEYCLOAK_ADMIN_PASSWORD=
|
||||
|
||||
# ============================================
|
||||
# REDIS - CACHE & SESSIONS
|
||||
# ============================================
|
||||
# Option 1: URL complète Redis
|
||||
REDIS_URL=redis://:password@localhost:6379
|
||||
# Format: redis://[:password@]host[:port][/database]
|
||||
|
||||
# Option 2: Paramètres séparés (priorité si REDIS_URL n'est pas défini)
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
# Mot de passe Redis (laisser vide si pas de mot de passe)
|
||||
|
||||
REDIS_ENCRYPTION_KEY=
|
||||
# Clé de chiffrement pour les données sensibles dans Redis (générer avec: openssl rand -base64 32)
|
||||
# Par défaut: 'default-encryption-key-change-in-production' (CHANGER EN PRODUCTION !)
|
||||
|
||||
# ============================================
|
||||
# AWS S3 / MINIO - STOCKAGE FICHIERS
|
||||
# ============================================
|
||||
# Configuration S3 (pour MinIO ou AWS S3 réel)
|
||||
S3_BUCKET=missions
|
||||
# Nom du bucket S3
|
||||
|
||||
# Si utilisation de MinIO (auto-hébergé)
|
||||
MINIO_S3_UPLOAD_BUCKET_URL=http://localhost:9000
|
||||
# URL de l'endpoint MinIO
|
||||
|
||||
MINIO_AWS_REGION=us-east-1
|
||||
# Région AWS (même pour MinIO)
|
||||
|
||||
MINIO_AWS_S3_UPLOAD_BUCKET_NAME=missions
|
||||
# Nom du bucket pour les uploads
|
||||
|
||||
MINIO_ACCESS_KEY=minioadmin
|
||||
# Clé d'accès MinIO
|
||||
|
||||
MINIO_SECRET_KEY=minioadmin
|
||||
# Clé secrète MinIO
|
||||
|
||||
# Si utilisation d'AWS S3 réel (remplacer les variables MINIO_*)
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_REGION=us-east-1
|
||||
AWS_S3_BUCKET=missions
|
||||
|
||||
# ============================================
|
||||
# LEANTIME - GESTION DE PROJET
|
||||
# ============================================
|
||||
LEANTIME_API_URL=https://leantime.example.com
|
||||
# URL de base de l'API Leantime
|
||||
|
||||
LEANTIME_TOKEN=
|
||||
# Token d'API Leantime (OBLIGATOIRE si intégration activée)
|
||||
|
||||
# ============================================
|
||||
# ROCKETCHAT - MESSAGERIE
|
||||
# ============================================
|
||||
ROCKET_CHAT_TOKEN=
|
||||
# Token d'authentification admin RocketChat (OBLIGATOIRE si intégration activée)
|
||||
|
||||
ROCKET_CHAT_USER_ID=
|
||||
# ID de l'utilisateur admin RocketChat (OBLIGATOIRE si intégration activée)
|
||||
|
||||
NEXT_PUBLIC_IFRAME_PAROLE_URL=https://rocketchat.example.com/channel/general
|
||||
# URL publique de RocketChat (pour les iframes)
|
||||
|
||||
# ============================================
|
||||
# N8N - AUTOMATISATION
|
||||
# ============================================
|
||||
N8N_API_KEY=
|
||||
# Clé API N8N pour authentifier les webhooks (OBLIGATOIRE si intégration activée)
|
||||
|
||||
N8N_WEBHOOK_URL=https://brain.slm-lab.net/webhook/mission-created
|
||||
# URL du webhook N8N pour la création de missions
|
||||
|
||||
N8N_ROLLBACK_WEBHOOK_URL=https://brain.slm-lab.net/webhook/mission-rollback
|
||||
# URL du webhook N8N pour le rollback de missions
|
||||
|
||||
N8N_DELETE_WEBHOOK_URL=https://brain.slm-lab.net/webhook/mission-delete
|
||||
# URL du webhook N8N pour la suppression de missions
|
||||
|
||||
NEXT_PUBLIC_API_URL=https://api.slm-lab.net/api
|
||||
# URL publique de l'API Neah (utilisée par N8N pour les callbacks)
|
||||
|
||||
# ============================================
|
||||
# DOLIBARR - ERP
|
||||
# ============================================
|
||||
DOLIBARR_API_URL=https://dolibarr.example.com
|
||||
# URL de base de l'API Dolibarr
|
||||
|
||||
DOLIBARR_API_KEY=
|
||||
# Clé API Dolibarr (OBLIGATOIRE si intégration activée)
|
||||
|
||||
# ============================================
|
||||
# MICROSOFT OAUTH (Optionnel)
|
||||
# ============================================
|
||||
MICROSOFT_TENANT_ID=common
|
||||
# ID du tenant Microsoft (ou 'common' pour multi-tenant)
|
||||
|
||||
MICROSOFT_CLIENT_ID=
|
||||
# ID du client Microsoft OAuth
|
||||
|
||||
MICROSOFT_CLIENT_SECRET=
|
||||
# Secret du client Microsoft OAuth
|
||||
|
||||
MICROSOFT_REDIRECT_URI=http://localhost:3000/api/auth/callback/microsoft
|
||||
# URI de redirection OAuth Microsoft
|
||||
|
||||
# ============================================
|
||||
# IFRAMES - INTÉGRATIONS EXTERNES
|
||||
# ============================================
|
||||
# URLs des différentes applications intégrées via iframe
|
||||
NEXT_PUBLIC_IFRAME_CARNET_URL=https://carnet.example.com
|
||||
NEXT_PUBLIC_IFRAME_DRIVE_URL=https://drive.example.com
|
||||
NEXT_PUBLIC_IFRAME_LEARN_URL=https://learn.example.com
|
||||
NEXT_PUBLIC_IFRAME_PAROLE_URL=https://rocketchat.example.com/channel/general
|
||||
NEXT_PUBLIC_IFRAME_MISSIONSBOARD_URL=https://missionsboard.example.com
|
||||
NEXT_PUBLIC_IFRAME_CHAPTER_URL=https://chapter.example.com
|
||||
NEXT_PUBLIC_IFRAME_AGILITY_URL=https://agility.example.com
|
||||
NEXT_PUBLIC_IFRAME_ARTLAB_URL=https://artlab.example.com
|
||||
NEXT_PUBLIC_IFRAME_GITE_URL=https://gite.example.com
|
||||
NEXT_PUBLIC_IFRAME_CALCULATION_URL=https://calculation.example.com
|
||||
NEXT_PUBLIC_IFRAME_MEDIATIONS_URL=https://mediations.example.com
|
||||
NEXT_PUBLIC_IFRAME_LIVRE_URL=https://livre.example.com
|
||||
NEXT_PUBLIC_IFRAME_SHOWCASE_URL=https://showcase.example.com
|
||||
NEXT_PUBLIC_IFRAME_RADIO_URL=https://radio.example.com
|
||||
NEXT_PUBLIC_IFRAME_OBSERVATORY_URL=https://observatory.example.com
|
||||
NEXT_PUBLIC_IFRAME_TIMETRACKER_URL=https://timetracker.example.com
|
||||
NEXT_PUBLIC_IFRAME_THEMESSAGE_URL=https://themessage.example.com
|
||||
NEXT_PUBLIC_IFRAME_MISSIONVIEW_URL=https://missionview.example.com
|
||||
NEXT_PUBLIC_IFRAME_CONFERENCE_URL=https://vision.slm-lab.net/MonMeeting
|
||||
|
||||
# ============================================
|
||||
# AUTRES SERVICES
|
||||
# ============================================
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
# URL publique de l'application (pour les scripts et callbacks)
|
||||
|
||||
EQUIPES_API_URL=https://equipes-api.example.com/users
|
||||
# URL de l'API Equipes (si utilisée)
|
||||
|
||||
EQUIPES_API_TOKEN=
|
||||
# Token d'authentification pour l'API Equipes
|
||||
|
||||
# ============================================
|
||||
# NOTES IMPORTANTES
|
||||
# ============================================
|
||||
# 1. Les variables préfixées par NEXT_PUBLIC_ sont exposées côté client (navigateur)
|
||||
# Ne jamais y mettre de secrets !
|
||||
#
|
||||
# 2. Pour la production sur Vercel:
|
||||
# - Configurez toutes ces variables dans: Project Settings > Environment Variables
|
||||
# - Assurez-vous que DATABASE_URL pointe vers votre PostgreSQL auto-hébergé
|
||||
# - Vérifiez que NEXTAUTH_URL correspond à votre domaine Vercel
|
||||
#
|
||||
# 3. Pour la production avec Docker:
|
||||
# - Ajoutez ces variables dans docker-compose.yml ou un fichier .env
|
||||
# - Utilisez des secrets Docker pour les valeurs sensibles
|
||||
#
|
||||
# 4. Génération de secrets:
|
||||
# - NEXTAUTH_SECRET: openssl rand -base64 32
|
||||
# - REDIS_ENCRYPTION_KEY: openssl rand -base64 32
|
||||
# - KEYCLOAK_CLIENT_SECRET: généré dans Keycloak Admin Console
|
||||
#
|
||||
# 5. Variables optionnelles:
|
||||
# - Les variables d'intégration (LEANTIME, ROCKETCHAT, DOLIBARR, N8N) peuvent être omises
|
||||
# si ces services ne sont pas utilisés
|
||||
# - Les variables IFRAME peuvent avoir des valeurs par défaut ou être omises
|
||||
# si les fonctionnalités correspondantes ne sont pas nécessaires
|
||||
65
Dockerfile.prod
Normal file
65
Dockerfile.prod
Normal file
@ -0,0 +1,65 @@
|
||||
# Dockerfile optimisé pour la production Neah
|
||||
# Utilisé uniquement si vous déployez l'application complète avec Docker
|
||||
# Pour Vercel, ce fichier n'est pas nécessaire (Vercel build automatiquement)
|
||||
|
||||
FROM node:22-alpine AS base
|
||||
|
||||
# Installer les dépendances nécessaires pour Prisma
|
||||
RUN apk add --no-cache libc6-compat openssl
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# ============================================
|
||||
# Étape 1: Dépendances
|
||||
# ============================================
|
||||
FROM base AS deps
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci
|
||||
|
||||
# ============================================
|
||||
# Étape 2: Builder
|
||||
# ============================================
|
||||
FROM base AS builder
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Générer le client Prisma
|
||||
RUN npx prisma generate
|
||||
|
||||
# Build Next.js (sans migrations - elles seront appliquées séparément)
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
RUN npm run build
|
||||
|
||||
# ============================================
|
||||
# Étape 3: Runner (image finale)
|
||||
# ============================================
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# Créer un utilisateur non-root pour la sécurité
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
# Copier les fichiers nécessaires depuis le builder
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
|
||||
COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma
|
||||
|
||||
# Changer les permissions
|
||||
RUN chown -R nextjs:nodejs /app
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
# Note: Les migrations Prisma doivent être appliquées séparément avant le démarrage
|
||||
# Utilisez: docker exec <container> npx prisma migrate deploy
|
||||
CMD ["node", "server.js"]
|
||||
66
README.DEPLOYMENT.md
Normal file
66
README.DEPLOYMENT.md
Normal file
@ -0,0 +1,66 @@
|
||||
# Guide de Déploiement - Neah
|
||||
|
||||
Ce document fournit un aperçu rapide des ressources de déploiement disponibles pour Neah.
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- **[DEPLOYMENT.md](docs/DEPLOYMENT.md)**: Guide complet de déploiement étape par étape
|
||||
- **[RUNBOOK.md](docs/RUNBOOK.md)**: Procédures opérationnelles (déploiement, incidents, rollback)
|
||||
- **[OBSERVABILITY.md](docs/OBSERVABILITY.md)**: Stratégie de monitoring et observabilité
|
||||
|
||||
## 🚀 Déploiement rapide
|
||||
|
||||
### Prérequis
|
||||
|
||||
1. Compte Vercel configuré
|
||||
2. Serveur PostgreSQL auto-hébergé
|
||||
3. Variables d'environnement configurées (voir `.env.example`)
|
||||
|
||||
### Étapes
|
||||
|
||||
1. **Configurer les variables d'environnement**
|
||||
|
||||
Copiez `.env.example` et remplissez les valeurs pour la production.
|
||||
|
||||
2. **Vérifier la configuration**
|
||||
|
||||
```bash
|
||||
./scripts/verify-vercel-config.sh
|
||||
```
|
||||
|
||||
3. **Appliquer les migrations Prisma**
|
||||
|
||||
```bash
|
||||
export DATABASE_URL="postgresql://..."
|
||||
./scripts/migrate-prod.sh
|
||||
```
|
||||
|
||||
4. **Déployer sur Vercel**
|
||||
|
||||
```bash
|
||||
git push origin main
|
||||
# Vercel déploiera automatiquement
|
||||
```
|
||||
|
||||
## 📁 Fichiers importants
|
||||
|
||||
- `.env.example`: Liste complète des variables d'environnement
|
||||
- `docker-compose.prod.yml`: Configuration Docker pour PostgreSQL/Redis en production
|
||||
- `vercel.json`: Configuration Vercel
|
||||
- `scripts/migrate-prod.sh`: Script de migration Prisma pour la production
|
||||
- `scripts/verify-vercel-config.sh`: Script de vérification de configuration
|
||||
|
||||
## 🔍 Vérifications post-déploiement
|
||||
|
||||
1. Health check: `GET /api/health`
|
||||
2. Vérifier les logs Vercel
|
||||
3. Tester l'authentification
|
||||
4. Vérifier les fonctionnalités critiques
|
||||
|
||||
## 🆘 En cas de problème
|
||||
|
||||
Consultez [RUNBOOK.md](docs/RUNBOOK.md) pour les procédures d'incident et de rollback.
|
||||
|
||||
## 📞 Support
|
||||
|
||||
Pour toute question sur le déploiement, contactez l'équipe Neah.
|
||||
78
app/api/health/route.ts
Normal file
78
app/api/health/route.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getRedisClient } from '@/lib/redis';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { logger } from '@/lib/logger';
|
||||
|
||||
/**
|
||||
* Health check endpoint
|
||||
*
|
||||
* Vérifie la santé de l'application et de ses dépendances:
|
||||
* - Base de données PostgreSQL
|
||||
* - Redis (si configuré)
|
||||
*
|
||||
* Usage:
|
||||
* GET /api/health
|
||||
*
|
||||
* Réponses:
|
||||
* 200: Tous les services sont opérationnels
|
||||
* 503: Un ou plusieurs services sont indisponibles
|
||||
*/
|
||||
export async function GET() {
|
||||
const startTime = Date.now();
|
||||
const checks: Record<string, { status: string; message?: string; latency?: number }> = {};
|
||||
let overallStatus: 'ok' | 'degraded' | 'down' = 'ok';
|
||||
|
||||
// Vérifier PostgreSQL
|
||||
try {
|
||||
const dbStart = Date.now();
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
const dbLatency = Date.now() - dbStart;
|
||||
checks.database = {
|
||||
status: 'ok',
|
||||
latency: dbLatency,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('[HEALTH] Database check failed', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
checks.database = {
|
||||
status: 'error',
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
overallStatus = 'degraded';
|
||||
}
|
||||
|
||||
// Vérifier Redis (optionnel)
|
||||
try {
|
||||
const redis = getRedisClient();
|
||||
const redisStart = Date.now();
|
||||
await redis.ping();
|
||||
const redisLatency = Date.now() - redisStart;
|
||||
checks.redis = {
|
||||
status: 'ok',
|
||||
latency: redisLatency,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.warn('[HEALTH] Redis check failed', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
// Redis n'est pas critique, donc on ne change pas le statut global
|
||||
checks.redis = {
|
||||
status: 'error',
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
}
|
||||
|
||||
const totalLatency = Date.now() - startTime;
|
||||
|
||||
const response = {
|
||||
status: overallStatus,
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: process.uptime(),
|
||||
checks,
|
||||
latency: totalLatency,
|
||||
};
|
||||
|
||||
const statusCode = overallStatus === 'ok' ? 200 : 503;
|
||||
return NextResponse.json(response, { status: statusCode });
|
||||
}
|
||||
@ -1,69 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { X, Minus, Square } from 'lucide-react';
|
||||
|
||||
// We're now using the global type declaration from types/electron.d.ts
|
||||
|
||||
export function WindowControls() {
|
||||
const [isElectron, setIsElectron] = useState(false);
|
||||
const [isMaximized, setIsMaximized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if we're running in Electron
|
||||
if (window.electron) {
|
||||
setIsElectron(true);
|
||||
|
||||
// Set up listeners for window state
|
||||
const handleMaximize = () => setIsMaximized(true);
|
||||
const handleUnmaximize = () => setIsMaximized(false);
|
||||
|
||||
window.electron.windowState?.onMaximized(handleMaximize);
|
||||
window.electron.windowState?.onUnmaximized(handleUnmaximize);
|
||||
|
||||
// Clean up listeners on unmount
|
||||
return () => {
|
||||
if (window.electron && window.electron.windowState) {
|
||||
window.electron.windowState.removeMaximizedListener();
|
||||
window.electron.windowState.removeUnmaximizedListener();
|
||||
}
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
// If not in Electron, don't render anything
|
||||
if (!isElectron) return null;
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 text-white/70 hover:text-white hover:bg-transparent"
|
||||
onClick={() => window.electron?.windowControl.minimize()}
|
||||
aria-label="Minimize"
|
||||
>
|
||||
<Minus className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 text-white/70 hover:text-white hover:bg-transparent"
|
||||
onClick={() => window.electron?.windowControl.maximize()}
|
||||
aria-label="Maximize"
|
||||
>
|
||||
<Square className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 text-white/70 hover:text-white hover:bg-red-500"
|
||||
onClick={() => window.electron?.windowControl.close()}
|
||||
aria-label="Close"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Minus, Square, X } from 'lucide-react';
|
||||
|
||||
// Import types from our electron.d.ts file
|
||||
declare global {
|
||||
interface Window {
|
||||
electron?: {
|
||||
windowControl: {
|
||||
minimize: () => Promise<void>;
|
||||
maximize: () => Promise<void>;
|
||||
close: () => Promise<void>;
|
||||
};
|
||||
windowState: {
|
||||
onMaximized: (callback: () => void) => void;
|
||||
onUnmaximized: (callback: () => void) => void;
|
||||
removeMaximizedListener: () => void;
|
||||
removeUnmaximizedListener: () => void;
|
||||
};
|
||||
appInfo: {
|
||||
isElectron: boolean;
|
||||
version: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function WindowControls() {
|
||||
const [isElectron, setIsElectron] = useState(false);
|
||||
const [isMaximized, setIsMaximized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if running in Electron
|
||||
if (typeof window !== 'undefined' && window.electron) {
|
||||
setIsElectron(true);
|
||||
|
||||
// Set up event listeners for window state
|
||||
const maximizedHandler = () => setIsMaximized(true);
|
||||
const unmaximizedHandler = () => setIsMaximized(false);
|
||||
|
||||
window.electron.windowState.onMaximized(maximizedHandler);
|
||||
window.electron.windowState.onUnmaximized(unmaximizedHandler);
|
||||
|
||||
return () => {
|
||||
window.electron?.windowState.removeMaximizedListener();
|
||||
window.electron?.windowState.removeUnmaximizedListener();
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!isElectron) return null;
|
||||
|
||||
return (
|
||||
<div className="flex -mr-2 items-center">
|
||||
<button
|
||||
onClick={() => window.electron?.windowControl.minimize()}
|
||||
className="px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
aria-label="Minimize"
|
||||
>
|
||||
<Minus size={16} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => window.electron?.windowControl.maximize()}
|
||||
className="px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
aria-label={isMaximized ? "Restore" : "Maximize"}
|
||||
>
|
||||
<Square size={16} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => window.electron?.windowControl.close()}
|
||||
className="px-4 py-2 hover:bg-red-500 dark:hover:bg-red-600"
|
||||
aria-label="Close"
|
||||
>
|
||||
<X size={16} className="hover:text-white" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -39,7 +39,6 @@ import {
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { NotificationBadge } from "./notification-badge";
|
||||
import { NotesDialog } from "./notes-dialog";
|
||||
import { WindowControls } from "@/components/electron/WindowControls";
|
||||
import { MainNavTime } from "./main-nav-time";
|
||||
|
||||
const requestNotificationPermission = async () => {
|
||||
@ -285,9 +284,6 @@ export function MainNav() {
|
||||
|
||||
{/* Right side */}
|
||||
<div className="flex items-center space-x-2">
|
||||
{/* Electron window controls - only shows in electron environment */}
|
||||
<WindowControls />
|
||||
|
||||
<NotificationBadge />
|
||||
|
||||
{status === "authenticated" && session?.user ? (
|
||||
|
||||
179
docker-compose.prod.yml
Normal file
179
docker-compose.prod.yml
Normal file
@ -0,0 +1,179 @@
|
||||
# ============================================
|
||||
# Docker Compose pour Production Neah
|
||||
# ============================================
|
||||
# Ce fichier est optimisé pour un déploiement en production.
|
||||
# Il configure PostgreSQL et Redis pour être utilisés avec Vercel (Next.js déployé séparément).
|
||||
#
|
||||
# Usage:
|
||||
# docker-compose -f docker-compose.prod.yml up -d
|
||||
#
|
||||
# IMPORTANT:
|
||||
# - Modifiez les mots de passe par défaut avant le déploiement !
|
||||
# - Configurez les volumes pour la persistance des données
|
||||
# - Ajustez les ports selon votre infrastructure réseau
|
||||
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# ============================================
|
||||
# PostgreSQL - Base de données principale
|
||||
# ============================================
|
||||
db:
|
||||
image: postgres:15-alpine
|
||||
container_name: neah-postgres-prod
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-neah_user}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-CHANGE_ME_IN_PRODUCTION}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-calendar_db}
|
||||
# Configuration pour la production
|
||||
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C"
|
||||
ports:
|
||||
# Exposer uniquement sur localhost pour la sécurité (ou utilisez un réseau Docker)
|
||||
- "127.0.0.1:5432:5432"
|
||||
# Pour accès externe depuis Vercel, utilisez plutôt un tunnel SSH ou VPN
|
||||
volumes:
|
||||
# Volume nommé pour la persistance (géré par Docker)
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
# Optionnel: sauvegardes automatiques
|
||||
# - ./backups:/backups
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-neah_user}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
# Configuration réseau (optionnel: créer un réseau isolé)
|
||||
# networks:
|
||||
# - neah_network
|
||||
# Sécurité: limiter les ressources
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2'
|
||||
memory: 2G
|
||||
reservations:
|
||||
cpus: '1'
|
||||
memory: 1G
|
||||
|
||||
# ============================================
|
||||
# Redis - Cache et sessions
|
||||
# ============================================
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: neah-redis-prod
|
||||
restart: unless-stopped
|
||||
command: >
|
||||
redis-server
|
||||
--requirepass ${REDIS_PASSWORD:-CHANGE_ME_IN_PRODUCTION}
|
||||
--appendonly yes
|
||||
--maxmemory 512mb
|
||||
--maxmemory-policy allkeys-lru
|
||||
ports:
|
||||
# Exposer uniquement sur localhost pour la sécurité
|
||||
- "127.0.0.1:6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
# Sécurité: limiter les ressources
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '1'
|
||||
memory: 1G
|
||||
reservations:
|
||||
cpus: '0.5'
|
||||
memory: 512M
|
||||
|
||||
# ============================================
|
||||
# MinIO - Stockage S3-compatible (optionnel)
|
||||
# ============================================
|
||||
# Décommentez si vous utilisez MinIO pour le stockage de fichiers
|
||||
# minio:
|
||||
# image: minio/minio:latest
|
||||
# container_name: neah-minio-prod
|
||||
# restart: unless-stopped
|
||||
# environment:
|
||||
# MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
# MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-CHANGE_ME_IN_PRODUCTION}
|
||||
# ports:
|
||||
# - "127.0.0.1:9000:9000" # API
|
||||
# - "127.0.0.1:9001:9001" # Console
|
||||
# volumes:
|
||||
# - minio_data:/data
|
||||
# command: server /data --console-address ":9001"
|
||||
# healthcheck:
|
||||
# test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
# interval: 30s
|
||||
# timeout: 20s
|
||||
# retries: 3
|
||||
# deploy:
|
||||
# resources:
|
||||
# limits:
|
||||
# cpus: '1'
|
||||
# memory: 1G
|
||||
|
||||
volumes:
|
||||
# Volume pour PostgreSQL - persistance des données
|
||||
postgres_data:
|
||||
driver: local
|
||||
# Optionnel: utiliser un driver de volume externe pour les sauvegardes
|
||||
# driver_opts:
|
||||
# type: nfs
|
||||
# o: addr=nfs-server.example.com,nolock,soft,rw
|
||||
# device: ":/path/to/nfs/share"
|
||||
|
||||
# Volume pour Redis - persistance AOF
|
||||
redis_data:
|
||||
driver: local
|
||||
|
||||
# Volume pour MinIO (si activé)
|
||||
# minio_data:
|
||||
# driver: local
|
||||
|
||||
# ============================================
|
||||
# Réseaux (optionnel)
|
||||
# ============================================
|
||||
# Décommentez pour isoler les services dans un réseau Docker
|
||||
# networks:
|
||||
# neah_network:
|
||||
# driver: bridge
|
||||
# ipam:
|
||||
# config:
|
||||
# - subnet: 172.20.0.0/16
|
||||
|
||||
# ============================================
|
||||
# NOTES IMPORTANTES POUR LA PRODUCTION
|
||||
# ============================================
|
||||
#
|
||||
# 1. SÉCURITÉ:
|
||||
# - Changez TOUS les mots de passe par défaut
|
||||
# - Utilisez des secrets Docker ou un gestionnaire de secrets (HashiCorp Vault, etc.)
|
||||
# - Limitez l'exposition des ports (utilisez 127.0.0.1 au lieu de 0.0.0.0)
|
||||
# - Configurez un firewall pour limiter l'accès aux ports exposés
|
||||
# - Activez SSL/TLS pour PostgreSQL si accessible depuis l'extérieur
|
||||
#
|
||||
# 2. CONNEXION DEPUIS VERCEL:
|
||||
# - Option A: Tunnel SSH (recommandé)
|
||||
# ssh -L 5432:localhost:5432 user@your-server
|
||||
# - Option B: VPN (si disponible)
|
||||
# - Option C: Exposer PostgreSQL avec SSL (moins sécurisé, nécessite configuration SSL)
|
||||
#
|
||||
# 3. SAUVEGARDES:
|
||||
# - Configurez des sauvegardes automatiques de PostgreSQL
|
||||
# - Sauvegardez régulièrement les volumes Docker
|
||||
# - Testez la restauration des sauvegardes
|
||||
#
|
||||
# 4. MONITORING:
|
||||
# - Surveillez l'utilisation des ressources (CPU, mémoire, disque)
|
||||
# - Configurez des alertes pour les problèmes de santé
|
||||
# - Utilisez des outils comme Prometheus + Grafana
|
||||
#
|
||||
# 5. MIGRATIONS PRISMA:
|
||||
# - Exécutez les migrations avant chaque déploiement:
|
||||
# DATABASE_URL="postgresql://..." npx prisma migrate deploy
|
||||
# - Testez les migrations en staging avant la production
|
||||
# - Gardez un plan de rollback pour chaque migration
|
||||
369
docs/DEPLOYMENT.md
Normal file
369
docs/DEPLOYMENT.md
Normal file
@ -0,0 +1,369 @@
|
||||
# Guide de Déploiement en Production - Neah
|
||||
|
||||
Ce document décrit les procédures pour déployer Neah en production avec Vercel (Next.js) et PostgreSQL auto-hébergé.
|
||||
|
||||
## Architecture de Production
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Utilisateurs │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Vercel (Next.js)│
|
||||
│ - Frontend │
|
||||
│ - API Routes │
|
||||
└────────┬────────┘
|
||||
│
|
||||
├─────────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ PostgreSQL │ │ Redis │
|
||||
│ (Auto-hébergé) │ │ (Auto-hébergé) │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
## Prérequis
|
||||
|
||||
- Un compte Vercel
|
||||
- Un serveur pour PostgreSQL et Redis (VPS, VM, ou conteneur Docker)
|
||||
- Accès SSH au serveur de base de données
|
||||
- Node.js 22+ installé localement (pour les migrations)
|
||||
|
||||
## Étape 1: Configuration PostgreSQL en Production
|
||||
|
||||
### 1.1 Déployer PostgreSQL avec Docker Compose
|
||||
|
||||
Sur votre serveur de production:
|
||||
|
||||
```bash
|
||||
# Copier le fichier docker-compose.prod.yml
|
||||
scp docker-compose.prod.yml user@your-server:/opt/neah/
|
||||
|
||||
# Se connecter au serveur
|
||||
ssh user@your-server
|
||||
|
||||
# Créer un fichier .env pour les secrets
|
||||
cd /opt/neah
|
||||
cat > .env << EOF
|
||||
POSTGRES_USER=neah_prod_user
|
||||
POSTGRES_PASSWORD=$(openssl rand -base64 32)
|
||||
POSTGRES_DB=calendar_db
|
||||
REDIS_PASSWORD=$(openssl rand -base64 32)
|
||||
EOF
|
||||
|
||||
# Démarrer les services
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
|
||||
# Vérifier que les services sont en cours d'exécution
|
||||
docker-compose -f docker-compose.prod.yml ps
|
||||
```
|
||||
|
||||
### 1.2 Configurer l'accès réseau
|
||||
|
||||
**Option A: Tunnel SSH (Recommandé pour Vercel)**
|
||||
|
||||
Depuis votre machine locale ou un serveur bastion:
|
||||
|
||||
```bash
|
||||
# Créer un tunnel SSH vers PostgreSQL
|
||||
ssh -L 5432:localhost:5432 -N user@your-server
|
||||
|
||||
# Dans un autre terminal, tester la connexion
|
||||
psql postgresql://neah_prod_user:password@localhost:5432/calendar_db
|
||||
```
|
||||
|
||||
**Option B: Exposer PostgreSQL avec SSL**
|
||||
|
||||
Modifiez `docker-compose.prod.yml` pour activer SSL:
|
||||
|
||||
```yaml
|
||||
db:
|
||||
environment:
|
||||
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C"
|
||||
command: >
|
||||
postgres
|
||||
-c ssl=on
|
||||
-c ssl_cert_file=/var/lib/postgresql/server.crt
|
||||
-c ssl_key_file=/var/lib/postgresql/server.key
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./ssl:/var/lib/postgresql
|
||||
```
|
||||
|
||||
Puis exposez le port avec un firewall configuré pour n'accepter que les IPs Vercel.
|
||||
|
||||
### 1.3 Créer la base de données et appliquer les migrations
|
||||
|
||||
```bash
|
||||
# Se connecter au conteneur PostgreSQL
|
||||
docker exec -it neah-postgres-prod psql -U neah_prod_user -d calendar_db
|
||||
|
||||
# Ou depuis l'extérieur (si accessible)
|
||||
export DATABASE_URL="postgresql://neah_prod_user:password@your-server:5432/calendar_db"
|
||||
npx prisma migrate deploy
|
||||
```
|
||||
|
||||
## Étape 2: Configuration Vercel
|
||||
|
||||
### 2.1 Créer un projet Vercel
|
||||
|
||||
1. Connectez-vous à [Vercel](https://vercel.com)
|
||||
2. Importez votre repository GitHub/GitLab
|
||||
3. Configurez le projet:
|
||||
- **Framework Preset**: Next.js
|
||||
- **Build Command**: `npm run build`
|
||||
- **Output Directory**: `.next` (par défaut)
|
||||
- **Install Command**: `npm ci`
|
||||
|
||||
### 2.2 Configurer les variables d'environnement
|
||||
|
||||
Dans Vercel Dashboard → Project Settings → Environment Variables, ajoutez:
|
||||
|
||||
#### Variables obligatoires
|
||||
|
||||
```env
|
||||
# Environnement
|
||||
NODE_ENV=production
|
||||
NEXTAUTH_URL=https://votre-domaine.vercel.app
|
||||
NEXTAUTH_SECRET=<généré avec: openssl rand -base64 32>
|
||||
|
||||
# Base de données (via tunnel SSH ou URL publique avec SSL)
|
||||
DATABASE_URL=postgresql://user:password@host:5432/calendar_db?sslmode=require
|
||||
|
||||
# Keycloak
|
||||
KEYCLOAK_BASE_URL=https://keycloak.example.com
|
||||
KEYCLOAK_REALM=neah
|
||||
KEYCLOAK_CLIENT_ID=neah-app
|
||||
KEYCLOAK_CLIENT_SECRET=<secret depuis Keycloak>
|
||||
KEYCLOAK_ISSUER=https://keycloak.example.com/realms/neah
|
||||
NEXT_PUBLIC_KEYCLOAK_ISSUER=https://keycloak.example.com/realms/neah
|
||||
|
||||
# Redis (si accessible depuis Vercel)
|
||||
REDIS_URL=redis://:password@your-server:6379
|
||||
# OU
|
||||
REDIS_HOST=your-server
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=<password>
|
||||
REDIS_ENCRYPTION_KEY=<généré avec: openssl rand -base64 32>
|
||||
```
|
||||
|
||||
#### Variables optionnelles (selon vos intégrations)
|
||||
|
||||
```env
|
||||
# Leantime
|
||||
LEANTIME_API_URL=https://leantime.example.com
|
||||
LEANTIME_TOKEN=<token>
|
||||
|
||||
# RocketChat
|
||||
ROCKET_CHAT_TOKEN=<token>
|
||||
ROCKET_CHAT_USER_ID=<user-id>
|
||||
NEXT_PUBLIC_IFRAME_PAROLE_URL=https://rocketchat.example.com/channel/general
|
||||
|
||||
# N8N
|
||||
N8N_API_KEY=<api-key>
|
||||
N8N_WEBHOOK_URL=https://brain.slm-lab.net/webhook/mission-created
|
||||
N8N_ROLLBACK_WEBHOOK_URL=https://brain.slm-lab.net/webhook/mission-rollback
|
||||
N8N_DELETE_WEBHOOK_URL=https://brain.slm-lab.net/webhook/mission-delete
|
||||
NEXT_PUBLIC_API_URL=https://api.slm-lab.net/api
|
||||
|
||||
# Dolibarr
|
||||
DOLIBARR_API_URL=https://dolibarr.example.com
|
||||
DOLIBARR_API_KEY=<api-key>
|
||||
|
||||
# S3 / MinIO
|
||||
S3_BUCKET=missions
|
||||
MINIO_S3_UPLOAD_BUCKET_URL=https://dome-api.slm-lab.net
|
||||
MINIO_AWS_REGION=us-east-1
|
||||
MINIO_AWS_S3_UPLOAD_BUCKET_NAME=missions
|
||||
MINIO_ACCESS_KEY=<access-key>
|
||||
MINIO_SECRET_KEY=<secret-key>
|
||||
|
||||
# Iframes (selon vos besoins)
|
||||
NEXT_PUBLIC_IFRAME_CARNET_URL=https://carnet.example.com
|
||||
NEXT_PUBLIC_IFRAME_DRIVE_URL=https://drive.example.com
|
||||
# ... (voir .env.example pour la liste complète)
|
||||
```
|
||||
|
||||
### 2.3 Configurer le domaine personnalisé (optionnel)
|
||||
|
||||
1. Dans Vercel Dashboard → Settings → Domains
|
||||
2. Ajoutez votre domaine
|
||||
3. Configurez les enregistrements DNS selon les instructions Vercel
|
||||
|
||||
## Étape 3: Migrations Prisma en Production
|
||||
|
||||
### 3.1 Préparer les migrations
|
||||
|
||||
```bash
|
||||
# Vérifier l'état des migrations
|
||||
npx prisma migrate status
|
||||
|
||||
# Créer une nouvelle migration (si nécessaire)
|
||||
npx prisma migrate dev --name nom_de_la_migration
|
||||
```
|
||||
|
||||
### 3.2 Appliquer les migrations en production
|
||||
|
||||
**Méthode 1: Via Vercel Build Hook (Recommandé)**
|
||||
|
||||
Créez un script de migration dans `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"migrate:deploy": "prisma migrate deploy",
|
||||
"postbuild": "npm run migrate:deploy"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Méthode 2: Manuellement avant chaque déploiement**
|
||||
|
||||
```bash
|
||||
# Depuis votre machine locale (avec tunnel SSH actif)
|
||||
export DATABASE_URL="postgresql://user:password@localhost:5432/calendar_db"
|
||||
npx prisma migrate deploy
|
||||
```
|
||||
|
||||
**Méthode 3: Via GitHub Actions (CI/CD)**
|
||||
|
||||
Créez `.github/workflows/migrate.yml`:
|
||||
|
||||
```yaml
|
||||
name: Database Migrations
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
migrate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '22'
|
||||
- run: npm ci
|
||||
- run: npx prisma migrate deploy
|
||||
env:
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
```
|
||||
|
||||
## Étape 4: Vérification et Tests
|
||||
|
||||
### 4.1 Vérifier la connexion à la base de données
|
||||
|
||||
```bash
|
||||
# Tester la connexion PostgreSQL
|
||||
psql $DATABASE_URL -c "SELECT version();"
|
||||
|
||||
# Vérifier les tables Prisma
|
||||
npx prisma db pull
|
||||
```
|
||||
|
||||
### 4.2 Vérifier les services externes
|
||||
|
||||
- Testez l'authentification Keycloak
|
||||
- Vérifiez la connexion Redis (si utilisée)
|
||||
- Testez les intégrations (Leantime, RocketChat, etc.)
|
||||
|
||||
### 4.3 Tests de bout en bout
|
||||
|
||||
1. Accédez à votre application Vercel
|
||||
2. Testez la connexion
|
||||
3. Testez les fonctionnalités critiques:
|
||||
- Création de compte
|
||||
- Authentification
|
||||
- Création de mission
|
||||
- Upload de fichiers
|
||||
- Notifications
|
||||
|
||||
## Étape 5: Monitoring et Maintenance
|
||||
|
||||
### 5.1 Logs Vercel
|
||||
|
||||
- Consultez les logs dans Vercel Dashboard → Deployments → [Déploiement] → Logs
|
||||
- Configurez des alertes pour les erreurs
|
||||
|
||||
### 5.2 Monitoring PostgreSQL
|
||||
|
||||
```bash
|
||||
# Vérifier l'état de PostgreSQL
|
||||
docker exec neah-postgres-prod pg_isready
|
||||
|
||||
# Vérifier l'utilisation des ressources
|
||||
docker stats neah-postgres-prod
|
||||
|
||||
# Vérifier les connexions actives
|
||||
docker exec neah-postgres-prod psql -U neah_prod_user -d calendar_db -c "SELECT count(*) FROM pg_stat_activity;"
|
||||
```
|
||||
|
||||
### 5.3 Sauvegardes
|
||||
|
||||
**Sauvegarde PostgreSQL:**
|
||||
|
||||
```bash
|
||||
# Sauvegarde complète
|
||||
docker exec neah-postgres-prod pg_dump -U neah_prod_user calendar_db > backup_$(date +%Y%m%d).sql
|
||||
|
||||
# Sauvegarde avec compression
|
||||
docker exec neah-postgres-prod pg_dump -U neah_prod_user calendar_db | gzip > backup_$(date +%Y%m%d).sql.gz
|
||||
```
|
||||
|
||||
**Restauration:**
|
||||
|
||||
```bash
|
||||
# Restaurer depuis une sauvegarde
|
||||
cat backup_20240112.sql | docker exec -i neah-postgres-prod psql -U neah_prod_user calendar_db
|
||||
```
|
||||
|
||||
## Procédures de Rollback
|
||||
|
||||
### Rollback Vercel
|
||||
|
||||
1. Dans Vercel Dashboard → Deployments
|
||||
2. Trouvez le déploiement précédent
|
||||
3. Cliquez sur "..." → "Promote to Production"
|
||||
|
||||
### Rollback de migration Prisma
|
||||
|
||||
```bash
|
||||
# Lister les migrations
|
||||
npx prisma migrate status
|
||||
|
||||
# Rollback manuel (si nécessaire)
|
||||
# ATTENTION: Testez d'abord en staging !
|
||||
psql $DATABASE_URL -f prisma/migrations/[migration_to_rollback]/migration.sql
|
||||
```
|
||||
|
||||
## Dépannage
|
||||
|
||||
### Problème: Connexion PostgreSQL échoue depuis Vercel
|
||||
|
||||
- Vérifiez que le tunnel SSH est actif (si utilisé)
|
||||
- Vérifiez les credentials dans Vercel
|
||||
- Vérifiez les règles de firewall
|
||||
- Testez la connexion depuis votre machine locale
|
||||
|
||||
### Problème: Migrations échouent
|
||||
|
||||
- Vérifiez que `DATABASE_URL` est correcte
|
||||
- Vérifiez les permissions de l'utilisateur PostgreSQL
|
||||
- Consultez les logs: `npx prisma migrate deploy --verbose`
|
||||
|
||||
### Problème: Erreurs NextAuth
|
||||
|
||||
- Vérifiez que `NEXTAUTH_URL` correspond à votre domaine Vercel
|
||||
- Vérifiez que `NEXTAUTH_SECRET` est défini
|
||||
- Vérifiez la configuration Keycloak
|
||||
|
||||
## Ressources
|
||||
|
||||
- [Documentation Vercel](https://vercel.com/docs)
|
||||
- [Documentation Prisma](https://www.prisma.io/docs)
|
||||
- [Documentation NextAuth](https://next-auth.js.org)
|
||||
- [Documentation Docker Compose](https://docs.docker.com/compose/)
|
||||
369
docs/OBSERVABILITY.md
Normal file
369
docs/OBSERVABILITY.md
Normal file
@ -0,0 +1,369 @@
|
||||
# Observabilité et Monitoring - Neah
|
||||
|
||||
Ce document décrit la stratégie d'observabilité pour Neah en production.
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
L'observabilité comprend trois piliers:
|
||||
- **Logs**: Enregistrement des événements et erreurs
|
||||
- **Métriques**: Mesures de performance et santé du système
|
||||
- **Traces**: Suivi des requêtes à travers le système
|
||||
|
||||
## 1. Logs
|
||||
|
||||
### 1.1 Logs Vercel
|
||||
|
||||
Vercel fournit des logs intégrés pour chaque déploiement:
|
||||
|
||||
**Accès:**
|
||||
- Dashboard Vercel → Project → Deployments → [Déploiement] → Logs
|
||||
- Ou via CLI: `vercel logs [deployment-url]`
|
||||
|
||||
**Types de logs:**
|
||||
- Build logs: Erreurs de compilation
|
||||
- Runtime logs: Erreurs d'exécution et logs applicatifs
|
||||
- Edge logs: Logs des fonctions Edge
|
||||
|
||||
**Configuration:**
|
||||
|
||||
Les logs sont automatiquement collectés. Pour améliorer la visibilité:
|
||||
|
||||
```typescript
|
||||
// lib/logger.ts (déjà présent dans le projet)
|
||||
import { logger } from '@/lib/logger';
|
||||
|
||||
// Utilisation
|
||||
logger.info('User logged in', { userId: user.id });
|
||||
logger.error('Database connection failed', { error: error.message });
|
||||
logger.warn('Rate limit approaching', { requests: count });
|
||||
```
|
||||
|
||||
### 1.2 Logs PostgreSQL
|
||||
|
||||
**Via Docker:**
|
||||
|
||||
```bash
|
||||
# Voir les logs PostgreSQL
|
||||
docker logs neah-postgres-prod -f
|
||||
|
||||
# Logs avec timestamps
|
||||
docker logs neah-postgres-prod --timestamps -f
|
||||
```
|
||||
|
||||
**Configuration PostgreSQL pour les logs:**
|
||||
|
||||
Modifiez `docker-compose.prod.yml`:
|
||||
|
||||
```yaml
|
||||
db:
|
||||
environment:
|
||||
POSTGRES_LOG_STATEMENT: "all" # ou "ddl", "mod", "none"
|
||||
POSTGRES_LOG_DESTINATION: "stderr"
|
||||
POSTGRES_LOG_TIMESTAMP: "on"
|
||||
```
|
||||
|
||||
### 1.3 Centralisation des logs (Optionnel)
|
||||
|
||||
**Option A: Logtail (Recommandé pour Vercel)**
|
||||
|
||||
1. Créez un compte sur [Logtail](https://logtail.com)
|
||||
2. Ajoutez l'intégration Vercel
|
||||
3. Les logs Vercel seront automatiquement envoyés à Logtail
|
||||
|
||||
**Option B: Papertrail**
|
||||
|
||||
1. Créez un compte sur [Papertrail](https://papertrailapp.com)
|
||||
2. Configurez un endpoint syslog
|
||||
3. Redirigez les logs Docker vers Papertrail:
|
||||
|
||||
```yaml
|
||||
# docker-compose.prod.yml
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
syslog-address: "tcp://logs.papertrailapp.com:XXXXX"
|
||||
```
|
||||
|
||||
**Option C: Self-hosted (Loki + Grafana)**
|
||||
|
||||
Pour une solution auto-hébergée, utilisez Loki + Grafana:
|
||||
|
||||
```yaml
|
||||
# docker-compose.prod.yml (ajout)
|
||||
loki:
|
||||
image: grafana/loki:latest
|
||||
ports:
|
||||
- "3100:3100"
|
||||
volumes:
|
||||
- loki_data:/loki
|
||||
|
||||
promtail:
|
||||
image: grafana/promtail:latest
|
||||
volumes:
|
||||
- /var/lib/docker/containers:/var/lib/docker/containers:ro
|
||||
- ./promtail-config.yml:/etc/promtail/config.yml
|
||||
```
|
||||
|
||||
## 2. Métriques
|
||||
|
||||
### 2.1 Métriques Vercel
|
||||
|
||||
Vercel fournit des métriques intégrées:
|
||||
|
||||
- **Analytics**: Trafic, pages vues, temps de chargement
|
||||
- **Speed Insights**: Core Web Vitals, temps de réponse
|
||||
- **Web Vitals**: LCP, FID, CLS
|
||||
|
||||
**Activation:**
|
||||
|
||||
```bash
|
||||
npm install @vercel/analytics @vercel/speed-insights
|
||||
```
|
||||
|
||||
```typescript
|
||||
// app/layout.tsx
|
||||
import { Analytics } from '@vercel/analytics/react';
|
||||
import { SpeedInsights } from '@vercel/speed-insights/next';
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
{children}
|
||||
<Analytics />
|
||||
<SpeedInsights />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Métriques PostgreSQL
|
||||
|
||||
**Via pg_stat_statements:**
|
||||
|
||||
```sql
|
||||
-- Activer l'extension
|
||||
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
|
||||
|
||||
-- Voir les requêtes les plus lentes
|
||||
SELECT
|
||||
query,
|
||||
calls,
|
||||
total_exec_time,
|
||||
mean_exec_time,
|
||||
max_exec_time
|
||||
FROM pg_stat_statements
|
||||
ORDER BY mean_exec_time DESC
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
**Via Docker:**
|
||||
|
||||
```bash
|
||||
# Statistiques du conteneur
|
||||
docker stats neah-postgres-prod
|
||||
|
||||
# Métriques détaillées
|
||||
docker exec neah-postgres-prod psql -U neah_prod_user -d calendar_db -c "
|
||||
SELECT
|
||||
datname,
|
||||
numbackends,
|
||||
xact_commit,
|
||||
xact_rollback,
|
||||
blks_read,
|
||||
blks_hit,
|
||||
tup_returned,
|
||||
tup_fetched
|
||||
FROM pg_stat_database
|
||||
WHERE datname = 'calendar_db';
|
||||
"
|
||||
```
|
||||
|
||||
### 2.3 Métriques Redis
|
||||
|
||||
```bash
|
||||
# Statistiques Redis
|
||||
docker exec neah-redis-prod redis-cli INFO stats
|
||||
|
||||
# Mémoire utilisée
|
||||
docker exec neah-redis-prod redis-cli INFO memory
|
||||
|
||||
# Commandes les plus utilisées
|
||||
docker exec neah-redis-prod redis-cli INFO commandstats
|
||||
```
|
||||
|
||||
### 2.4 Monitoring avec Prometheus (Optionnel)
|
||||
|
||||
Pour un monitoring avancé, utilisez Prometheus + Grafana:
|
||||
|
||||
```yaml
|
||||
# docker-compose.prod.yml (ajout)
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
volumes:
|
||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- prometheus_data:/prometheus
|
||||
ports:
|
||||
- "9090:9090"
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
ports:
|
||||
- "3001:3000"
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
```
|
||||
|
||||
## 3. Alertes
|
||||
|
||||
### 3.1 Alertes Vercel
|
||||
|
||||
Vercel envoie automatiquement des alertes pour:
|
||||
- Échecs de déploiement
|
||||
- Erreurs critiques
|
||||
- Dépassement de quotas
|
||||
|
||||
**Configuration:**
|
||||
- Dashboard Vercel → Project → Settings → Notifications
|
||||
|
||||
### 3.2 Alertes personnalisées
|
||||
|
||||
**Option A: Sentry (Recommandé)**
|
||||
|
||||
Sentry fournit un suivi d'erreurs avancé:
|
||||
|
||||
```bash
|
||||
npm install @sentry/nextjs
|
||||
```
|
||||
|
||||
```bash
|
||||
npx @sentry/wizard@latest -i nextjs
|
||||
```
|
||||
|
||||
**Option B: Uptime Robot**
|
||||
|
||||
Pour surveiller la disponibilité:
|
||||
1. Créez un compte sur [Uptime Robot](https://uptimerobot.com)
|
||||
2. Ajoutez un monitor HTTP pour votre domaine Vercel
|
||||
3. Configurez les alertes (email, Slack, etc.)
|
||||
|
||||
**Option C: Health Check Endpoint**
|
||||
|
||||
Créez un endpoint de santé:
|
||||
|
||||
```typescript
|
||||
// app/api/health/route.ts
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getRedisClient } from '@/lib/redis';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
export async function GET() {
|
||||
const checks = {
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
checks: {
|
||||
database: 'unknown',
|
||||
redis: 'unknown',
|
||||
},
|
||||
};
|
||||
|
||||
// Vérifier PostgreSQL
|
||||
try {
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
checks.checks.database = 'ok';
|
||||
} catch (error) {
|
||||
checks.checks.database = 'error';
|
||||
checks.status = 'degraded';
|
||||
}
|
||||
|
||||
// Vérifier Redis
|
||||
try {
|
||||
const redis = getRedisClient();
|
||||
await redis.ping();
|
||||
checks.checks.redis = 'ok';
|
||||
} catch (error) {
|
||||
checks.checks.redis = 'error';
|
||||
checks.status = 'degraded';
|
||||
}
|
||||
|
||||
const statusCode = checks.status === 'ok' ? 200 : 503;
|
||||
return NextResponse.json(checks, { status: statusCode });
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Dashboards
|
||||
|
||||
### 4.1 Dashboard Vercel
|
||||
|
||||
Le dashboard Vercel fournit:
|
||||
- Vue d'ensemble des déploiements
|
||||
- Analytics en temps réel
|
||||
- Logs intégrés
|
||||
|
||||
### 4.2 Dashboard personnalisé (Grafana)
|
||||
|
||||
Si vous utilisez Prometheus + Grafana:
|
||||
|
||||
1. Créez un dashboard Grafana
|
||||
2. Ajoutez des panels pour:
|
||||
- Taux d'erreur HTTP
|
||||
- Temps de réponse
|
||||
- Utilisation de la base de données
|
||||
- Utilisation de Redis
|
||||
- Métriques applicatives
|
||||
|
||||
## 5. Bonnes pratiques
|
||||
|
||||
### 5.1 Logging
|
||||
|
||||
- **Niveau approprié**: Utilisez `info`, `warn`, `error` selon le contexte
|
||||
- **Contexte structuré**: Ajoutez des métadonnées pertinentes
|
||||
- **Pas de secrets**: Ne loguez jamais de mots de passe, tokens, etc.
|
||||
- **Format cohérent**: Utilisez un format JSON structuré
|
||||
|
||||
```typescript
|
||||
// ✅ Bon
|
||||
logger.info('User created', {
|
||||
userId: user.id,
|
||||
email: user.email
|
||||
});
|
||||
|
||||
// ❌ Mauvais
|
||||
logger.info(`User created: ${user.password}`); // Ne jamais logger de secrets
|
||||
```
|
||||
|
||||
### 5.2 Monitoring
|
||||
|
||||
- **Métriques clés**: Surveillez le taux d'erreur, latence, débit
|
||||
- **Alertes pertinentes**: Configurez des alertes pour les problèmes critiques uniquement
|
||||
- **SLIs/SLOs**: Définissez des objectifs de niveau de service
|
||||
|
||||
### 5.3 Performance
|
||||
|
||||
- **APM**: Utilisez un outil d'APM (Application Performance Monitoring)
|
||||
- **Profiling**: Profilez régulièrement pour identifier les goulots d'étranglement
|
||||
- **Optimisation**: Optimisez les requêtes lentes identifiées
|
||||
|
||||
## 6. Outils recommandés
|
||||
|
||||
| Outil | Usage | Coût |
|
||||
|-------|-------|------|
|
||||
| Vercel Analytics | Métriques intégrées | Gratuit (plan Hobby) |
|
||||
| Sentry | Suivi d'erreurs | Gratuit (plan Developer) |
|
||||
| Logtail | Centralisation logs | Payant |
|
||||
| Uptime Robot | Monitoring uptime | Gratuit (50 monitors) |
|
||||
| Grafana Cloud | Dashboards | Gratuit (limité) |
|
||||
|
||||
## 7. Checklist de mise en place
|
||||
|
||||
- [ ] Activer Vercel Analytics et Speed Insights
|
||||
- [ ] Configurer Sentry pour le suivi d'erreurs
|
||||
- [ ] Créer un endpoint `/api/health`
|
||||
- [ ] Configurer les alertes Vercel
|
||||
- [ ] Activer les logs PostgreSQL
|
||||
- [ ] Configurer un service de centralisation de logs (optionnel)
|
||||
- [ ] Créer des dashboards de monitoring (optionnel)
|
||||
- [ ] Documenter les procédures d'alerte
|
||||
588
docs/RUNBOOK.md
Normal file
588
docs/RUNBOOK.md
Normal file
@ -0,0 +1,588 @@
|
||||
# Runbook de Production - Neah
|
||||
|
||||
Ce document contient toutes les procédures opérationnelles pour gérer Neah en production.
|
||||
|
||||
## Table des matières
|
||||
|
||||
1. [Déploiement](#déploiement)
|
||||
2. [Exploitation quotidienne](#exploitation-quotidienne)
|
||||
3. [Incidents](#incidents)
|
||||
4. [Rollback](#rollback)
|
||||
5. [Maintenance](#maintenance)
|
||||
6. [Contacts](#contacts)
|
||||
|
||||
---
|
||||
|
||||
## Déploiement
|
||||
|
||||
### Déploiement standard (Vercel)
|
||||
|
||||
#### Prérequis
|
||||
|
||||
- [ ] Toutes les migrations Prisma sont testées en staging
|
||||
- [ ] Les variables d'environnement sont à jour dans Vercel
|
||||
- [ ] Les tests passent localement: `npm run build`
|
||||
|
||||
#### Étapes
|
||||
|
||||
1. **Vérifier l'état actuel**
|
||||
|
||||
```bash
|
||||
# Vérifier les migrations en attente
|
||||
npx prisma migrate status
|
||||
|
||||
# Vérifier la configuration
|
||||
./scripts/verify-vercel-config.sh
|
||||
```
|
||||
|
||||
2. **Appliquer les migrations Prisma**
|
||||
|
||||
```bash
|
||||
# Se connecter au serveur PostgreSQL (via tunnel SSH si nécessaire)
|
||||
export DATABASE_URL="postgresql://user:password@host:5432/calendar_db"
|
||||
|
||||
# Appliquer les migrations
|
||||
./scripts/migrate-prod.sh
|
||||
|
||||
# OU manuellement
|
||||
npx prisma migrate deploy
|
||||
```
|
||||
|
||||
3. **Déployer sur Vercel**
|
||||
|
||||
Le déploiement se fait automatiquement via Git:
|
||||
|
||||
```bash
|
||||
# Pousser les changements vers la branche main
|
||||
git push origin main
|
||||
|
||||
# Vercel déploiera automatiquement
|
||||
# Surveiller le déploiement dans Vercel Dashboard
|
||||
```
|
||||
|
||||
**OU manuellement via CLI:**
|
||||
|
||||
```bash
|
||||
vercel --prod
|
||||
```
|
||||
|
||||
4. **Vérifier le déploiement**
|
||||
|
||||
- [ ] Vérifier les logs Vercel pour les erreurs
|
||||
- [ ] Tester l'endpoint de santé: `GET https://votre-domaine.vercel.app/api/health`
|
||||
- [ ] Tester l'authentification
|
||||
- [ ] Vérifier les fonctionnalités critiques
|
||||
|
||||
### Déploiement avec migrations critiques
|
||||
|
||||
Si les migrations modifient des données existantes:
|
||||
|
||||
1. **Sauvegarder la base de données**
|
||||
|
||||
```bash
|
||||
# Sauvegarde complète
|
||||
docker exec neah-postgres-prod pg_dump -U neah_prod_user calendar_db > backup_$(date +%Y%m%d_%H%M%S).sql
|
||||
|
||||
# Sauvegarde compressée
|
||||
docker exec neah-postgres-prod pg_dump -U neah_prod_user calendar_db | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz
|
||||
```
|
||||
|
||||
2. **Tester les migrations en staging**
|
||||
|
||||
3. **Appliquer en production** (voir procédure standard)
|
||||
|
||||
4. **Vérifier l'intégrité des données**
|
||||
|
||||
```sql
|
||||
-- Exemples de vérifications
|
||||
SELECT COUNT(*) FROM "User";
|
||||
SELECT COUNT(*) FROM "Mission";
|
||||
SELECT COUNT(*) FROM "Event";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Exploitation quotidienne
|
||||
|
||||
### Vérifications quotidiennes
|
||||
|
||||
#### 1. Santé de l'application
|
||||
|
||||
```bash
|
||||
# Vérifier l'endpoint de santé
|
||||
curl https://votre-domaine.vercel.app/api/health
|
||||
|
||||
# Vérifier les logs Vercel récents
|
||||
vercel logs --follow
|
||||
```
|
||||
|
||||
#### 2. Santé de PostgreSQL
|
||||
|
||||
```bash
|
||||
# Se connecter au serveur
|
||||
ssh user@your-server
|
||||
|
||||
# Vérifier l'état du conteneur
|
||||
docker ps | grep neah-postgres-prod
|
||||
|
||||
# Vérifier les connexions actives
|
||||
docker exec neah-postgres-prod psql -U neah_prod_user -d calendar_db -c "
|
||||
SELECT count(*) as active_connections
|
||||
FROM pg_stat_activity
|
||||
WHERE datname = 'calendar_db';
|
||||
"
|
||||
|
||||
# Vérifier l'espace disque
|
||||
docker exec neah-postgres-prod df -h
|
||||
```
|
||||
|
||||
#### 3. Santé de Redis
|
||||
|
||||
```bash
|
||||
# Vérifier l'état du conteneur
|
||||
docker ps | grep neah-redis-prod
|
||||
|
||||
# Vérifier la mémoire utilisée
|
||||
docker exec neah-redis-prod redis-cli INFO memory
|
||||
|
||||
# Vérifier les clés
|
||||
docker exec neah-redis-prod redis-cli DBSIZE
|
||||
```
|
||||
|
||||
#### 4. Métriques Vercel
|
||||
|
||||
- Consulter Vercel Dashboard → Analytics
|
||||
- Vérifier:
|
||||
- Taux d'erreur (< 1%)
|
||||
- Temps de réponse (< 500ms)
|
||||
- Trafic normal
|
||||
|
||||
### Tâches hebdomadaires
|
||||
|
||||
#### Sauvegarde de la base de données
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# scripts/backup-db.sh
|
||||
|
||||
BACKUP_DIR="/opt/neah/backups"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="$BACKUP_DIR/backup_$DATE.sql.gz"
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
docker exec neah-postgres-prod pg_dump -U neah_prod_user calendar_db | gzip > "$BACKUP_FILE"
|
||||
|
||||
# Garder uniquement les 7 derniers backups
|
||||
ls -t $BACKUP_DIR/backup_*.sql.gz | tail -n +8 | xargs rm -f
|
||||
|
||||
echo "Backup créé: $BACKUP_FILE"
|
||||
```
|
||||
|
||||
**Automatisation avec cron:**
|
||||
|
||||
```bash
|
||||
# Ajouter à crontab (crontab -e)
|
||||
0 2 * * * /opt/neah/scripts/backup-db.sh >> /var/log/neah-backup.log 2>&1
|
||||
```
|
||||
|
||||
#### Nettoyage des logs
|
||||
|
||||
```bash
|
||||
# Nettoyer les anciens logs Docker (garder 7 jours)
|
||||
docker system prune -f --filter "until=168h"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Incidents
|
||||
|
||||
### Procédure générale
|
||||
|
||||
1. **Identifier le problème**
|
||||
- Consulter les logs Vercel
|
||||
- Vérifier l'endpoint `/api/health`
|
||||
- Vérifier les logs PostgreSQL/Redis
|
||||
|
||||
2. **Évaluer l'impact**
|
||||
- Nombre d'utilisateurs affectés
|
||||
- Fonctionnalités impactées
|
||||
- Criticité (P1, P2, P3)
|
||||
|
||||
3. **Contenir le problème**
|
||||
- Rollback si nécessaire (voir section Rollback)
|
||||
- Désactiver les fonctionnalités problématiques
|
||||
- Communiquer avec les utilisateurs
|
||||
|
||||
4. **Résoudre**
|
||||
- Corriger le problème
|
||||
- Tester en staging
|
||||
- Déployer en production
|
||||
|
||||
5. **Post-mortem**
|
||||
- Documenter l'incident
|
||||
- Identifier les causes racines
|
||||
- Mettre en place des mesures préventives
|
||||
|
||||
### Scénarios courants
|
||||
|
||||
#### Scénario 1: Application inaccessible (503)
|
||||
|
||||
**Symptômes:**
|
||||
- Erreur 503 sur toutes les pages
|
||||
- Health check échoue
|
||||
|
||||
**Actions:**
|
||||
|
||||
1. Vérifier Vercel Dashboard → Deployments
|
||||
2. Vérifier les logs Vercel
|
||||
3. Vérifier la connexion à PostgreSQL:
|
||||
|
||||
```bash
|
||||
# Tester la connexion
|
||||
psql $DATABASE_URL -c "SELECT 1;"
|
||||
```
|
||||
|
||||
4. Vérifier les variables d'environnement dans Vercel
|
||||
5. Si problème persistant, rollback vers version précédente
|
||||
|
||||
#### Scénario 2: Erreurs de base de données
|
||||
|
||||
**Symptômes:**
|
||||
- Erreurs "Connection refused" ou "Connection timeout"
|
||||
- Health check indique `database: error`
|
||||
|
||||
**Actions:**
|
||||
|
||||
1. Vérifier l'état du conteneur PostgreSQL:
|
||||
|
||||
```bash
|
||||
docker ps | grep neah-postgres-prod
|
||||
docker logs neah-postgres-prod --tail 50
|
||||
```
|
||||
|
||||
2. Vérifier les ressources:
|
||||
|
||||
```bash
|
||||
docker stats neah-postgres-prod
|
||||
```
|
||||
|
||||
3. Redémarrer si nécessaire:
|
||||
|
||||
```bash
|
||||
docker restart neah-postgres-prod
|
||||
```
|
||||
|
||||
4. Vérifier la connexion après redémarrage
|
||||
|
||||
#### Scénario 3: Performance dégradée
|
||||
|
||||
**Symptômes:**
|
||||
- Temps de réponse élevés
|
||||
- Timeouts fréquents
|
||||
|
||||
**Actions:**
|
||||
|
||||
1. Identifier les requêtes lentes:
|
||||
|
||||
```sql
|
||||
-- Requêtes les plus lentes
|
||||
SELECT
|
||||
query,
|
||||
calls,
|
||||
mean_exec_time,
|
||||
max_exec_time
|
||||
FROM pg_stat_statements
|
||||
ORDER BY mean_exec_time DESC
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
2. Vérifier les index manquants:
|
||||
|
||||
```sql
|
||||
-- Tables sans index
|
||||
SELECT
|
||||
schemaname,
|
||||
tablename,
|
||||
attname,
|
||||
n_distinct,
|
||||
correlation
|
||||
FROM pg_stats
|
||||
WHERE schemaname = 'public'
|
||||
AND n_distinct > 100
|
||||
AND correlation < 0.1;
|
||||
```
|
||||
|
||||
3. Optimiser les requêtes ou ajouter des index
|
||||
|
||||
4. Vérifier l'utilisation de Redis (cache)
|
||||
|
||||
#### Scénario 4: Erreurs d'authentification Keycloak
|
||||
|
||||
**Symptômes:**
|
||||
- Utilisateurs ne peuvent pas se connecter
|
||||
- Erreurs "Invalid token" ou "Authentication failed"
|
||||
|
||||
**Actions:**
|
||||
|
||||
1. Vérifier la configuration Keycloak dans Vercel
|
||||
2. Vérifier que Keycloak est accessible:
|
||||
|
||||
```bash
|
||||
curl https://keycloak.example.com/realms/neah/.well-known/openid-configuration
|
||||
```
|
||||
|
||||
3. Vérifier les tokens dans les logs
|
||||
4. Contacter l'administrateur Keycloak si nécessaire
|
||||
|
||||
---
|
||||
|
||||
## Rollback
|
||||
|
||||
### Rollback Vercel
|
||||
|
||||
#### Méthode 1: Via Dashboard (Recommandé)
|
||||
|
||||
1. Aller dans Vercel Dashboard → Deployments
|
||||
2. Trouver le déploiement précédent (stable)
|
||||
3. Cliquer sur "..." → "Promote to Production"
|
||||
4. Confirmer le rollback
|
||||
|
||||
#### Méthode 2: Via CLI
|
||||
|
||||
```bash
|
||||
# Lister les déploiements
|
||||
vercel ls
|
||||
|
||||
# Rollback vers un déploiement spécifique
|
||||
vercel rollback [deployment-url]
|
||||
```
|
||||
|
||||
### Rollback de migration Prisma
|
||||
|
||||
**ATTENTION:** Les rollbacks de migration peuvent être destructifs. Toujours tester en staging d'abord.
|
||||
|
||||
#### Méthode 1: Migration de rollback
|
||||
|
||||
Si une migration de rollback existe:
|
||||
|
||||
```bash
|
||||
export DATABASE_URL="postgresql://..."
|
||||
npx prisma migrate resolve --rolled-back [migration-name]
|
||||
npx prisma migrate deploy
|
||||
```
|
||||
|
||||
#### Méthode 2: Restauration depuis sauvegarde
|
||||
|
||||
```bash
|
||||
# Arrêter l'application si nécessaire
|
||||
# Restaurer la sauvegarde
|
||||
cat backup_20240112_120000.sql | docker exec -i neah-postgres-prod psql -U neah_prod_user calendar_db
|
||||
|
||||
# OU avec compression
|
||||
gunzip < backup_20240112_120000.sql.gz | docker exec -i neah-postgres-prod psql -U neah_prod_user calendar_db
|
||||
|
||||
# Vérifier l'intégrité
|
||||
docker exec neah-postgres-prod psql -U neah_prod_user -d calendar_db -c "SELECT COUNT(*) FROM \"User\";"
|
||||
```
|
||||
|
||||
### Procédure complète de rollback
|
||||
|
||||
1. **Évaluer l'impact**
|
||||
- Identifier les changements à annuler
|
||||
- Vérifier les dépendances
|
||||
|
||||
2. **Sauvegarder l'état actuel**
|
||||
|
||||
```bash
|
||||
# Sauvegarde avant rollback
|
||||
docker exec neah-postgres-prod pg_dump -U neah_prod_user calendar_db > backup_before_rollback_$(date +%Y%m%d_%H%M%S).sql
|
||||
```
|
||||
|
||||
3. **Rollback Vercel** (voir ci-dessus)
|
||||
|
||||
4. **Rollback base de données** (si nécessaire)
|
||||
|
||||
5. **Vérifier**
|
||||
|
||||
```bash
|
||||
# Tester l'endpoint de santé
|
||||
curl https://votre-domaine.vercel.app/api/health
|
||||
|
||||
# Tester les fonctionnalités critiques
|
||||
```
|
||||
|
||||
6. **Communiquer**
|
||||
|
||||
- Informer l'équipe du rollback
|
||||
- Documenter la raison du rollback
|
||||
- Planifier la correction du problème
|
||||
|
||||
---
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Maintenance planifiée
|
||||
|
||||
#### Mise à jour des dépendances
|
||||
|
||||
```bash
|
||||
# Vérifier les mises à jour
|
||||
npm outdated
|
||||
|
||||
# Mettre à jour (une dépendance à la fois)
|
||||
npm update [package-name]
|
||||
|
||||
# Tester en local
|
||||
npm run build
|
||||
npm run dev
|
||||
|
||||
# Tester en staging avant production
|
||||
```
|
||||
|
||||
#### Mise à jour PostgreSQL
|
||||
|
||||
```bash
|
||||
# Arrêter le conteneur
|
||||
docker stop neah-postgres-prod
|
||||
|
||||
# Sauvegarder
|
||||
docker exec neah-postgres-prod pg_dump -U neah_prod_user calendar_db > backup_before_upgrade.sql
|
||||
|
||||
# Mettre à jour l'image dans docker-compose.prod.yml
|
||||
# postgres:15-alpine → postgres:16-alpine
|
||||
|
||||
# Redémarrer
|
||||
docker-compose -f docker-compose.prod.yml up -d db
|
||||
|
||||
# Vérifier
|
||||
docker exec neah-postgres-prod psql -U neah_prod_user -d calendar_db -c "SELECT version();"
|
||||
```
|
||||
|
||||
#### Nettoyage de la base de données
|
||||
|
||||
```sql
|
||||
-- Analyser les tables
|
||||
ANALYZE;
|
||||
|
||||
-- VACUUM (nettoyage)
|
||||
VACUUM ANALYZE;
|
||||
|
||||
-- Vérifier les tables orphelines
|
||||
SELECT
|
||||
schemaname,
|
||||
tablename,
|
||||
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
|
||||
FROM pg_tables
|
||||
WHERE schemaname = 'public'
|
||||
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
|
||||
```
|
||||
|
||||
### Maintenance d'urgence
|
||||
|
||||
#### Redémarrage des services
|
||||
|
||||
```bash
|
||||
# Redémarrer PostgreSQL
|
||||
docker restart neah-postgres-prod
|
||||
|
||||
# Redémarrer Redis
|
||||
docker restart neah-redis-prod
|
||||
|
||||
# Redémarrer tous les services
|
||||
docker-compose -f docker-compose.prod.yml restart
|
||||
```
|
||||
|
||||
#### Libération d'espace disque
|
||||
|
||||
```bash
|
||||
# Vérifier l'espace disque
|
||||
df -h
|
||||
|
||||
# Nettoyer les images Docker non utilisées
|
||||
docker image prune -a
|
||||
|
||||
# Nettoyer les volumes non utilisés
|
||||
docker volume prune
|
||||
|
||||
# Nettoyer les anciens logs
|
||||
journalctl --vacuum-time=7d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Contacts
|
||||
|
||||
### Équipe Neah
|
||||
|
||||
- **DevOps**: [email]
|
||||
- **Développement**: [email]
|
||||
- **Support**: [email]
|
||||
|
||||
### Services externes
|
||||
|
||||
- **Vercel Support**: https://vercel.com/support
|
||||
- **Keycloak Admin**: [contact]
|
||||
- **PostgreSQL**: [contact serveur]
|
||||
|
||||
### Escalade
|
||||
|
||||
1. **Niveau 1**: Équipe Neah
|
||||
2. **Niveau 2**: Administrateur système
|
||||
3. **Niveau 3**: Direction technique
|
||||
|
||||
---
|
||||
|
||||
## Annexes
|
||||
|
||||
### Commandes utiles
|
||||
|
||||
```bash
|
||||
# Vérifier les logs en temps réel
|
||||
vercel logs --follow
|
||||
|
||||
# Vérifier l'état des migrations
|
||||
npx prisma migrate status
|
||||
|
||||
# Tester la connexion PostgreSQL
|
||||
psql $DATABASE_URL -c "SELECT version();"
|
||||
|
||||
# Statistiques PostgreSQL
|
||||
docker exec neah-postgres-prod psql -U neah_prod_user -d calendar_db -c "
|
||||
SELECT
|
||||
datname,
|
||||
numbackends,
|
||||
xact_commit,
|
||||
xact_rollback,
|
||||
blks_read,
|
||||
blks_hit
|
||||
FROM pg_stat_database
|
||||
WHERE datname = 'calendar_db';
|
||||
"
|
||||
|
||||
# Statistiques Redis
|
||||
docker exec neah-redis-prod redis-cli INFO stats
|
||||
```
|
||||
|
||||
### Checklist de déploiement
|
||||
|
||||
- [ ] Migrations testées en staging
|
||||
- [ ] Variables d'environnement à jour
|
||||
- [ ] Build local réussi
|
||||
- [ ] Sauvegarde de la base de données
|
||||
- [ ] Migrations appliquées
|
||||
- [ ] Déploiement Vercel réussi
|
||||
- [ ] Health check OK
|
||||
- [ ] Tests fonctionnels passés
|
||||
- [ ] Logs vérifiés (pas d'erreurs)
|
||||
|
||||
### Checklist de rollback
|
||||
|
||||
- [ ] Sauvegarde avant rollback créée
|
||||
- [ ] Déploiement précédent identifié
|
||||
- [ ] Rollback Vercel effectué
|
||||
- [ ] Rollback base de données (si nécessaire)
|
||||
- [ ] Health check OK
|
||||
- [ ] Tests fonctionnels passés
|
||||
- [ ] Équipe informée
|
||||
- [ ] Problème documenté
|
||||
@ -1,47 +0,0 @@
|
||||
// This file contains helper functions for Electron builds
|
||||
|
||||
const { writeFileSync, existsSync, mkdirSync } = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Create a CSC_LINK placeholder file for macOS builds
|
||||
* In a real production environment, you would use a real certificate
|
||||
*/
|
||||
function createDummyCertificateFile() {
|
||||
const buildDir = path.join(__dirname, '../build');
|
||||
|
||||
// Create build directory if it doesn't exist
|
||||
if (!existsSync(buildDir)) {
|
||||
mkdirSync(buildDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Create a dummy cert file
|
||||
const certPath = path.join(buildDir, 'dummy-cert.p12');
|
||||
writeFileSync(certPath, 'DUMMY CERTIFICATE', 'utf8');
|
||||
|
||||
console.log(`Created dummy certificate at ${certPath}`);
|
||||
return certPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the environment for code signing
|
||||
* This is a placeholder - in production, you'd use real certificates
|
||||
*/
|
||||
function setupCodeSigning() {
|
||||
if (process.platform === 'darwin') {
|
||||
console.log('Setting up macOS code signing');
|
||||
const certPath = createDummyCertificateFile();
|
||||
|
||||
// In a real environment, you would have a real certificate and password
|
||||
process.env.CSC_LINK = certPath;
|
||||
process.env.CSC_KEY_PASSWORD = 'dummy-password';
|
||||
|
||||
console.log('Code signing environment variables set');
|
||||
} else {
|
||||
console.log(`Code signing setup not implemented for ${process.platform}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupCodeSigning
|
||||
};
|
||||
@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
const { execSync } = require('child_process');
|
||||
const { setupCodeSigning } = require('./build-helpers');
|
||||
|
||||
// Set NODE_ENV
|
||||
process.env.NODE_ENV = 'production';
|
||||
|
||||
console.log('Starting Electron build process...');
|
||||
|
||||
try {
|
||||
// Set up code signing if needed
|
||||
setupCodeSigning();
|
||||
|
||||
// Build Next.js app with static export
|
||||
console.log('Building Next.js application...');
|
||||
execSync('cross-env ELECTRON_BUILD=true next build', {
|
||||
stdio: 'inherit',
|
||||
env: { ...process.env }
|
||||
});
|
||||
|
||||
// Build Electron app
|
||||
console.log('Building Electron application...');
|
||||
execSync('electron-builder', {
|
||||
stdio: 'inherit',
|
||||
env: { ...process.env }
|
||||
});
|
||||
|
||||
console.log('Electron build completed successfully!');
|
||||
} catch (error) {
|
||||
console.error('Electron build failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
const { app, BrowserWindow, ipcMain } = require('electron');
|
||||
const path = require('path');
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
const port = process.env.PORT || 3000;
|
||||
const fs = require('fs');
|
||||
const serve = require('electron-serve');
|
||||
|
||||
// Setup for production with static files
|
||||
const loadURL = process.env.NODE_ENV !== 'development'
|
||||
? serve({ directory: path.join(__dirname, '../.next') })
|
||||
: null;
|
||||
|
||||
// Keep a global reference of the window object to prevent garbage collection
|
||||
let mainWindow;
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
},
|
||||
icon: path.join(__dirname, '../public/favicon.ico')
|
||||
});
|
||||
|
||||
// Load the app
|
||||
if (isDev) {
|
||||
// In development, load from local server
|
||||
mainWindow.loadURL(`http://localhost:${port}`);
|
||||
mainWindow.webContents.openDevTools();
|
||||
} else {
|
||||
// In production, load from static files
|
||||
loadURL(mainWindow);
|
||||
}
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
// Handle window maximize/unmaximize
|
||||
mainWindow.on('maximize', () => {
|
||||
mainWindow.webContents.send('window-maximized');
|
||||
});
|
||||
|
||||
mainWindow.on('unmaximize', () => {
|
||||
mainWindow.webContents.send('window-unmaximized');
|
||||
});
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow);
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
// IPC handlers for window controls
|
||||
ipcMain.handle('minimize-window', () => {
|
||||
mainWindow.minimize();
|
||||
});
|
||||
|
||||
ipcMain.handle('maximize-window', () => {
|
||||
if (mainWindow.isMaximized()) {
|
||||
mainWindow.unmaximize();
|
||||
} else {
|
||||
mainWindow.maximize();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('close-window', () => {
|
||||
mainWindow.close();
|
||||
});
|
||||
@ -1,24 +0,0 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
// Expose protected methods that allow the renderer process to use
|
||||
// the ipcRenderer without exposing the entire object
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
// Window control methods
|
||||
windowControl: {
|
||||
minimize: () => ipcRenderer.invoke('minimize-window'),
|
||||
maximize: () => ipcRenderer.invoke('maximize-window'),
|
||||
close: () => ipcRenderer.invoke('close-window'),
|
||||
},
|
||||
// Window state listeners
|
||||
windowState: {
|
||||
onMaximized: (callback) => ipcRenderer.on('window-maximized', callback),
|
||||
onUnmaximized: (callback) => ipcRenderer.on('window-unmaximized', callback),
|
||||
removeMaximizedListener: () => ipcRenderer.removeAllListeners('window-maximized'),
|
||||
removeUnmaximizedListener: () => ipcRenderer.removeAllListeners('window-unmaximized'),
|
||||
},
|
||||
// App info
|
||||
appInfo: {
|
||||
isElectron: true,
|
||||
version: process.env.npm_package_version,
|
||||
}
|
||||
});
|
||||
@ -14,7 +14,9 @@ const nextConfig = {
|
||||
parallelServerBuildTraces: true,
|
||||
parallelServerCompiles: true,
|
||||
},
|
||||
turbopack: {},
|
||||
// Turbopack: activé pour le développement, mais peut causer des problèmes en production
|
||||
// Vercel utilisera son propre système de build optimisé
|
||||
// turbopack: {},
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
@ -27,7 +29,13 @@ const nextConfig = {
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
// Configuration pour Vercel
|
||||
// Assurez-vous que les variables d'environnement sont correctement configurées
|
||||
env: {
|
||||
// Les variables NEXT_PUBLIC_* sont automatiquement exposées par Next.js
|
||||
// Pas besoin de les déclarer ici explicitement
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
44
package.json
44
package.json
@ -3,7 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"description": "Neah Desktop Application",
|
||||
"description": "Neah Web Application",
|
||||
"author": "Neah Team",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@ -11,38 +11,13 @@
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"sync-users": "node scripts/sync-users.js",
|
||||
"electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:3000 && electron electron/main.js\"",
|
||||
"electron:build": "node electron/build.js",
|
||||
"electron:start": "electron electron/main.js"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.neah.app",
|
||||
"productName": "Neah",
|
||||
"files": [
|
||||
"index.js",
|
||||
".next/**/*",
|
||||
"node_modules/**/*",
|
||||
"electron/**/*",
|
||||
"public/**/*",
|
||||
"package.json"
|
||||
],
|
||||
"directories": {
|
||||
"buildResources": "public"
|
||||
},
|
||||
"mac": {
|
||||
"category": "public.app-category.productivity"
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis"
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"AppImage",
|
||||
"deb"
|
||||
]
|
||||
}
|
||||
"migrate:dev": "prisma migrate dev",
|
||||
"migrate:deploy": "prisma migrate deploy",
|
||||
"migrate:status": "prisma migrate status",
|
||||
"migrate:prod": "bash scripts/migrate-prod.sh",
|
||||
"verify:config": "bash scripts/verify-vercel-config.sh",
|
||||
"db:studio": "prisma studio",
|
||||
"db:generate": "prisma generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.802.0",
|
||||
@ -150,9 +125,6 @@
|
||||
"buffer": "^6.0.3",
|
||||
"concurrently": "^9.1.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "^36.1.0",
|
||||
"electron-builder": "^26.0.12",
|
||||
"electron-serve": "^2.1.1",
|
||||
"postcss": "^8",
|
||||
"prisma": "^6.4.1",
|
||||
"stream-browserify": "^3.0.0",
|
||||
|
||||
66
scripts/migrate-prod.sh
Executable file
66
scripts/migrate-prod.sh
Executable file
@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
# Script de migration Prisma pour la production
|
||||
# Usage: ./scripts/migrate-prod.sh [--dry-run]
|
||||
|
||||
set -e
|
||||
|
||||
# Couleurs pour les messages
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Vérifier que DATABASE_URL est définie
|
||||
if [ -z "$DATABASE_URL" ]; then
|
||||
echo -e "${RED}❌ ERREUR: DATABASE_URL n'est pas définie${NC}"
|
||||
echo "Définissez-la avec: export DATABASE_URL='postgresql://...'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Mode dry-run
|
||||
DRY_RUN=false
|
||||
if [ "$1" == "--dry-run" ]; then
|
||||
DRY_RUN=true
|
||||
echo -e "${YELLOW}⚠️ Mode dry-run activé - aucune modification ne sera appliquée${NC}"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}🚀 Migration Prisma pour la production${NC}"
|
||||
echo ""
|
||||
|
||||
# Vérifier la connexion à la base de données
|
||||
echo -e "${YELLOW}📡 Vérification de la connexion à la base de données...${NC}"
|
||||
if ! npx prisma db execute --stdin <<< "SELECT 1;" > /dev/null 2>&1; then
|
||||
echo -e "${RED}❌ Impossible de se connecter à la base de données${NC}"
|
||||
echo "Vérifiez que DATABASE_URL est correcte et que la base de données est accessible"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}✅ Connexion réussie${NC}"
|
||||
echo ""
|
||||
|
||||
# Afficher l'état actuel des migrations
|
||||
echo -e "${YELLOW}📊 État actuel des migrations:${NC}"
|
||||
npx prisma migrate status
|
||||
echo ""
|
||||
|
||||
# Demander confirmation si pas en dry-run
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
echo -e "${YELLOW}⚠️ Vous êtes sur le point d'appliquer les migrations en PRODUCTION${NC}"
|
||||
read -p "Continuer? (oui/non): " -r
|
||||
if [[ ! $REPLY =~ ^[Oo]ui$ ]]; then
|
||||
echo -e "${RED}❌ Migration annulée${NC}"
|
||||
exit 0
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Appliquer les migrations
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
echo -e "${YELLOW}🔄 Application des migrations...${NC}"
|
||||
npx prisma migrate deploy
|
||||
echo -e "${GREEN}✅ Migrations appliquées avec succès${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}ℹ️ Mode dry-run: les migrations ne seraient pas appliquées${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}✨ Migration terminée${NC}"
|
||||
160
scripts/verify-vercel-config.sh
Executable file
160
scripts/verify-vercel-config.sh
Executable file
@ -0,0 +1,160 @@
|
||||
#!/bin/bash
|
||||
# Script de vérification de la configuration pour Vercel
|
||||
# Usage: ./scripts/verify-vercel-config.sh
|
||||
|
||||
set -e
|
||||
|
||||
# Couleurs
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo -e "${BLUE}🔍 Vérification de la configuration pour Vercel${NC}"
|
||||
echo ""
|
||||
|
||||
ERRORS=0
|
||||
WARNINGS=0
|
||||
|
||||
# Fonction pour vérifier une variable d'environnement
|
||||
check_env_var() {
|
||||
local var_name=$1
|
||||
local required=${2:-false}
|
||||
|
||||
if [ -z "${!var_name}" ]; then
|
||||
if [ "$required" = true ]; then
|
||||
echo -e "${RED}❌ ${var_name} n'est pas définie (OBLIGATOIRE)${NC}"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ ${var_name} n'est pas définie (optionnelle)${NC}"
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
fi
|
||||
else
|
||||
echo -e "${GREEN}✅ ${var_name} est définie${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Charger les variables d'environnement depuis .env.local si présent
|
||||
if [ -f .env.local ]; then
|
||||
echo -e "${BLUE}📄 Chargement de .env.local...${NC}"
|
||||
set -a
|
||||
source .env.local
|
||||
set +a
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}📋 Vérification des variables d'environnement critiques:${NC}"
|
||||
echo ""
|
||||
|
||||
# Variables obligatoires
|
||||
check_env_var "NODE_ENV" true
|
||||
check_env_var "NEXTAUTH_URL" true
|
||||
check_env_var "NEXTAUTH_SECRET" true
|
||||
check_env_var "DATABASE_URL" true
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}🔐 Vérification de l'authentification Keycloak:${NC}"
|
||||
check_env_var "KEYCLOAK_BASE_URL" true
|
||||
check_env_var "KEYCLOAK_REALM" true
|
||||
check_env_var "KEYCLOAK_CLIENT_ID" true
|
||||
check_env_var "KEYCLOAK_CLIENT_SECRET" true
|
||||
check_env_var "KEYCLOAK_ISSUER" false
|
||||
check_env_var "NEXT_PUBLIC_KEYCLOAK_ISSUER" false
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}💾 Vérification de la base de données:${NC}"
|
||||
if [ -n "$DATABASE_URL" ]; then
|
||||
if [[ "$DATABASE_URL" == postgresql://* ]]; then
|
||||
echo -e "${GREEN}✅ DATABASE_URL semble être une URL PostgreSQL valide${NC}"
|
||||
|
||||
# Vérifier si SSL est activé (recommandé pour la production)
|
||||
if [[ "$DATABASE_URL" != *"sslmode=require"* ]] && [[ "$DATABASE_URL" != *"sslmode=prefer"* ]]; then
|
||||
echo -e "${YELLOW}⚠️ DATABASE_URL ne contient pas sslmode=require (recommandé pour la production)${NC}"
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}❌ DATABASE_URL ne semble pas être une URL PostgreSQL valide${NC}"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}🔴 Vérification de Redis (optionnel):${NC}"
|
||||
check_env_var "REDIS_URL" false
|
||||
if [ -z "$REDIS_URL" ]; then
|
||||
check_env_var "REDIS_HOST" false
|
||||
check_env_var "REDIS_PORT" false
|
||||
fi
|
||||
check_env_var "REDIS_ENCRYPTION_KEY" false
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}🔧 Vérification des intégrations (optionnelles):${NC}"
|
||||
check_env_var "LEANTIME_API_URL" false
|
||||
check_env_var "LEANTIME_TOKEN" false
|
||||
check_env_var "ROCKET_CHAT_TOKEN" false
|
||||
check_env_var "ROCKET_CHAT_USER_ID" false
|
||||
check_env_var "N8N_API_KEY" false
|
||||
check_env_var "DOLIBARR_API_URL" false
|
||||
check_env_var "DOLIBARR_API_KEY" false
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}📦 Vérification de la configuration Next.js:${NC}"
|
||||
|
||||
# Vérifier que next.config.mjs existe
|
||||
if [ -f "next.config.mjs" ]; then
|
||||
echo -e "${GREEN}✅ next.config.mjs existe${NC}"
|
||||
|
||||
# Vérifier que Turbopack n'est pas forcé en production
|
||||
if grep -q "turbopack:" next.config.mjs && [ "$NODE_ENV" = "production" ]; then
|
||||
echo -e "${YELLOW}⚠️ Turbopack est activé dans next.config.mjs (peut causer des problèmes sur Vercel)${NC}"
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}❌ next.config.mjs n'existe pas${NC}"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
# Vérifier package.json
|
||||
if [ -f "package.json" ]; then
|
||||
echo -e "${GREEN}✅ package.json existe${NC}"
|
||||
|
||||
# Vérifier que le script build existe
|
||||
if grep -q '"build"' package.json; then
|
||||
echo -e "${GREEN}✅ Script 'build' trouvé dans package.json${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ Script 'build' manquant dans package.json${NC}"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}❌ package.json n'existe pas${NC}"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}🧪 Test de build (simulation):${NC}"
|
||||
if command -v npm &> /dev/null; then
|
||||
echo -e "${YELLOW}ℹ️ Pour tester le build réellement, exécutez: npm run build${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ npm n'est pas installé, impossible de tester le build${NC}"
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}📊 Résumé:${NC}"
|
||||
echo -e " ${GREEN}✅ Variables correctes${NC}"
|
||||
if [ $WARNINGS -gt 0 ]; then
|
||||
echo -e " ${YELLOW}⚠️ $WARNINGS avertissement(s)${NC}"
|
||||
fi
|
||||
if [ $ERRORS -gt 0 ]; then
|
||||
echo -e " ${RED}❌ $ERRORS erreur(s)${NC}"
|
||||
echo ""
|
||||
echo -e "${RED}❌ La configuration n'est pas prête pour Vercel${NC}"
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo -e "${GREEN}✅ Configuration prête pour Vercel${NC}"
|
||||
if [ $WARNINGS -gt 0 ]; then
|
||||
echo -e "${YELLOW}⚠️ Vérifiez les avertissements ci-dessus${NC}"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
41
types/electron.d.ts
vendored
41
types/electron.d.ts
vendored
@ -1,41 +0,0 @@
|
||||
interface ElectronWindowControl {
|
||||
minimize: () => Promise<void>;
|
||||
maximize: () => Promise<void>;
|
||||
close: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface ElectronWindowState {
|
||||
onMaximized: (callback: () => void) => void;
|
||||
onUnmaximized: (callback: () => void) => void;
|
||||
removeMaximizedListener: () => void;
|
||||
removeUnmaximizedListener: () => void;
|
||||
}
|
||||
|
||||
interface ElectronAppInfo {
|
||||
isElectron: boolean;
|
||||
version: string;
|
||||
}
|
||||
|
||||
interface ElectronAPI {
|
||||
windowControl: {
|
||||
minimize: () => Promise<void>;
|
||||
maximize: () => Promise<void>;
|
||||
close: () => Promise<void>;
|
||||
};
|
||||
windowState: {
|
||||
onMaximized: (callback: () => void) => void;
|
||||
onUnmaximized: (callback: () => void) => void;
|
||||
removeMaximizedListener: () => void;
|
||||
removeUnmaximizedListener: () => void;
|
||||
};
|
||||
appInfo: {
|
||||
isElectron: boolean;
|
||||
version: string;
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electron?: ElectronAPI;
|
||||
}
|
||||
}
|
||||
47
vercel.json
Normal file
47
vercel.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"buildCommand": "npm run build",
|
||||
"devCommand": "npm run dev",
|
||||
"installCommand": "npm ci",
|
||||
"framework": "nextjs",
|
||||
"regions": ["cdg1"],
|
||||
"env": {
|
||||
"NEXT_TELEMETRY_DISABLED": "1"
|
||||
},
|
||||
"functions": {
|
||||
"app/**/*.ts": {
|
||||
"maxDuration": 30
|
||||
},
|
||||
"app/**/*.tsx": {
|
||||
"maxDuration": 30
|
||||
}
|
||||
},
|
||||
"headers": [
|
||||
{
|
||||
"source": "/(.*)",
|
||||
"headers": [
|
||||
{
|
||||
"key": "X-Content-Type-Options",
|
||||
"value": "nosniff"
|
||||
},
|
||||
{
|
||||
"key": "X-Frame-Options",
|
||||
"value": "SAMEORIGIN"
|
||||
},
|
||||
{
|
||||
"key": "X-XSS-Protection",
|
||||
"value": "1; mode=block"
|
||||
},
|
||||
{
|
||||
"key": "Referrer-Policy",
|
||||
"value": "strict-origin-when-cross-origin"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "/api/:path*",
|
||||
"destination": "/api/:path*"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user