server.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. // server.js - Fichier principal du serveur
  2. const express = require('express');
  3. const sqlite3 = require('sqlite3').verbose();
  4. const bcrypt = require('bcrypt');
  5. const cors = require('cors');
  6. const path = require('path');
  7. const http = require('http');
  8. const WebSocket = require('ws');
  9. const app = express();
  10. const server = http.createServer(app);
  11. const port = process.env.PORT || 3000;
  12. // Create a WebSocket server
  13. const wss = new WebSocket.Server({ server });
  14. // Store connected WebSocket clients
  15. const clients = new Set();
  16. // WebSocket connection handler
  17. wss.on('connection', (ws) => {
  18. console.log('Client connected');
  19. clients.add(ws);
  20. // Send a welcome message
  21. ws.send(JSON.stringify({ type: 'connection', message: 'Connected to WebSocket server' }));
  22. // Handle client disconnection
  23. ws.on('close', () => {
  24. console.log('Client disconnected');
  25. clients.delete(ws);
  26. });
  27. // Handle messages from clients (not used in this implementation, but available for future use)
  28. ws.on('message', (message) => {
  29. console.log('Received message:', message);
  30. });
  31. });
  32. // Helper function to broadcast messages to all connected clients
  33. function broadcastMessage(type, data) {
  34. const message = JSON.stringify({ type, data });
  35. clients.forEach(client => {
  36. if (client.readyState === WebSocket.OPEN) {
  37. client.send(message);
  38. }
  39. });
  40. }
  41. // Middleware
  42. app.use(express.json());
  43. app.use(cors({
  44. origin: ['http://localhost:3001', 'http://localhost:3000', 'http://localhost'],
  45. credentials: true
  46. }));
  47. app.use(express.static(path.join(__dirname, 'public')));
  48. // Connexion à la base de données SQLite
  49. const DB_PATH = process.env.DB_PATH || '/app/data/framed.db';
  50. console.log(`Tentative de connexion à la BD: ${DB_PATH}`);
  51. const db = new sqlite3.Database(DB_PATH, (err) => {
  52. if (err) {
  53. console.error('Erreur de connexion à la base de données:', err.message);
  54. } else {
  55. console.log('Connecté à la base de données SQLite');
  56. initDatabase();
  57. }
  58. });
  59. // Initialiser la base de données
  60. function initDatabase() {
  61. // Activer les clés étrangères
  62. db.run('PRAGMA foreign_keys = ON');
  63. // Créer la table des utilisateurs
  64. db.run(`
  65. CREATE TABLE IF NOT EXISTS users (
  66. id INTEGER PRIMARY KEY AUTOINCREMENT,
  67. username TEXT UNIQUE NOT NULL,
  68. password TEXT NOT NULL,
  69. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  70. )
  71. `);
  72. // Créer la table des scores
  73. db.run(`
  74. CREATE TABLE IF NOT EXISTS scores (
  75. id INTEGER PRIMARY KEY AUTOINCREMENT,
  76. user_id INTEGER NOT NULL,
  77. date TEXT NOT NULL,
  78. game_type TEXT NOT NULL,
  79. game_number INTEGER,
  80. score INTEGER NOT NULL,
  81. comment TEXT,
  82. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  83. FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
  84. )
  85. `);
  86. console.log('Tables vérifiées/créées');
  87. }
  88. // Route de test simple
  89. app.get('/api/test', (req, res) => {
  90. res.json({ message: 'Le serveur fonctionne correctement!' });
  91. });
  92. // Routes d'API
  93. // Inscription
  94. app.post('/api/register', async (req, res) => {
  95. const { username, password } = req.body;
  96. if (!username || !password) {
  97. return res.status(400).json({ error: 'Nom d\'utilisateur et mot de passe requis' });
  98. }
  99. try {
  100. // Vérifier si l'utilisateur existe déjà
  101. db.get('SELECT * FROM users WHERE username = ?', [username], async (err, user) => {
  102. if (err) {
  103. return res.status(500).json({ error: err.message });
  104. }
  105. if (user) {
  106. return res.status(400).json({ error: 'Cet utilisateur existe déjà' });
  107. }
  108. // Hasher le mot de passe
  109. const hashedPassword = await bcrypt.hash(password, 10);
  110. // Insérer le nouvel utilisateur
  111. db.run('INSERT INTO users (username, password) VALUES (?, ?)',
  112. [username, hashedPassword],
  113. function(err) {
  114. if (err) {
  115. return res.status(500).json({ error: err.message });
  116. }
  117. res.status(201).json({
  118. message: 'Utilisateur créé avec succès',
  119. userId: this.lastID,
  120. username
  121. });
  122. }
  123. );
  124. });
  125. } catch (error) {
  126. res.status(500).json({ error: error.message });
  127. }
  128. });
  129. // Connexion
  130. app.post('/api/login', (req, res) => {
  131. const { username, password } = req.body;
  132. if (!username || !password) {
  133. return res.status(400).json({ error: 'Nom d\'utilisateur et mot de passe requis' });
  134. }
  135. db.get('SELECT * FROM users WHERE username = ?', [username], async (err, user) => {
  136. if (err) {
  137. return res.status(500).json({ error: err.message });
  138. }
  139. if (!user) {
  140. return res.status(401).json({ error: 'Identifiants invalides' });
  141. }
  142. // Vérifier le mot de passe
  143. const validPassword = await bcrypt.compare(password, user.password);
  144. if (!validPassword) {
  145. return res.status(401).json({ error: 'Identifiants invalides' });
  146. }
  147. res.json({
  148. userId: user.id,
  149. username: user.username
  150. });
  151. });
  152. });
  153. // Ajouter un score
  154. app.post('/api/scores', (req, res) => {
  155. const { userId, date, gameType, gameNumber, score, comment } = req.body;
  156. if (!userId || !date || !gameType || !score) {
  157. return res.status(400).json({ error: 'Données incomplètes' });
  158. }
  159. db.run(
  160. 'INSERT INTO scores (user_id, date, game_type, game_number, score, comment) VALUES (?, ?, ?, ?, ?, ?)',
  161. [userId, date, gameType, gameNumber, score, comment || null],
  162. function(err) {
  163. if (err) {
  164. return res.status(500).json({ error: err.message });
  165. }
  166. const newScore = {
  167. id: this.lastID,
  168. userId,
  169. date,
  170. gameType,
  171. gameNumber,
  172. score,
  173. comment
  174. };
  175. // Get username for the new score
  176. db.get('SELECT username FROM users WHERE id = ?', [userId], (err, user) => {
  177. if (!err && user) {
  178. newScore.username = user.username;
  179. // Broadcast the new score to all connected clients
  180. broadcastMessage('new-score', newScore);
  181. }
  182. });
  183. res.status(201).json(newScore);
  184. }
  185. );
  186. });
  187. // Récupérer les scores d'un utilisateur
  188. app.get('/api/scores/user/:userId', (req, res) => {
  189. const userId = req.params.userId;
  190. db.all('SELECT * FROM scores WHERE user_id = ? ORDER BY date DESC', [userId], (err, scores) => {
  191. if (err) {
  192. return res.status(500).json({ error: err.message });
  193. }
  194. res.json(scores);
  195. });
  196. });
  197. // Récupérer tous les scores (pour le classement)
  198. app.get('/api/scores', (req, res) => {
  199. const gameType = req.query.gameType || 'all';
  200. let query = `
  201. SELECT s.*, u.username
  202. FROM scores s
  203. JOIN users u ON s.user_id = u.id
  204. `;
  205. if (gameType !== 'all') {
  206. query += ` WHERE s.game_type = '${gameType}'`;
  207. }
  208. db.all(query, (err, scores) => {
  209. if (err) {
  210. return res.status(500).json({ error: err.message });
  211. }
  212. res.json(scores);
  213. });
  214. });
  215. // Supprimer un score
  216. app.delete('/api/scores/:id', (req, res) => {
  217. const scoreId = req.params.id;
  218. const userId = req.query.userId;
  219. if (!userId) {
  220. return res.status(400).json({ error: 'Utilisateur non spécifié' });
  221. }
  222. db.run('DELETE FROM scores WHERE id = ? AND user_id = ?', [scoreId, userId], function(err) {
  223. if (err) {
  224. return res.status(500).json({ error: err.message });
  225. }
  226. if (this.changes === 0) {
  227. return res.status(404).json({ error: 'Score non trouvé ou non autorisé' });
  228. }
  229. // Broadcast the deleted score to all connected clients
  230. broadcastMessage('delete-score', { id: scoreId, userId });
  231. res.json({ message: 'Score supprimé avec succès' });
  232. });
  233. });
  234. // Récupérer les statistiques d'un utilisateur
  235. app.get('/api/stats/:userId', (req, res) => {
  236. const userId = req.params.userId;
  237. const query = `
  238. SELECT
  239. game_type,
  240. COUNT(*) as total_games,
  241. AVG(score) as average_score,
  242. MIN(score) as best_score
  243. FROM scores
  244. WHERE user_id = ?
  245. GROUP BY game_type
  246. `;
  247. db.all(query, [userId], (err, stats) => {
  248. if (err) {
  249. return res.status(500).json({ error: err.message });
  250. }
  251. res.json(stats);
  252. });
  253. });
  254. // Récupérer le classement
  255. app.get('/api/leaderboard', (req, res) => {
  256. const gameType = req.query.gameType || 'all';
  257. let query = `
  258. SELECT
  259. u.id,
  260. u.username,
  261. COUNT(*) as total_games,
  262. AVG(s.score) as average_score,
  263. MIN(s.score) as best_score
  264. FROM scores s
  265. JOIN users u ON s.user_id = u.id
  266. `;
  267. if (gameType !== 'all') {
  268. query += ` WHERE s.game_type = '${gameType}'`;
  269. }
  270. query += `
  271. GROUP BY u.id
  272. ORDER BY average_score ASC, best_score ASC
  273. `;
  274. db.all(query, (err, leaderboard) => {
  275. if (err) {
  276. return res.status(500).json({ error: err.message });
  277. }
  278. res.json(leaderboard);
  279. });
  280. });
  281. // Gestion du mode de production ou développement
  282. if (process.env.NODE_ENV === 'production') {
  283. // En production, servir les fichiers statiques
  284. app.use(express.static(path.join(__dirname, 'public')));
  285. // Route par défaut pour servir l'application React
  286. app.get('*', (req, res) => {
  287. res.sendFile(path.join(__dirname, 'public', 'index.html'));
  288. });
  289. } else {
  290. // En développement, juste répondre à l'API
  291. app.get('/', (req, res) => {
  292. res.json({ message: 'API Framed Tracker fonctionne correctement. Utilisez le port 3001 pour accéder au frontend.' });
  293. });
  294. }
  295. // Démarrer le serveur HTTP avec WebSocket
  296. server.listen(port, () => {
  297. console.log(`Serveur HTTP en écoute sur le port ${port}`);
  298. console.log(`Serveur WebSocket en écoute sur le port ${port}`);
  299. });
  300. // Gestion des erreurs
  301. app.use((err, req, res, next) => {
  302. console.error('Erreur API:', err);
  303. res.status(500).json({ error: 'Erreur serveur' });
  304. });
  305. // Fermer proprement la connexion à la base de données lors de l'arrêt du serveur
  306. process.on('SIGINT', () => {
  307. db.close((err) => {
  308. if (err) {
  309. console.error(err.message);
  310. }
  311. console.log('Connexion à la base de données fermée');
  312. process.exit(0);
  313. });
  314. });
  315. // Gestion des exceptions non capturées
  316. process.on('uncaughtException', (error) => {
  317. console.error('ERREUR NON CAPTURÉE:', error);
  318. });
  319. process.on('unhandledRejection', (reason, promise) => {
  320. console.error('PROMESSE REJETÉE NON GÉRÉE:', reason);
  321. });