Some checks failed
SourceFinder CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
SourceFinder CI/CD Pipeline / Unit Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Security Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Integration Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Performance Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Code Coverage Report (push) Has been cancelled
SourceFinder CI/CD Pipeline / Build & Deployment Validation (16.x) (push) Has been cancelled
SourceFinder CI/CD Pipeline / Build & Deployment Validation (18.x) (push) Has been cancelled
SourceFinder CI/CD Pipeline / Build & Deployment Validation (20.x) (push) Has been cancelled
SourceFinder CI/CD Pipeline / Regression Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Security Audit (push) Has been cancelled
SourceFinder CI/CD Pipeline / Notify Results (push) Has been cancelled
- Architecture modulaire avec injection de dépendances - Système de scoring intelligent multi-facteurs (spécificité, fraîcheur, qualité, réutilisation) - Moteur anti-injection 4 couches (preprocessing, patterns, sémantique, pénalités) - API REST complète avec validation et rate limiting - Repository JSON avec index mémoire et backup automatique - Provider LLM modulaire pour génération de contenu - Suite de tests complète (Jest) : * Tests unitaires pour sécurité et scoring * Tests d'intégration API end-to-end * Tests de sécurité avec simulation d'attaques * Tests de performance et charge - Pipeline CI/CD avec GitHub Actions - Logging structuré et monitoring - Configuration ESLint et environnement de test 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
177 lines
5.0 KiB
JavaScript
177 lines
5.0 KiB
JavaScript
/**
|
|
* Point d'entrée du serveur SourceFinder
|
|
* Démarrage et arrêt gracieux de l'application
|
|
*/
|
|
require('dotenv').config();
|
|
|
|
const SourceFinderApp = require('./src/app');
|
|
const logger = require('./src/utils/logger');
|
|
|
|
class Server {
|
|
constructor() {
|
|
this.app = null;
|
|
this.server = null;
|
|
this.sourceFinderApp = new SourceFinderApp();
|
|
}
|
|
|
|
/**
|
|
* Démarrer le serveur
|
|
*/
|
|
async start() {
|
|
try {
|
|
// Initialiser l'application avec toutes ses dépendances
|
|
this.app = await this.sourceFinderApp.initialize();
|
|
|
|
// Configuration du port
|
|
const port = parseInt(process.env.PORT) || 3000;
|
|
const host = process.env.HOST || '0.0.0.0';
|
|
|
|
// Démarrer le serveur HTTP
|
|
this.server = this.app.listen(port, host, () => {
|
|
logger.info('🚀 SourceFinder server started', {
|
|
server: {
|
|
port,
|
|
host,
|
|
environment: process.env.NODE_ENV || 'development',
|
|
apiVersion: process.env.API_VERSION || 'v1',
|
|
pid: process.pid
|
|
},
|
|
endpoints: {
|
|
health: `http://${host}:${port}/health`,
|
|
api: `http://${host}:${port}/api/v1`,
|
|
docs: `http://${host}:${port}/api/v1/docs` // TODO: implémenter docs
|
|
}
|
|
});
|
|
|
|
// Log configuration active
|
|
const container = this.sourceFinderApp.getContainer();
|
|
const config = container.get('config');
|
|
|
|
logger.info('📦 Active configuration', {
|
|
components: {
|
|
newsProvider: config.newsProvider.type,
|
|
stockRepository: config.stockRepository.type,
|
|
scoringEngine: config.scoringEngine.type
|
|
},
|
|
features: {
|
|
rateLimiting: true,
|
|
cors: true,
|
|
requestLogging: true,
|
|
errorHandling: true
|
|
}
|
|
});
|
|
});
|
|
|
|
// Configuration serveur
|
|
this.server.keepAliveTimeout = 65000; // Plus que le load balancer
|
|
this.server.headersTimeout = 66000; // Plus que keepAliveTimeout
|
|
|
|
// Gestion des signaux de fermeture
|
|
this.setupGracefulShutdown();
|
|
|
|
} catch (error) {
|
|
logger.error('❌ Failed to start SourceFinder server', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Configurer l'arrêt gracieux
|
|
*/
|
|
setupGracefulShutdown() {
|
|
// Gestion des signaux système
|
|
const signals = ['SIGTERM', 'SIGINT', 'SIGUSR2'];
|
|
|
|
signals.forEach((signal) => {
|
|
process.on(signal, () => {
|
|
logger.info(`📡 Received ${signal}, starting graceful shutdown...`);
|
|
this.shutdown();
|
|
});
|
|
});
|
|
|
|
// Gestion des exceptions non catchées
|
|
process.on('uncaughtException', (error) => {
|
|
logger.error('💥 Uncaught Exception', error);
|
|
this.shutdown(1);
|
|
});
|
|
|
|
process.on('unhandledRejection', (reason, promise) => {
|
|
logger.error('💥 Unhandled Rejection', new Error(reason), { promise });
|
|
this.shutdown(1);
|
|
});
|
|
|
|
// Gestion mémoire (warning si > 80% utilisée)
|
|
if (process.env.NODE_ENV === 'production') {
|
|
setInterval(() => {
|
|
const memUsage = process.memoryUsage();
|
|
const memUsedMB = Math.round(memUsage.heapUsed / 1024 / 1024);
|
|
const memTotalMB = Math.round(memUsage.heapTotal / 1024 / 1024);
|
|
const memPercent = (memUsage.heapUsed / memUsage.heapTotal) * 100;
|
|
|
|
if (memPercent > 80) {
|
|
logger.warn(`⚠️ High memory usage: ${memUsedMB}MB / ${memTotalMB}MB (${memPercent.toFixed(1)}%)`, {
|
|
memory: {
|
|
used: memUsedMB,
|
|
total: memTotalMB,
|
|
percent: memPercent,
|
|
rss: Math.round(memUsage.rss / 1024 / 1024)
|
|
}
|
|
});
|
|
}
|
|
}, 30000); // Check toutes les 30s
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Arrêter le serveur gracieusement
|
|
*/
|
|
async shutdown(exitCode = 0) {
|
|
logger.info('🔄 Starting graceful shutdown...');
|
|
|
|
try {
|
|
// Arrêter d'accepter nouvelles connexions
|
|
if (this.server) {
|
|
await new Promise((resolve, reject) => {
|
|
this.server.close((err) => {
|
|
if (err) {
|
|
logger.error('❌ Error closing HTTP server', err);
|
|
reject(err);
|
|
} else {
|
|
logger.info('✅ HTTP server closed');
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Fermer l'application et ses dépendances
|
|
if (this.sourceFinderApp) {
|
|
await this.sourceFinderApp.shutdown();
|
|
}
|
|
|
|
// Forcer fermeture après timeout
|
|
setTimeout(() => {
|
|
logger.warn('⚠️ Forceful shutdown after timeout');
|
|
process.exit(1);
|
|
}, 30000); // 30 secondes max
|
|
|
|
logger.info('✅ Graceful shutdown completed');
|
|
process.exit(exitCode);
|
|
|
|
} catch (error) {
|
|
logger.error('❌ Error during shutdown', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Démarrer le serveur si ce fichier est exécuté directement
|
|
if (require.main === module) {
|
|
const server = new Server();
|
|
server.start().catch((error) => {
|
|
console.error('Failed to start server:', error);
|
|
process.exit(1);
|
|
});
|
|
}
|
|
|
|
module.exports = Server; |