const express = require('express'); const http = require('http'); const { Server } = require('socket.io'); const basicAuth = require('express-basic-auth'); const cookieParser = require('cookie-parser'); const { db, init } = require('./database'); const app = express(); const server = http.createServer(app); const io = new Server(server, { cookie: true }); // Initialize DB init(); app.use(cookieParser()); // Rate limit helper using IP and Cookie // Map: ip -> timestamp const ipLimits = new Map(); function canClick(req, socket) { const ip = req ? req.ip : socket.handshake.address; // Check IP limit const lastClickTime = ipLimits.get(ip); const now = Date.now(); if (lastClickTime && (now - lastClickTime) < 30000) { return false; } return true; } function updateLimit(req, socket) { const ip = req ? req.ip : socket.handshake.address; ipLimits.set(ip, Date.now()); } function getGlobalCount() { return db.prepare('SELECT count FROM global_count WHERE id = 1').get().count; } // Security: Basic Auth for Admin app.use('/admin.html', basicAuth({ users: { 'admin': 'admin123' }, // In a real app, use env vars! challenge: true })); app.use(express.static('public')); // Input Sanitization function sanitize(str) { if (!str) return ''; return str.replace(//g, ">"); } io.on('connection', (socket) => { // Send current count immediately socket.emit('update', { count: getGlobalCount() }); // Handle increment socket.on('increment', (data) => { let { name, quote } = data; name = sanitize(name); quote = sanitize(quote); if (!name || name.trim() === "") { socket.emit('error', 'Name is required'); return; } if (!canClick(null, socket)) { socket.emit('error', 'You must wait 30 seconds between clicks.'); return; } // Transaction to ensure consistency const incrementTx = db.transaction(() => { db.prepare('UPDATE global_count SET count = count + 1 WHERE id = 1').run(); db.prepare('INSERT INTO logs (name, quote) VALUES (?, ?)').run(name, quote || ''); return getGlobalCount(); }); const newCount = incrementTx(); // Update rate limit updateLimit(null, socket); // Broadcast new count to EVERYONE io.emit('update', { count: newCount }); io.to('admin').emit('new_log', { name, quote, timestamp: new Date().toISOString() }); }); // Admin room join socket.on('join_admin', () => { socket.join('admin'); // Send recent logs and simplified stats for chart const logs = db.prepare('SELECT * FROM logs ORDER BY timestamp DESC LIMIT 50').all(); // Stats: Last 24 hours logs for chart const statsCus = db.prepare(` SELECT strftime('%H:00', timestamp) as hour, COUNT(*) as count FROM logs WHERE timestamp >= datetime('now', '-24 hours') GROUP BY hour ORDER BY hour `).all(); socket.emit('admin_data', { logs, stats: statsCus }); }); }); const PORT = process.env.PORT || 3000; server.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); });