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'));