const express = require('express'); const bodyParser = require('body-parser'); const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const session = require('express-session'); const moment = require('moment'); const archiver = require('./leaderboard_archiver'); // Constants const app = express(); const PORT = process.env.ADMIN_PORT || 6969; const DATA_FILE = path.join(__dirname, 'data', 'data.json'); const IP_FILE = path.join(__dirname, 'data', 'ips.json'); const CATEGORIES = ["6gb", "12gb", "16gb", "24gb", "48gb", "72gb", "96gb"]; // Admin credentials - in a real app, store these securely const ADMIN_USER = 'admin'; const ADMIN_PASS = 'secure_password123'; // Change this to a strong password // Middleware app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); app.use(session({ secret: crypto.randomBytes(32).toString('hex'), resave: false, saveUninitialized: false, cookie: { secure: false, // Set to true if using HTTPS httpOnly: true, maxAge: 3600000 // 1 hour } })); // --- Authentication --- const authenticate = (req, res, next) => { if (req.session && req.session.authenticated) { return next(); } res.redirect('/admin'); }; // --- Data Handling --- function readJson(file, fallback) { try { if (fs.existsSync(file)) { return JSON.parse(fs.readFileSync(file)); } return fallback; } catch { return fallback; } } function writeJson(file, obj) { fs.writeFileSync(file, JSON.stringify(obj, null, 2)); } // --- Routes --- // Simple root message app.get('/', (req, res) => { res.send('Admin Server is running. Access /admin for the interface.'); }); // Admin Login Page app.get('/admin', (req, res) => { if (req.session && req.session.authenticated) { return res.redirect('/admin/dashboard'); } res.send(` Poll Admin - Login

Poll Admin Login

