const express = require('express'); const { authenticateToken } = require('../middleware/auth'); const db = require('../database'); const router = express.Router(); // Live vote endpoint - receives real-time votes from bot router.post('/live', authenticateToken, (req, res) => { try { const { username, vote, timestamp } = req.body; // Validate payload if (!username || !vote || !timestamp) { return res.status(400).json({ error: 'Missing required fields: username, vote, timestamp' }); } if (vote !== 'up' && vote !== 'down') { return res.status(400).json({ error: 'vote must be either "up" or "down"' }); } // Validate timestamp format const voteTimestamp = new Date(timestamp); if (isNaN(voteTimestamp.getTime())) { return res.status(400).json({ error: 'Invalid timestamp format. Use ISO 8601 format (e.g., 2025-11-01T20:30:00Z)' }); } // Check for active session const activeSession = db.prepare(` SELECT * FROM sessions WHERE is_active = 1 LIMIT 1 `).get(); if (!activeSession) { return res.status(404).json({ error: 'No active session found' }); } // Get all games played in this session with timestamps const sessionGames = db.prepare(` SELECT sg.game_id, sg.played_at, g.title, g.upvotes, g.downvotes, g.popularity_score FROM session_games sg JOIN games g ON sg.game_id = g.id WHERE sg.session_id = ? ORDER BY sg.played_at ASC `).all(activeSession.id); if (sessionGames.length === 0) { return res.status(404).json({ error: 'No games have been played in the active session yet' }); } // Match vote timestamp to the correct game using interval logic const voteTime = voteTimestamp.getTime(); let matchedGame = null; for (let i = 0; i < sessionGames.length; i++) { const currentGame = sessionGames[i]; const nextGame = sessionGames[i + 1]; const currentGameTime = new Date(currentGame.played_at).getTime(); if (nextGame) { const nextGameTime = new Date(nextGame.played_at).getTime(); if (voteTime >= currentGameTime && voteTime < nextGameTime) { matchedGame = currentGame; break; } } else { // Last game in session - vote belongs here if timestamp is after this game started if (voteTime >= currentGameTime) { matchedGame = currentGame; break; } } } if (!matchedGame) { return res.status(404).json({ error: 'Vote timestamp does not match any game in the active session', debug: { voteTimestamp: timestamp, sessionGames: sessionGames.map(g => ({ title: g.title, played_at: g.played_at })) } }); } // Check for duplicate vote (within 1 second window) // Get the most recent vote from this user const lastVote = db.prepare(` SELECT timestamp FROM live_votes WHERE username = ? ORDER BY created_at DESC LIMIT 1 `).get(username); if (lastVote) { const lastVoteTime = new Date(lastVote.timestamp).getTime(); const currentVoteTime = new Date(timestamp).getTime(); const timeDiffSeconds = Math.abs(currentVoteTime - lastVoteTime) / 1000; if (timeDiffSeconds <= 1) { return res.status(409).json({ error: 'Duplicate vote detected (within 1 second of previous vote)', message: 'Please wait at least 1 second between votes', timeSinceLastVote: timeDiffSeconds }); } } // Process the vote in a transaction const voteType = vote === 'up' ? 1 : -1; const insertVote = db.prepare(` INSERT INTO live_votes (session_id, game_id, username, vote_type, timestamp) VALUES (?, ?, ?, ?, ?) `); const updateUpvote = db.prepare(` UPDATE games SET upvotes = upvotes + 1, popularity_score = popularity_score + 1 WHERE id = ? `); const updateDownvote = db.prepare(` UPDATE games SET downvotes = downvotes + 1, popularity_score = popularity_score - 1 WHERE id = ? `); const processVote = db.transaction(() => { insertVote.run(activeSession.id, matchedGame.game_id, username, voteType, timestamp); if (voteType === 1) { updateUpvote.run(matchedGame.game_id); } else { updateDownvote.run(matchedGame.game_id); } }); processVote(); // Get updated game stats const updatedGame = db.prepare(` SELECT id, title, upvotes, downvotes, popularity_score FROM games WHERE id = ? `).get(matchedGame.game_id); // Get session stats const sessionStats = db.prepare(` SELECT s.*, COUNT(sg.id) as games_played FROM sessions s LEFT JOIN session_games sg ON s.id = sg.session_id WHERE s.id = ? GROUP BY s.id `).get(activeSession.id); res.json({ success: true, message: 'Vote recorded successfully', session: { id: sessionStats.id, games_played: sessionStats.games_played }, game: { id: updatedGame.id, title: updatedGame.title, upvotes: updatedGame.upvotes, downvotes: updatedGame.downvotes, popularity_score: updatedGame.popularity_score }, vote: { username: username, type: vote, timestamp: timestamp } }); } catch (error) { console.error('Error processing live vote:', error); res.status(500).json({ error: error.message }); } }); module.exports = router;