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 ? `
ID |
Name |
Votes |
Actions |
${tableRows}
` : '
No entries in this category.
'}
`;
});
res.send(`
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
`);
});
// 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
Search Archives by Date Range
All Archived Weeks
Week ID |
Start Date |
End Date |
Archived At |
Actions |
${tableRows}
`;
}
res.send(`
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 ? `
ID |
Name |
Votes |
${tableRows}
` : '
No entries in this category.
'}
`;
});
res.send(`
Archived Week: ${weekId}
${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}
Week ID |
Start Date |
End Date |
Archived At |
Actions |
${tableRows}
`;
}
res.send(`
Archive Search Results
${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}`);
});