Option 1 : SQLite (recommandé pour démarrer, sans serveur externe) Backend complet (server.js) const express = require('express'); const cors = require('cors'); const sqlite3 = require('sqlite3').verbose(); const { open } = require('sqlite'); const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); const app = express(); const PORT = 3000; const SECRET_KEY = 'votre_cle_secrete_tres_longue_et_complexe_2025'; app.use(cors()); app.use(express.json()); // Initialisation de la base de données SQLite let db; async function initDatabase() { db = await open({ filename: './dream_journal.db', driver: sqlite3.Database }); // Table des utilisateurs await db.exec(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); // Table des rêves await db.exec(` CREATE TABLE IF NOT EXISTS dreams ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, date_time DATETIME NOT NULL, title TEXT, story TEXT NOT NULL, emotions TEXT, symbols TEXT, interpretation TEXT, prayer_action TEXT, followup TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) `); // Index pour optimiser les recherches await db.exec(` CREATE INDEX IF NOT EXISTS idx_dreams_user_date ON dreams(user_id, date_time); CREATE INDEX IF NOT EXISTS idx_dreams_user_title ON dreams(user_id, title); `); console.log("✅ Base de données SQLite initialisée"); } // ============ ROUTES AUTH ============ // Inscription app.post('/api/register', async (req, res) => { try { const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ error: "Nom d'utilisateur et mot de passe requis" }); } const existingUser = await db.get('SELECT id FROM users WHERE username = ?', [username]); if (existingUser) { return res.status(400).json({ error: "Cet utilisateur existe déjà" }); } const passwordHash = await bcrypt.hash(password, 10); const result = await db.run( 'INSERT INTO users (username, password_hash) VALUES (?, ?)', [username, passwordHash] ); const token = jwt.sign({ userId: result.lastID, username }, SECRET_KEY, { expiresIn: '30d' }); res.json({ token, username, userId: result.lastID }); } catch (error) { console.error(error); res.status(500).json({ error: "Erreur serveur" }); } }); // Connexion app.post('/api/login', async (req, res) => { try { const { username, password } = req.body; const user = await db.get('SELECT * FROM users WHERE username = ?', [username]); if (!user) { return res.status(401).json({ error: "Identifiants incorrects" }); } const validPassword = await bcrypt.compare(password, user.password_hash); if (!validPassword) { return res.status(401).json({ error: "Identifiants incorrects" }); } const token = jwt.sign({ userId: user.id, username }, SECRET_KEY, { expiresIn: '30d' }); res.json({ token, username, userId: user.id }); } catch (error) { res.status(500).json({ error: "Erreur serveur" }); } }); // Middleware d'authentification function authenticateToken(req, res, next) { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (!token) { return res.status(401).json({ error: "Token manquant" }); } jwt.verify(token, SECRET_KEY, (err, user) => { if (err) return res.status(403).json({ error: "Token invalide" }); req.user = user; next(); }); } // ============ ROUTES DREAMS ============ // Récupérer tous les rêves d'un utilisateur app.get('/api/dreams', authenticateToken, async (req, res) => { try { const dreams = await db.all( `SELECT * FROM dreams WHERE user_id = ? ORDER BY date_time DESC`, [req.user.userId] ); res.json(dreams); } catch (error) { res.status(500).json({ error: "Erreur lors de la récupération" }); } }); // Récupérer les 5 derniers rêves app.get('/api/dreams/recent', authenticateToken, async (req, res) => { try { const dreams = await db.all( `SELECT * FROM dreams WHERE user_id = ? ORDER BY date_time DESC LIMIT 5`, [req.user.userId] ); res.json(dreams); } catch (error) { res.status(500).json({ error: "Erreur lors de la récupération" }); } }); // Récupérer un rêve par ID app.get('/api/dreams/:id', authenticateToken, async (req, res) => { try { const dream = await db.get( `SELECT * FROM dreams WHERE id = ? AND user_id = ?`, [req.params.id, req.user.userId] ); if (!dream) { return res.status(404).json({ error: "Rêve non trouvé" }); } res.json(dream); } catch (error) { res.status(500).json({ error: "Erreur lors de la récupération" }); } }); // Créer un nouveau rêve app.post('/api/dreams', authenticateToken, async (req, res) => { try { const { dateTime, title, story, emotions, symbols, interpretation, prayerAction, followup } = req.body; if (!dateTime || !story) { return res.status(400).json({ error: "Date et récit sont obligatoires" }); } const result = await db.run( `INSERT INTO dreams (user_id, date_time, title, story, emotions, symbols, interpretation, prayer_action, followup) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [req.user.userId, dateTime, title || "", story, emotions || "", symbols || "", interpretation || "", prayerAction || "", followup || ""] ); const newDream = await db.get('SELECT * FROM dreams WHERE id = ?', [result.lastID]); res.json(newDream); } catch (error) { console.error(error); res.status(500).json({ error: "Erreur lors de la création" }); } }); // Mettre à jour un rêve app.put('/api/dreams/:id', authenticateToken, async (req, res) => { try { const { dateTime, title, story, emotions, symbols, interpretation, prayerAction, followup } = req.body; const dream = await db.get( 'SELECT * FROM dreams WHERE id = ? AND user_id = ?', [req.params.id, req.user.userId] ); if (!dream) { return res.status(404).json({ error: "Rêve non trouvé" }); } await db.run( `UPDATE dreams SET date_time = ?, title = ?, story = ?, emotions = ?, symbols = ?, interpretation = ?, prayer_action = ?, followup = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, [dateTime, title || "", story, emotions || "", symbols || "", interpretation || "", prayerAction || "", followup || "", req.params.id] ); const updatedDream = await db.get('SELECT * FROM dreams WHERE id = ?', [req.params.id]); res.json(updatedDream); } catch (error) { res.status(500).json({ error: "Erreur lors de la mise à jour" }); } }); // Supprimer un rêve app.delete('/api/dreams/:id', authenticateToken, async (req, res) => { try { const result = await db.run( 'DELETE FROM dreams WHERE id = ? AND user_id = ?', [req.params.id, req.user.userId] ); if (result.changes === 0) { return res.status(404).json({ error: "Rêve non trouvé" }); } res.json({ success: true, message: "Rêve supprimé" }); } catch (error) { res.status(500).json({ error: "Erreur lors de la suppression" }); } }); // Rechercher des rêves app.post('/api/dreams/search', authenticateToken, async (req, res) => { try { const { keyword, date } = req.body; let query = `SELECT * FROM dreams WHERE user_id = ?`; let params = [req.user.userId]; if (keyword && keyword.trim()) { query += ` AND (title LIKE ? OR story LIKE ? OR symbols LIKE ? OR emotions LIKE ? OR interpretation LIKE ?)`; const searchTerm = `%${keyword.trim()}%`; params.push(searchTerm, searchTerm, searchTerm, searchTerm, searchTerm); } if (date && date.trim()) { query += ` AND date_time LIKE ?`; params.push(`%${date.trim()}%`); } query += ` ORDER BY date_time DESC`; const dreams = await db.all(query, params); res.json(dreams); } catch (error) { res.status(500).json({ error: "Erreur lors de la recherche" }); } }); // Statistiques utilisateur app.get('/api/stats', authenticateToken, async (req, res) => { try { const total = await db.get( 'SELECT COUNT(*) as total FROM dreams WHERE user_id = ?', [req.user.userId] ); const lastMonth = await db.get( `SELECT COUNT(*) as count FROM dreams WHERE user_id = ? AND date_time >= datetime('now', '-30 days')`, [req.user.userId] ); const commonSymbols = await db.all( `SELECT symbols, COUNT(*) as count FROM dreams WHERE user_id = ? AND symbols IS NOT NULL AND symbols != '' GROUP BY symbols ORDER BY count DESC LIMIT 5`, [req.user.userId] ); res.json({ total: total.total, lastMonth: lastMonth.count, commonSymbols }); } catch (error) { res.status(500).json({ error: "Erreur" }); } }); // Démarrer le serveur async function start() { await initDatabase(); app.listen(PORT, () => { console.log(`🚀 Serveur démarré sur http://localhost:${PORT}`); }); } start(); Installation du backend # Créer un dossier pour le backend mkdir dream-journal-backend cd dream-journal-backend # Initialiser le projet npm init -y # Installer les dépendances npm install express cors sqlite sqlite3 bcrypt jsonwebtoken npm install -D nodemon # Créer le fichier server.js et y coller le code ci-dessus # Démarrer le serveur node server.js # ou avec nodemon pour le développement npx nodemon server.js Frontend adapté pour SQL (version simplifiée avec API)