Configuration et environnement
Vue d'ensemble
MathQuest utilise plusieurs fichiers de configuration et variables d'environnement pour gérer différents aspects de l'application. Cette section détaille toutes les options de configuration disponibles.
Variables d'environnement
Variables obligatoires
Base de données
# URL de connexion PostgreSQL
DATABASE_URL="postgresql://username:password@localhost:5432/mathquest"
Cache Redis
# URL de connexion Redis
REDIS_URL="redis://localhost:6379"
Authentification JWT
# Clé secrète pour les tokens JWT (minimum 32 caractères)
JWT_SECRET="your_super_secure_jwt_secret_key_here"
Variables recommandées
Configuration serveur
# Port d'écoute du serveur (défaut: 3007)
PORT=3007
# Niveau de log (DEBUG, INFO, WARN, ERROR)
LOG_LEVEL=INFO
# URL du frontend pour CORS (production)
FRONTEND_URL="https://yourdomain.com"
# Domaine de l'application
APP_DOMAIN="yourdomain.com"
APP_NAME="MathQuest"
Sécurité
# Configuration email (Brevo/Sendinblue)
BREVO_API_KEY="your_brevo_api_key"
BREVO_SENDER_EMAIL="noreply@yourdomain.com"
BREVO_SENDER_NAME="MathQuest"
Email et vérification
# Durée d'expiration des tokens
EMAIL_VERIFICATION_TOKEN_EXPIRY="24h"
PASSWORD_RESET_TOKEN_EXPIRY="1h"
Génération IA / LLM
# Chiffrement des clés API LLM stockées en base (AES-256-GCM, 64 hex chars)
LLM_KEY_MASTER_KEY="<openssl rand -hex 32>"
# Protection burst HTTP sur POST /api/v1/ai/generate-questions (par user/heure)
AI_GENERATION_RATE_LIMIT_MAX_REQUESTS_PER_HOUR=60
# Fallbacks globaux optionnels quand le tenant n'a pas défini ses propres limites
AI_DEFAULT_MAX_TOKENS_PER_REQUEST=4096
AI_DEFAULT_MAX_PROMPTS_PER_USER_PER_DAY=100
# Fallbacks globaux optionnels — plafond tenant-wide (clés TENANT/GLOBAL partagées, todo 280)
AI_DEFAULT_MAX_PROMPTS_PER_TENANT_PER_DAY=800
AI_DEFAULT_MAX_PROMPTS_PER_TENANT_PER_HOUR=100
Notes :
LLM_KEY_MASTER_KEYest obligatoire dès qu'un enseignant doit enregistrer une clé API personnelle.AI_GENERATION_RATE_LIMIT_MAX_REQUESTS_PER_HOURprotège l'infrastructure ; elle s'applique même quand l'utilisateur utilise sa propre clé API.- Les variables
AI_DEFAULT_*sont optionnelles : elles ne s'appliquent que si le champ correspondant est absent desettings.aiGenerationpour le tenant (maxTokensPerRequest,maxPromptsPerUserPerDay,maxPromptsPerTenantPerDay,maxPromptsPerTenantPerHour). - Les deux
AI_DEFAULT_MAX_PROMPTS_PER_TENANT_*ne s'appliquent qu'aux clés partagéesTENANT/GLOBAL(jamais aux clésUSER/BYOK) et protègent le quota du compte fournisseur lui-même, pas seulement celui d'un utilisateur — voir Génération IA / LLM. - L'activation du feature ne passe pas uniquement par l'env. Il faut aussi activer
settings.aiGenerationdansorganizations.settingspour le tenant cible. Voir Génération IA / LLM.
Protection anti-abus et limites de création
# Limites de création — templates enseignant (créés manuellement par les profs)
DEFAULT_TEACHER_GAME_TEMPLATE_LIMIT=20
# Limites de création — templates auto (générés par les élèves depuis le portail pratique)
DEFAULT_AUTO_GAME_TEMPLATE_LIMIT=20
# Limites d'instances par template — templates enseignant
DEFAULT_TEACHER_GAME_INSTANCES_PER_TEMPLATE=20
# Limites d'instances par template — templates auto
DEFAULT_AUTO_GAME_INSTANCES_PER_TEMPLATE=20
# Limite globale d'instances par enseignant
DEFAULT_GAME_INSTANCE_LIMIT=50
# Limites invité (création de sessions pratique)
GUEST_PRACTICE_IP_LIMIT_PER_MINUTE=360
GUEST_PRACTICE_IP_LIMIT_PER_HOUR=1200
GUEST_PRACTICE_COOKIE_LIMIT_PER_HOUR=120
# Rate limiting REST API
ENABLE_API_RATE_LIMIT=true
ENABLE_API_RATE_LIMIT_IN_TEST=false
API_RATE_LIMIT_WINDOW_MS=60000
API_RATE_LIMIT_MAX_REQUESTS=300
API_RATE_LIMIT_KEY_PREFIX=limits:api:requests
# Cleanup quotidien des artefacts invités abandonnés
ENABLE_GUEST_ARTIFACT_CLEANUP=true
GUEST_ARTIFACT_CLEANUP_HOUR=3
GUEST_ARTIFACT_CLEANUP_MINUTE=30
GUEST_ARTIFACT_CLEANUP_STALE_HOURS=72
# Import externe MathALÉA
EXTERNAL_DRAFT_ALLOWED_ORIGINS=https://coopmaths.fr,https://www.coopmaths.fr
# Cleanup quotidien des imports de pratique MathALÉA expirés (TTL 24h)
ENABLE_MATHALEA_SESSION_CLEANUP=true
EPHEMERAL_PRACTICE_CLEANUP_HOUR=4
EPHEMERAL_PRACTICE_CLEANUP_MINUTE=0
Notes:
- Les limites de templates sont différenciées :
DEFAULT_TEACHER_GAME_TEMPLATE_LIMITs'applique aux templates créés manuellement par les enseignants ;DEFAULT_AUTO_GAME_TEMPLATE_LIMITs'applique aux templates auto-générés par le portail élève. Ces defaults peuvent être surchargés par profil viaTeacherProfile.teacherGameTemplateLimitetTeacherProfile.autoGameTemplateLimit. - Les limites d'instances par template suivent la même logique :
DEFAULT_TEACHER_GAME_INSTANCES_PER_TEMPLATE/DEFAULT_AUTO_GAME_INSTANCES_PER_TEMPLATE, surchargeables viaTeacherProfile.teacherGameInstancesPerTemplateLimit/TeacherProfile.autoGameInstancesPerTemplateLimit. - Pour les templates auto, le système tente d'abord un nettoyage silencieux (suppression des templates anciens sans instance live) avant de refuser la création avec
AUTO_TEMPLATE_LIMIT_EXCEEDED. Pour les templates enseignant, le refus est immédiat (GAME_TEMPLATE_LIMIT_EXCEEDED). - Le cleanup invité est volontairement conservateur: il cible les artefacts abandonnés et ne doit pas supprimer l'historique des parties terminées.
EXTERNAL_DRAFT_ALLOWED_ORIGINSdéfinit les origines autorisées à appelerPOST /api/v1/external-drafts.- Le cleanup des imports de pratique MathALÉA tourne une fois par jour (comme le cleanup invité), à l'heure fixée par
EPHEMERAL_PRACTICE_CLEANUP_HOUR/EPHEMERAL_PRACTICE_CLEANUP_MINUTE— il ne cible que lesEphemeralPracticeImportdont le TTL de 24h (expiresAt) est dépassé, donc une granularité quotidienne suffit. LLM_KEY_MASTER_KEYchiffre les clés API LLM stockées en base ; sans elle,POST /api/v1/llm-api-keyséchoue.AI_DEFAULT_MAX_TOKENS_PER_REQUESTetAI_DEFAULT_MAX_PROMPTS_PER_USER_PER_DAYfournissent des limites globales de secours quand le tenant n'en fixe pas danssettings.aiGeneration.
Fichiers de configuration
PM2 Ecosystem Configuration
Le fichier ecosystem.config.js configure le déploiement avec PM2 :
module.exports = {
apps: [
{
name: "mathquest-backend",
script: "npm",
cwd: "./backend",
args: "run start",
env: {
NODE_ENV: "production",
REDIS_URL: "redis://localhost:6379",
DATABASE_URL: "postgresql://...",
JWT_SECRET: "...",
FRONTEND_URL: "https://yourdomain.com"
},
log_file: "./logs/pm2-backend.log",
out_file: "./logs/pm2-backend-out.log",
error_file: "./logs/pm2-backend-error.log",
log_date_format: "YYYY-MM-DD HH:mm:ss Z",
merge_logs: true,
max_memory_restart: "400M"
},
{
name: "mathquest-frontend",
script: "npm",
cwd: "./frontend",
args: "run start:minimal",
env: {
NODE_ENV: "production",
NEXT_TELEMETRY_DISABLED: "1"
},
log_file: "./logs/pm2-frontend.log",
out_file: "./logs/pm2-frontend-out.log",
error_file: "./logs/pm2-frontend-error.log",
log_date_format: "YYYY-MM-DD HH:mm:ss Z",
merge_logs: true,
max_memory_restart: "300M"
}
]
}
Configuration Next.js
Le fichier next.config.ts configure l'application frontend :
/** @type {import('next').NextConfig} */
const nextConfig = {
// Configuration de base
reactStrictMode: true,
// Configuration des images
images: {
domains: ['localhost'],
unoptimized: process.env.NODE_ENV === 'development'
},
// Headers de sécurité
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'X-Frame-Options',
value: 'DENY'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
}
]
}
];
},
// Variables d'environnement exposées au frontend
env: {
NEXT_PUBLIC_APP_NAME: process.env.APP_NAME,
NEXT_PUBLIC_APP_DOMAIN: process.env.APP_DOMAIN
}
};
module.exports = nextConfig;
Configuration ESLint
Le fichier eslint.config.mjs définit les règles de linting :
import { defineConfig } from 'eslint-define-config';
export default defineConfig({
root: true,
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended'
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 12,
sourceType: 'module'
},
plugins: ['react', '@typescript-eslint'],
rules: {
// Règles personnalisées
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn'
},
settings: {
react: {
version: 'detect'
}
}
});
Configuration de la base de données
Prisma Schema
Le fichier schema.prisma définit le modèle de données :
generator client {
provider = "prisma-client-js"
output = "../src/db/generated/client"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// Modèles de données...
Configuration de connexion
La connexion Prisma est configurée dans src/db/prisma.ts :
import { PrismaClient } from '@/db/generated/client';
// Singleton pattern pour éviter les connexions multiples
declare global {
var prisma: PrismaClient | undefined;
}
export const prisma = global.prisma || new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
});
if (process.env.NODE_ENV !== 'production') {
global.prisma = prisma;
}
Configuration Redis
Connexion Redis
Le fichier src/config/redis.ts configure la connexion Redis :
import Redis from 'ioredis';
import { logger } from '../utils/logger';
const redisUrl = process.env.REDIS_URL;
if (!redisUrl) {
logger.error('REDIS_URL is not defined in environment variables.');
throw new Error('REDIS_URL is not defined.');
}
const redisClient = new Redis(redisUrl, {
maxRetriesPerRequest: null,
enableReadyCheck: false,
retryDelayOnFailover: 100,
maxRetriesPerRequest: 3,
lazyConnect: true
});
redisClient.on('connect', () => {
logger.info('Successfully connected to Redis.');
});
redisClient.on('error', (err) => {
logger.error('Redis connection error:', err);
});
export { redisClient };
Clés Redis utilisées
MathQuest utilise plusieurs patterns de clés Redis :
# États de partie
mathquest:game:{accessCode}
# Timers de questions
mathquest:timer:{accessCode}:{questionUid}
# Sessions utilisateur
mathquest:session:{userId}
# Cache des questions
mathquest:question:{questionUid}
# Leaderboards
mathquest:leaderboard:{gameId}
Configuration CORS
Middleware CORS
Le serveur configure CORS dans src/server.ts :
app.use(cors({
origin: process.env.NODE_ENV === 'production'
? process.env.FRONTEND_URL || 'https://mathquest.example.com'
: ['http://localhost:3000', 'http://localhost:3001', 'http://localhost:3008'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
credentials: true,
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
}));
Configuration des logs
Logger personnalisé
Le système de logging utilise winston avec configuration dans src/utils/logger.ts :
import winston from 'winston';
const logLevel = process.env.LOG_LEVEL || 'info';
const logger = winston.createLogger({
level: logLevel,
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'mathquest' },
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}),
new winston.transports.File({
filename: 'logs/error.log',
level: 'error'
}),
new winston.transports.File({
filename: 'logs/combined.log'
})
]
});
export default logger;
Configuration des tests
Jest Configuration
Le fichier jest.config.js configure les tests :
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src', '<rootDir>/tests'],
testMatch: [
'**/__tests__/**/*.+(ts|tsx|js)',
'**/?(*.)+(spec|test).+(ts|tsx|js)'
],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
collectCoverage: false,
clearMocks: true,
globalSetup: '<rootDir>/tests/support/globalSetup.ts',
globalTeardown: '<rootDir>/tests/support/globalTeardown.ts',
setupFiles: ['<rootDir>/tests/setupTestEnv.js'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'^@shared/(.*)$': '<rootDir>/../shared/$1'
},
maxConcurrency: 1,
maxWorkers: 1,
forceExit: true,
testTimeout: 10000
};
Variables d'environnement par environnement
Développement
# .env
NODE_ENV=development
DATABASE_URL="postgresql://postgres:password@localhost:5432/mathquest_dev"
REDIS_URL="redis://localhost:6379"
JWT_SECRET="dev_secret_key_not_for_production"
LOG_LEVEL=DEBUG
PORT=3007
FRONTEND_URL="http://localhost:3000"
Production
# .env
NODE_ENV=production
DATABASE_URL="postgresql://user:password@prod-db-host:5432/mathquest_prod"
REDIS_URL="redis://prod-redis-host:6379"
JWT_SECRET="production_secret_key_very_secure"
LOG_LEVEL=WARN
PORT=3007
FRONTEND_URL="https://mathquest.example.com"
APP_DOMAIN="mathquest.example.com"
BREVO_API_KEY="prod_brevo_key"
Test
# .env.test
NODE_ENV=test
DATABASE_URL="postgresql://postgres:password@localhost:5432/mathquest_test"
REDIS_URL="redis://localhost:6379"
JWT_SECRET="test_secret_key"
LOG_LEVEL=ERROR
PORT=3008
Scripts de déploiement
Scripts disponibles
# Construction complète
./build-all.sh
# Construction pour VPS
./build-vps.sh
# Démarrage avec PM2
npm run pm2:start
# Redémarrage des services
npm run pm2:restart
# Arrêt des services
npm run pm2:stop
# Logs PM2
npm run pm2:logs
Script de construction complet
Le fichier build-all.sh :
#!/bin/bash
# Construction du backend
echo "Building backend..."
cd backend
npm install
npm run build
cd ..
# Construction du frontend
echo "Building frontend..."
cd frontend
npm install
npm run build
cd ..
# Construction de la documentation
echo "Building docs..."
cd vuepress
npm install
npm run build
cd ..
echo "Build completed!"
Monitoring et métriques
Endpoints de santé
# Santé générale
GET /health
# Métriques mémoire
GET /health/memory
# Métriques détaillées (authentification requise)
GET /api/v1/health/metrics
Métriques collectées
- Utilisation mémoire (heap, RSS, external)
- Uptime du processus
- Nombre de connexions Socket.IO actives
- Latence base de données
- Taux d'erreur des requêtes
- Métriques Redis (connexions, commandes/seconde)
Sécurité
Headers de sécurité
Configuration des headers dans Next.js :
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=()' }
]
}
];
}
Validation des entrées
Toutes les entrées utilisateur sont validées avec Zod :
import { z } from 'zod';
export const UserInputSchema = z.object({
email: z.string().email('Email invalide'),
password: z.string().min(8, 'Mot de passe trop court'),
username: z.string().min(3, 'Nom d\'utilisateur trop court')
});
Migration et mise à jour
Variables d'environnement critiques
Lors des mises à jour, vérifier ces variables :
DATABASE_URL- Peut changer avec la migration DBREDIS_URL- Peut changer avec le scalingJWT_SECRET- Ne jamais changer en productionBREVO_API_KEY- Peut nécessiter rotation
Commandes de migration
# Migration base de données
cd backend
npx prisma migrate deploy
# Génération du client Prisma
npx prisma generate
# Redémarrage des services
npm run pm2:restart