${req.query.error ? '

Invalid username or password

' : ''}
`); }); // Admin Login Handler app.post('/admin/login', (req, res) => { const { username, password } = req.body; if (username === ADMIN_USER && password === ADMIN_PASS) { req.session.authenticated = true; req.session.username = username; res.redirect('/admin/dashboard'); } else { res.redirect('/admin?error=1'); } }); // Admin Logout app.get('/admin/logout', (req, res) => { req.session.destroy(); res.redirect('/admin'); }); // Admin Dashboard (Protected) app.get('/admin/dashboard', authenticate, (req, res) => { const data = readJson(DATA_FILE, {}); let categoriesHtml = ''; CATEGORIES.forEach(category => { const entries = data[category] || []; const sortedEntries = [...entries].sort((a, b) => b.votes - a.votes); let tableRows = sortedEntries.map((entry) => ` ${escapeHtml(entry.id)} ${escapeHtml(entry.name)} ${entry.votes} Edit
`).join(''); categoriesHtml += `

${category} Category

${sortedEntries.length > 0 ? ` ${tableRows}
ID Name Votes Actions
` : '

No entries in this category.

'}
`; }); res.send(` Poll Admin Dashboard

Poll Admin Dashboard

${categoriesHtml} `); }); // Edit Entry Form app.get('/admin/edit/:category/:id', authenticate, (req, res) => { const { category, id } = req.params; const data = readJson(DATA_FILE, {}); if (!CATEGORIES.includes(category)) { return res.status(400).send('Invalid category'); } const entries = data[category] || []; const entry = entries.find(e => e.id === id); if (!entry) { return res.status(404).send('Entry not found'); } res.send(` Edit Entry

Edit Entry

`); }); // Update Entry app.post('/admin/edit/:category/:id', authenticate, (req, res) => { const { category, id } = req.params; const { name, votes } = req.body; const data = readJson(DATA_FILE, {}); if (!CATEGORIES.includes(category)) { return res.status(400).send('Invalid category'); } const entries = data[category] || []; const entryIndex = entries.findIndex(e => e.id === id); if (entryIndex === -1) { return res.status(404).send('Entry not found'); } // Update entry entries[entryIndex].name = name.trim(); entries[entryIndex].votes = parseInt(votes, 10); // Save data writeJson(DATA_FILE, data); console.log(`Updated entry: ${category}/${id}`); res.redirect('/admin/dashboard'); }); // Delete Entry app.post('/admin/delete/:category/:id', authenticate, (req, res) => { const { category, id } = req.params; const data = readJson(DATA_FILE, {}); if (!CATEGORIES.includes(category)) { return res.status(400).send('Invalid category'); } const entries = data[category] || []; const entryIndex = entries.findIndex(e => e.id === id); if (entryIndex === -1) { return res.status(404).send('Entry not found'); } // Remove entry entries.splice(entryIndex, 1); // Save data writeJson(DATA_FILE, data); console.log(`Deleted entry: ${category}/${id}`); // Also clean up any IP votes for this entry const ips = readJson(IP_FILE, {}); let ipChanged = false; // Check each IP entry Object.keys(ips).forEach(ipKey => { if (typeof ips[ipKey] === 'object' && ips[ipKey][category] === id) { delete ips[ipKey][category]; ipChanged = true; } }); if (ipChanged) { writeJson(IP_FILE, ips); console.log('Updated IP tracking file after entry deletion'); } res.redirect('/admin/dashboard'); }); // Archives Dashboard app.get('/admin/archives', authenticate, (req, res) => { const archivedWeeks = archiver.getArchivedWeeks(); let archivesHtml = ''; if (archivedWeeks.length === 0) { archivesHtml = '

No archived data available yet.

'; } else { let tableRows = archivedWeeks.map(weekId => { const archive = archiver.getArchivedWeek(weekId); if (!archive) return ''; return ` ${escapeHtml(archive.weekId)} ${escapeHtml(archive.startDate)} ${escapeHtml(archive.endDate)} ${new Date(archive.archivedAt).toLocaleString()} View `; }).join(''); archivesHtml = `

Archived Leaderboards

All Archived Weeks

${tableRows}
Week ID Start Date End Date Archived At Actions
`; } res.send(` Archived Leaderboards

Archived Leaderboards

${archivesHtml} `); }); // View specific archived week app.get('/admin/archives/week/:weekId', authenticate, (req, res) => { const { weekId } = req.params; const archive = archiver.getArchivedWeek(weekId); if (!archive) { return res.status(404).send('Archive not found'); } let categoriesHtml = ''; CATEGORIES.forEach(category => { const entries = archive.data[category] || []; const sortedEntries = [...entries].sort((a, b) => b.votes - a.votes); let tableRows = sortedEntries.map((entry) => ` ${escapeHtml(entry.id)} ${escapeHtml(entry.name)} ${entry.votes} `).join(''); categoriesHtml += `

${category} Category

${sortedEntries.length > 0 ? ` ${tableRows}
ID Name Votes
` : '

No entries in this category.

'}
`; }); res.send(` Archived Week: ${weekId}

Archived Week: ${weekId}

Week ID: ${archive.weekId}

Start Date: ${archive.startDate}

End Date: ${archive.endDate}

Archived At: ${new Date(archive.archivedAt).toLocaleString()}

${categoriesHtml} `); }); // Search archives by date range app.get('/admin/archives/search', authenticate, (req, res) => { const { startDate, endDate } = req.query; if (!startDate || !endDate) { return res.redirect('/admin/archives'); } try { const archives = archiver.getArchivedRange(startDate, endDate); let resultsHtml = ''; if (archives.length === 0) { resultsHtml = '

No archives found for the specified date range.

'; } else { let tableRows = archives.map(archive => ` ${escapeHtml(archive.weekId)} ${escapeHtml(archive.startDate)} ${escapeHtml(archive.endDate)} ${new Date(archive.archivedAt).toLocaleString()} View `).join(''); resultsHtml = `

Search Results

Found ${archives.length} archive(s) between ${startDate} and ${endDate}

${tableRows}
Week ID Start Date End Date Archived At Actions
`; } res.send(` Archive Search Results

Archive Search Results

Search Archives by Date Range

${resultsHtml} `); } catch (error) { console.error('Error searching archives:', error); res.redirect('/admin/archives?error=1'); } }); // Manually create an archive app.post('/admin/archives/create', authenticate, (req, res) => { try { const weekId = archiver.archiveCurrentWeek(); // Reset votes after archiving archiver.resetLeaderboard(); res.redirect(`/admin/archives/week/${weekId}`); } catch (error) { console.error('Error creating archive:', error); res.redirect('/admin/archives?error=1'); } }); // Helper function to escape HTML (prevent XSS) function escapeHtml(unsafe) { if (typeof unsafe !== 'string') return ''; return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } // Start Server app.listen(PORT, () => { console.log(`Admin server listening on http://localhost:${PORT}`); });