// server.js - Fichier principal du serveur const express = require('express'); const sqlite3 = require('sqlite3').verbose(); const bcrypt = require('bcrypt'); const cors = require('cors'); const path = require('path'); const app = express(); const port = process.env.PORT || 3000; // Middleware app.use(express.json()); app.use(cors()); app.use(express.static(path.join(__dirname, 'public'))); // Connexion à la base de données SQLite const db = new sqlite3.Database('/app/data/framed.db', (err) => { if (err) { console.error('Erreur de connexion à la base de données:', err.message); } else { console.log('Connecté à la base de données SQLite'); initDatabase(); } }); // Initialiser la base de données function initDatabase() { // Activer les clés étrangères db.run('PRAGMA foreign_keys = ON'); // Créer la table des utilisateurs db.run(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); // Créer la table des scores db.run(` CREATE TABLE IF NOT EXISTS scores ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, date TEXT NOT NULL, game_type TEXT NOT NULL, game_number INTEGER, score INTEGER NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ) `); console.log('Tables vérifiées/créées'); } // Routes d'API // Inscription app.post('/api/register', async (req, res) => { const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ error: 'Nom d\'utilisateur et mot de passe requis' }); } try { // Vérifier si l'utilisateur existe déjà db.get('SELECT * FROM users WHERE username = ?', [username], async (err, user) => { if (err) { return res.status(500).json({ error: err.message }); } if (user) { return res.status(400).json({ error: 'Cet utilisateur existe déjà' }); } // Hasher le mot de passe const hashedPassword = await bcrypt.hash(password, 10); // Insérer le nouvel utilisateur db.run('INSERT INTO users (username, password) VALUES (?, ?)', [username, hashedPassword], function(err) { if (err) { return res.status(500).json({ error: err.message }); } res.status(201).json({ message: 'Utilisateur créé avec succès', userId: this.lastID, username }); } ); }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Connexion app.post('/api/login', (req, res) => { const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ error: 'Nom d\'utilisateur et mot de passe requis' }); } db.get('SELECT * FROM users WHERE username = ?', [username], async (err, user) => { if (err) { return res.status(500).json({ error: err.message }); } if (!user) { return res.status(401).json({ error: 'Identifiants invalides' }); } // Vérifier le mot de passe const validPassword = await bcrypt.compare(password, user.password); if (!validPassword) { return res.status(401).json({ error: 'Identifiants invalides' }); } res.json({ userId: user.id, username: user.username }); }); }); // Ajouter un score app.post('/api/scores', (req, res) => { const { userId, date, gameType, gameNumber, score } = req.body; if (!userId || !date || !gameType || !score) { return res.status(400).json({ error: 'Données incomplètes' }); } db.run( 'INSERT INTO scores (user_id, date, game_type, game_number, score) VALUES (?, ?, ?, ?, ?)', [userId, date, gameType, gameNumber, score], function(err) { if (err) { return res.status(500).json({ error: err.message }); } res.status(201).json({ id: this.lastID, userId, date, gameType, gameNumber, score }); } ); }); // Récupérer les scores d'un utilisateur app.get('/api/scores/user/:userId', (req, res) => { const userId = req.params.userId; db.all('SELECT * FROM scores WHERE user_id = ? ORDER BY date DESC', [userId], (err, scores) => { if (err) { return res.status(500).json({ error: err.message }); } res.json(scores); }); }); // Récupérer tous les scores (pour le classement) app.get('/api/scores', (req, res) => { const gameType = req.query.gameType || 'all'; let query = ` SELECT s.*, u.username FROM scores s JOIN users u ON s.user_id = u.id `; if (gameType !== 'all') { query += ` WHERE s.game_type = '${gameType}'`; } db.all(query, (err, scores) => { if (err) { return res.status(500).json({ error: err.message }); } res.json(scores); }); }); // Supprimer un score app.delete('/api/scores/:id', (req, res) => { const scoreId = req.params.id; const userId = req.query.userId; if (!userId) { return res.status(400).json({ error: 'Utilisateur non spécifié' }); } db.run('DELETE FROM scores WHERE id = ? AND user_id = ?', [scoreId, userId], function(err) { if (err) { return res.status(500).json({ error: err.message }); } if (this.changes === 0) { return res.status(404).json({ error: 'Score non trouvé ou non autorisé' }); } res.json({ message: 'Score supprimé avec succès' }); }); }); // Récupérer les statistiques d'un utilisateur app.get('/api/stats/:userId', (req, res) => { const userId = req.params.userId; const query = ` SELECT game_type, COUNT(*) as total_games, AVG(score) as average_score, MIN(score) as best_score FROM scores WHERE user_id = ? GROUP BY game_type `; db.all(query, [userId], (err, stats) => { if (err) { return res.status(500).json({ error: err.message }); } res.json(stats); }); }); // Récupérer le classement app.get('/api/leaderboard', (req, res) => { const gameType = req.query.gameType || 'all'; let query = ` SELECT u.id, u.username, COUNT(*) as total_games, AVG(s.score) as average_score, MIN(s.score) as best_score FROM scores s JOIN users u ON s.user_id = u.id `; if (gameType !== 'all') { query += ` WHERE s.game_type = '${gameType}'`; } query += ` GROUP BY u.id ORDER BY average_score ASC, best_score ASC `; db.all(query, (err, leaderboard) => { if (err) { return res.status(500).json({ error: err.message }); } res.json(leaderboard); }); }); // Route par défaut pour servir l'application React app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); // Démarrer le serveur //app.listen(port, () => { // console.log(`Serveur en écoute sur le port ${port}`); //}); if (process.env.NODE_ENV === 'production') { // En production, servir les fichiers statiques app.use(express.static(path.join(__dirname, 'public'))); // Route par défaut pour servir l'application React app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); } else { // En développement, juste répondre à l'API app.get('/', (req, res) => { res.json({ message: 'API Framed Tracker fonctionne correctement. Utilisez le port 3001 pour accéder au frontend.' }); }); } // Fermer proprement la connexion à la base de données lors de l'arrêt du serveur process.on('SIGINT', () => { db.close((err) => { if (err) { console.error(err.message); } console.log('Connexion à la base de données fermée'); process.exit(0); }); });