diff --git a/middleware/auth.js b/middleware/auth.js index 7923741..9503771 100644 --- a/middleware/auth.js +++ b/middleware/auth.js @@ -1,5 +1,17 @@ const jwt = require('jsonwebtoken'); -const { logger } = require('../config/database'); +const winston = require('winston'); + +// Configure logger +const logger = winston.createLogger({ + level: 'info', + format: winston.format.combine( + winston.format.timestamp(), + winston.format.json() + ), + transports: [ + new winston.transports.Console() + ] +}); const authMiddleware = (req, res, next) => { try { diff --git a/public/index.html b/public/index.html index 010983d..bd43842 100644 --- a/public/index.html +++ b/public/index.html @@ -5,8 +5,7 @@ Notes & Todos - - + diff --git a/public/js/app.js b/public/js/app.js index f765897..f1fa2f4 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -10,7 +10,7 @@ class App { // Load initial data if authenticated if (this.auth.token) { this.notes.loadNotes(); - if (this.auth.user.is_admin) { + if (this.auth.user && this.auth.user.is_admin) { this.admin.loadUsers(); } } @@ -18,4 +18,9 @@ class App { } // Initialize main app -window.app = new App(); \ No newline at end of file +window.app = new App(); + +// Initialize app when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + window.app.init(); +}); \ No newline at end of file diff --git a/public/js/auth.js b/public/js/auth.js index c5b2733..a082787 100644 --- a/public/js/auth.js +++ b/public/js/auth.js @@ -1,7 +1,7 @@ class AuthService { constructor() { this.token = localStorage.getItem('token'); - this.user = JSON.parse(localStorage.getItem('user')); + this.user = JSON.parse(localStorage.getItem('user') || 'null'); this.loginForm = document.getElementById('login-form'); this.logoutBtn = document.getElementById('logout-btn'); this.userInfo = document.getElementById('user-info'); @@ -11,8 +11,12 @@ class AuthService { } setupEventListeners() { - this.loginForm.addEventListener('submit', (e) => this.handleLogin(e)); - this.logoutBtn.addEventListener('click', () => this.handleLogout()); + if (this.loginForm) { + this.loginForm.addEventListener('submit', (e) => this.handleLogin(e)); + } + if (this.logoutBtn) { + this.logoutBtn.addEventListener('click', () => this.handleLogout()); + } } async handleLogin(e) { @@ -66,11 +70,14 @@ class AuthService { // Show admin panel button if user is admin if (this.user.is_admin) { - const adminBtn = document.createElement('button'); - adminBtn.className = 'ml-4 text-sm text-gray-600 hover:text-gray-900'; - adminBtn.textContent = 'Admin'; - adminBtn.onclick = () => adminPanel.classList.remove('hidden'); - this.userInfo.parentNode.insertBefore(adminBtn, this.logoutBtn); + const adminBtnExists = document.querySelector('.admin-btn'); + if (!adminBtnExists) { + const adminBtn = document.createElement('button'); + adminBtn.className = 'admin-btn ml-4 text-sm text-gray-600 hover:text-gray-900'; + adminBtn.textContent = 'Admin'; + adminBtn.onclick = () => adminPanel.classList.remove('hidden'); + this.userInfo.parentNode.insertBefore(adminBtn, this.logoutBtn); + } } } else { authContainer.classList.remove('hidden'); diff --git a/public/js/notes.js b/public/js/notes.js index 60e396e..87b4ec6 100644 --- a/public/js/notes.js +++ b/public/js/notes.js @@ -18,27 +18,52 @@ class NotesService { this.imageUpload = document.getElementById('image-upload'); this.setupEventListeners(); - this.loadNotes(); } setupEventListeners() { - this.newNoteBtn.addEventListener('click', () => this.createNewNote()); - this.archiveNoteBtn.addEventListener('click', () => this.toggleArchiveNote()); - this.deleteNoteBtn.addEventListener('click', () => this.deleteNote()); - this.addTodoBtn.addEventListener('click', () => this.addTodo()); - this.imageUpload.addEventListener('change', (e) => this.handleImageUpload(e)); - this.searchInput.addEventListener('input', (e) => this.handleSearch(e)); + if (this.newNoteBtn) { + this.newNoteBtn.addEventListener('click', () => this.createNewNote()); + } + if (this.archiveNoteBtn) { + this.archiveNoteBtn.addEventListener('click', () => this.toggleArchiveNote()); + } + if (this.deleteNoteBtn) { + this.deleteNoteBtn.addEventListener('click', () => this.deleteNote()); + } + if (this.addTodoBtn) { + this.addTodoBtn.addEventListener('click', () => this.addTodo()); + } + if (this.imageUpload) { + this.imageUpload.addEventListener('change', (e) => this.handleImageUpload(e)); + } + if (this.searchInput) { + this.searchInput.addEventListener('input', (e) => this.handleSearch(e)); + } // Auto-save on content changes - this.noteTitle.addEventListener('input', () => this.autoSave()); - this.noteContent.addEventListener('input', () => this.autoSave()); + if (this.noteTitle) { + this.noteTitle.addEventListener('input', () => this.autoSave()); + } + if (this.noteContent) { + this.noteContent.addEventListener('input', () => this.autoSave()); + } } async loadNotes() { + if (!window.auth.token) { + console.log('Not authenticated, skipping notes loading'); + return; + } + try { const response = await fetch('/api/notes', { headers: window.auth.getHeaders() }); + + if (!response.ok) { + throw new Error(`Failed to load notes: ${response.status}`); + } + this.notes = await response.json(); this.renderNotesList(); } catch (error) { @@ -47,12 +72,19 @@ class NotesService { } renderNotesList(searchTerm = '') { + if (!this.notesList) return; + this.notesList.innerHTML = ''; + if (!this.notes || !this.notes.length) { + this.notesList.innerHTML = '
Aucune note
'; + return; + } + const filteredNotes = searchTerm ? this.notes.filter(note => note.title.toLowerCase().includes(searchTerm.toLowerCase()) || - note.content.toLowerCase().includes(searchTerm.toLowerCase()) + (note.content && note.content.toLowerCase().includes(searchTerm.toLowerCase())) ) : this.notes; @@ -70,14 +102,16 @@ class NotesService { selectNote(note) { this.currentNote = note; - this.noteTitle.value = note.title; - this.noteContent.innerHTML = note.content; + if (this.noteTitle) this.noteTitle.value = note.title || ''; + if (this.noteContent) this.noteContent.innerHTML = note.content || ''; this.renderTodos(); this.renderImages(); this.renderNotesList(); // Update active state } renderTodos() { + if (!this.todosList || !this.currentNote || !this.currentNote.todos) return; + this.todosList.innerHTML = ''; this.currentNote.todos.forEach((todo, index) => { const todoElement = document.createElement('div'); @@ -101,8 +135,10 @@ class NotesService { } renderImages() { + if (!this.imagesList || !this.currentNote || !this.currentNote.images) return; + this.imagesList.innerHTML = ''; - this.currentNote.images.forEach((image, index) => { + this.currentNote.images.forEach((image) => { const imageElement = document.createElement('div'); imageElement.className = 'image-thumbnail'; imageElement.innerHTML = ` @@ -199,10 +235,10 @@ class NotesService { this.notes.splice(index, 1); this.currentNote = null; this.renderNotesList(); - this.noteTitle.value = ''; - this.noteContent.innerHTML = ''; - this.todosList.innerHTML = ''; - this.imagesList.innerHTML = ''; + if (this.noteTitle) this.noteTitle.value = ''; + if (this.noteContent) this.noteContent.innerHTML = ''; + if (this.todosList) this.todosList.innerHTML = ''; + if (this.imagesList) this.imagesList.innerHTML = ''; } catch (error) { console.error('Error deleting note:', error); } @@ -214,13 +250,17 @@ class NotesService { const text = prompt('Nouveau todo:'); if (!text) return; + if (!this.currentNote.todos) { + this.currentNote.todos = []; + } + this.currentNote.todos.push({ text, completed: false }); this.renderTodos(); this.autoSave(); } async toggleTodo(index) { - if (!this.currentNote) return; + if (!this.currentNote || !this.currentNote.todos) return; this.currentNote.todos[index].completed = !this.currentNote.todos[index].completed; this.renderTodos(); @@ -228,7 +268,7 @@ class NotesService { } async deleteTodo(index) { - if (!this.currentNote) return; + if (!this.currentNote || !this.currentNote.todos) return; this.currentNote.todos.splice(index, 1); this.renderTodos(); @@ -249,6 +289,9 @@ class NotesService { }); const image = await response.json(); + if (!this.currentNote.images) { + this.currentNote.images = []; + } this.currentNote.images.push(image); this.renderImages(); } catch (error) { @@ -259,7 +302,7 @@ class NotesService { } async deleteImage(imageId) { - if (!this.currentNote || !confirm('Supprimer cette image ?')) return; + if (!this.currentNote || !this.currentNote.images || !confirm('Supprimer cette image ?')) return; try { await fetch(`/api/notes/${this.currentNote.id}/images/${imageId}`, { diff --git a/server.js b/server.js index 2196139..8b737fb 100644 --- a/server.js +++ b/server.js @@ -3,8 +3,15 @@ const cors = require('cors'); const helmet = require('helmet'); 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 }); +} + // Configure logger const logger = winston.createLogger({ level: 'info', @@ -25,9 +32,11 @@ app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], - scriptSrc: ["'self'", "'unsafe-inline'"], - styleSrc: ["'self'", "'unsafe-inline'"], + 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'"] } } })); @@ -35,6 +44,12 @@ 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'); +} + // Routes app.use('/api/auth', require('./routes/auth.routes')); app.use('/api/users', require('./routes/users.routes'));