mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-19 05:09:54 +02:00
Création complète de l'application NoteFlow - Notes & Todo List
Application web Dockerisée moderne avec: - Backend Node.js + Express + SQLite - Frontend HTML/CSS/JavaScript vanilla - Authentification JWT + bcrypt - Gestion des notes avec images et todos intégrés - Sidebar todos globaux avec filtres - Interface utilisateur fluide avec animations CSS - Administration des utilisateurs - Sécurité production-ready (Helmet, rate limiting, validation) - Configuration Docker complète - Documentation README détaillée Stack technique: - Node.js 20 Alpine - SQLite3 avec indexes optimisés - Multer pour upload d'images - Winston pour logging - Express-validator pour validation - Architecture modulaire (routes, middleware, config) Fonctionnalités: - Notes: CRUD complet, recherche, images, todos intégrés - Todos globaux: création, toggle, filtres, suppression - Auth: JWT 24h, bcrypt 12 rounds - Admin: gestion utilisateurs complète - UI: masonry grid, modal animations, responsive Configuration Docker: - Healthcheck intégré - Volumes persistants pour data et uploads - Network nginx_default pour reverse proxy - Variables d'environnement configurables Prêt pour déploiement avec: docker-compose up -d
This commit is contained in:
22 files changed
+3414
-589
No files matched your search
@@ -1,76 +1,158 @@
|
||||
// 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 winston = require('winston');
|
||||
const fs = require('fs');
|
||||
const { db } = require('./config/database');
|
||||
|
||||
// Ensure uploads directory exists
|
||||
const uploadsDir = path.join(__dirname, 'public/uploads');
|
||||
if (!fs.existsSync(uploadsDir)) {
|
||||
fs.mkdirSync(uploadsDir, { recursive: true });
|
||||
}
|
||||
const logger = require('./config/logger');
|
||||
const { initDatabase } = require('./config/database');
|
||||
|
||||
// Configure logger
|
||||
const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.json()
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.Console()
|
||||
]
|
||||
// Créer l'application Express
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// Créer les dossiers nécessaires
|
||||
const dataDir = path.join(__dirname, 'data');
|
||||
const uploadsDir = path.join(__dirname, 'public', 'uploads');
|
||||
|
||||
[dataDir, uploadsDir].forEach(dir => {
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
logger.info(`Dossier créé: ${dir}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Create Express app
|
||||
const app = express();
|
||||
|
||||
// Middleware
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
|
||||
fontSrc: ["'self'", "https://fonts.gstatic.com"],
|
||||
imgSrc: ["'self'", "data:"],
|
||||
connectSrc: ["'self'"]
|
||||
}
|
||||
}
|
||||
}));
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.static('public'));
|
||||
|
||||
// Set JWT_SECRET environment variable if not set
|
||||
if (!process.env.JWT_SECRET) {
|
||||
process.env.JWT_SECRET = 'default_development_secret';
|
||||
logger.warn('JWT_SECRET not set, using default (insecure) value');
|
||||
// Créer un fichier .gitkeep dans uploads pour le tracking git
|
||||
const gitkeepPath = path.join(uploadsDir, '.gitkeep');
|
||||
if (!fs.existsSync(gitkeepPath)) {
|
||||
fs.writeFileSync(gitkeepPath, '');
|
||||
}
|
||||
|
||||
// Routes
|
||||
// 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'));
|
||||
|
||||
// Serve static files
|
||||
app.use('/uploads', express.static(path.join(__dirname, 'public/uploads')));
|
||||
// Route de recherche
|
||||
app.get('/api/search', require('./routes/notes.routes').searchNotes);
|
||||
|
||||
// SPA fallback
|
||||
// SPA fallback - Servir index.html pour toutes les autres routes
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public/index.html'));
|
||||
// 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'));
|
||||
});
|
||||
|
||||
// Error handling
|
||||
// Gestionnaire d'erreurs global
|
||||
app.use((err, req, res, next) => {
|
||||
logger.error('Unhandled error:', err);
|
||||
res.status(500).json({ message: 'Internal server error' });
|
||||
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
|
||||
});
|
||||
});
|
||||
|
||||
// Start server
|
||||
const PORT = process.env.PORT || 2222;
|
||||
app.listen(PORT, () => {
|
||||
logger.info(`Server running on port ${PORT}`);
|
||||
});
|
||||
// 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 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();
|
||||
Reference in new issue
Block a user