mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-11 17:29:37 +02:00
Problèmes résolus: ✅ Flux RSS ne s'affichaient pas → LEFT JOIN + COALESCE ✅ Pas de mise à jour automatique → Scheduler toutes les 5min ✅ Application lente → Cache serveur + client Scheduler RSS (services/rss-scheduler.js): - Mise à jour automatique des flux toutes les 5 minutes - Première exécution 5 secondes après démarrage - Gestion parallèle de tous les flux activés - Logs détaillés pour debugging - Protection contre les exécutions multiples simultanées - Limite de 20 articles par flux pour performance Routes RSS optimisées: - Cache serveur 30 secondes pour /api/rss/articles - Invalidation cache après fetch manuel - LEFT JOIN au lieu de INNER JOIN (flux sans articles) - COALESCE pour titre fallback - Logs améliorés avec compteurs Frontend vanilla JS: - Auto-refresh RSS toutes les 30 secondes - Cache settings client 1 minute - Synchronisation avec scheduler serveur - console.debug au lieu de console.error pour auto-refresh Frontend React: - Auto-refresh RSS toutes les 30 secondes - loadRssData() séparée pour refresh indépendant - useEffect cleanup proper (clearInterval) - État lastUpdate pour tracking Server.js: - Démarrage scheduler au startup - Log confirmation démarrage Performances: - Moins de requêtes DB grâce au cache - Refresh automatique sans action utilisateur - Articles toujours à jour (max 30s de délai) - Support de beaucoup de flux RSS sans ralentissement
164 lines
5.3 KiB
JavaScript
164 lines
5.3 KiB
JavaScript
// Serveur Express principal pour l'application NoteFlow
|
|
const express = require('express');
|
|
const cors = require('cors');
|
|
const helmet = require('helmet');
|
|
const rateLimit = require('express-rate-limit');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const logger = require('./config/logger');
|
|
const { initDatabase } = require('./config/database');
|
|
|
|
// Créer l'application Express
|
|
const app = express();
|
|
const PORT = process.env.PORT || 2222;
|
|
|
|
// Créer les dossiers nécessaires (silencieusement, ils peuvent déjà exister via les volumes Docker)
|
|
const dataDir = path.join(__dirname, 'data');
|
|
const uploadsDir = path.join(__dirname, 'public', 'uploads');
|
|
|
|
[dataDir, uploadsDir].forEach(dir => {
|
|
try {
|
|
if (!fs.existsSync(dir)) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
logger.info(`Dossier créé: ${dir}`);
|
|
}
|
|
} catch (error) {
|
|
// Ignorer les erreurs si les dossiers existent déjà ou sont montés via Docker
|
|
logger.debug(`Dossier déjà existant: ${dir}`);
|
|
}
|
|
});
|
|
|
|
// Configuration de la sécurité avec Helmet
|
|
app.use(helmet({
|
|
contentSecurityPolicy: {
|
|
directives: {
|
|
defaultSrc: ["'self'"],
|
|
scriptSrc: ["'self'", "'unsafe-inline'"],
|
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
imgSrc: ["'self'", "data:", "blob:"],
|
|
fontSrc: ["'self'"],
|
|
connectSrc: ["'self'"]
|
|
}
|
|
}
|
|
}));
|
|
|
|
// CORS - À ajuster selon vos besoins
|
|
app.use(cors({
|
|
origin: process.env.CORS_ORIGIN || '*',
|
|
credentials: true
|
|
}));
|
|
|
|
// Body parsers
|
|
app.use(express.json({ limit: '10mb' }));
|
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
|
|
// Rate limiting sur les endpoints d'authentification
|
|
const authLimiter = rateLimit({
|
|
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000, // 15 minutes par défaut
|
|
max: parseInt(process.env.RATE_LIMIT_MAX) || 100, // 100 requêtes par fenêtre
|
|
message: { error: 'Trop de tentatives, veuillez réessayer plus tard.' },
|
|
standardHeaders: true,
|
|
legacyHeaders: false
|
|
});
|
|
|
|
app.use('/api/auth', authLimiter);
|
|
|
|
// Servir les fichiers statiques
|
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
app.use('/uploads', express.static(uploadsDir));
|
|
|
|
// Healthcheck endpoint pour Docker
|
|
app.get('/health', (req, res) => {
|
|
res.status(200).json({
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: process.uptime()
|
|
});
|
|
});
|
|
|
|
// Routes API
|
|
app.use('/api/auth', require('./routes/auth.routes'));
|
|
app.use('/api/users', require('./routes/users.routes'));
|
|
app.use('/api/notes', require('./routes/notes.routes'));
|
|
app.use('/api/todos', require('./routes/todos.routes'));
|
|
app.use('/api/settings', require('./routes/settings.routes'));
|
|
app.use('/api/rss', require('./routes/rss.routes'));
|
|
|
|
// Route de recherche
|
|
app.get('/api/search', require('./routes/notes.routes').searchNotes);
|
|
|
|
// SPA fallback - Servir index.html pour toutes les autres routes
|
|
app.get('*', (req, res) => {
|
|
// Ne pas servir index.html pour les requêtes d'API
|
|
if (req.path.startsWith('/api/')) {
|
|
return res.status(404).json({ error: 'Endpoint non trouvé' });
|
|
}
|
|
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
|
});
|
|
|
|
// Gestionnaire d'erreurs global
|
|
app.use((err, req, res, next) => {
|
|
logger.error('Erreur non gérée:', {
|
|
error: err.message,
|
|
stack: err.stack,
|
|
path: req.path,
|
|
method: req.method
|
|
});
|
|
|
|
// Ne pas exposer les détails de l'erreur en production
|
|
const errorMessage = process.env.NODE_ENV === 'production'
|
|
? 'Une erreur est survenue'
|
|
: err.message;
|
|
|
|
res.status(err.status || 500).json({
|
|
error: errorMessage
|
|
});
|
|
});
|
|
|
|
// Initialiser la base de données et démarrer le serveur
|
|
async function startServer() {
|
|
try {
|
|
// Initialiser la base de données
|
|
await initDatabase();
|
|
logger.info('✓ Base de données initialisée avec succès');
|
|
|
|
// Démarrer le scheduler RSS
|
|
const rssScheduler = require('./services/rss-scheduler');
|
|
rssScheduler.startScheduler();
|
|
logger.info('✓ Scheduler RSS démarré');
|
|
|
|
// Démarrer le serveur
|
|
app.listen(PORT, '0.0.0.0', () => {
|
|
logger.info('═════════════════════════════════════════════');
|
|
logger.info(`✓ Serveur NoteFlow démarré sur le port ${PORT}`);
|
|
logger.info(`✓ Environnement: ${process.env.NODE_ENV || 'development'}`);
|
|
logger.info(`✓ URL: http://localhost:${PORT}`);
|
|
logger.info('═════════════════════════════════════════════');
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
logger.info('Credentials par défaut:');
|
|
logger.info(' Username: admin');
|
|
logger.info(' Password: admin');
|
|
logger.info('═════════════════════════════════════════════');
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error('Erreur lors du démarrage du serveur:', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Gestion propre de l'arrêt
|
|
process.on('SIGTERM', () => {
|
|
logger.info('Signal SIGTERM reçu, arrêt du serveur...');
|
|
process.exit(0);
|
|
});
|
|
|
|
process.on('SIGINT', () => {
|
|
logger.info('Signal SIGINT reçu, arrêt du serveur...');
|
|
process.exit(0);
|
|
});
|
|
|
|
// Démarrer l'application
|
|
startServer(); |