CoolFace
Apppublic

Peacemanguy/LLMChoice

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
admin_server.js667 linesDownload Raw Back to root
1const express = require('express');2const bodyParser = require('body-parser');3const fs = require('fs');4const path = require('path');5const crypto = require('crypto');6const session = require('express-session');7const moment = require('moment');8const archiver = require('./leaderboard_archiver');9 10// Constants11const app = express();12const PORT = process.env.ADMIN_PORT || 6969;13const DATA_FILE = path.join(__dirname, 'data', 'data.json');14const IP_FILE = path.join(__dirname, 'data', 'ips.json');15const CATEGORIES = ["6gb", "12gb", "16gb", "24gb", "48gb", "72gb", "96gb"];16 17// Admin credentials - in a real app, store these securely18const ADMIN_USER = 'admin';19const ADMIN_PASS = 'secure_password123'; // Change this to a strong password20 21// Middleware22app.use(bodyParser.urlencoded({ extended: true }));23app.use(bodyParser.json());24app.use(session({25    secret: crypto.randomBytes(32).toString('hex'),26    resave: false,27    saveUninitialized: false,28    cookie: {29        secure: false, // Set to true if using HTTPS30        httpOnly: true,31        maxAge: 3600000 // 1 hour32    }33}));34 35// --- Authentication ---36const authenticate = (req, res, next) => {37    if (req.session && req.session.authenticated) {38        return next();39    }40    res.redirect('/admin');41};42 43// --- Data Handling ---44function readJson(file, fallback) {45    try {46        if (fs.existsSync(file)) {47            return JSON.parse(fs.readFileSync(file));48        }49        return fallback;50    }51    catch { return fallback; }52}53 54function writeJson(file, obj) {55    fs.writeFileSync(file, JSON.stringify(obj, null, 2));56}57 58// --- Routes ---59 60// Simple root message61app.get('/', (req, res) => {62    res.send('Admin Server is running. Access /admin for the interface.');63});64 65// Admin Login Page66app.get('/admin', (req, res) => {67    if (req.session && req.session.authenticated) {68        return res.redirect('/admin/dashboard');69    }70    71    res.send(`72        <!DOCTYPE html>73        <html>74        <head>75            <title>Poll Admin - Login</title>76            <style>77                body { font-family: Arial, sans-serif; margin: 0; padding: 20px; line-height: 1.6; }78                .container { max-width: 500px; margin: 50px auto; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }79                h1 { color: #333; }80                label { display: block; margin-bottom: 5px; }81                input[type="text"], input[type="password"] { width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 3px; }82                button { background: #4CAF50; color: white; border: none; padding: 10px 15px; border-radius: 3px; cursor: pointer; }83                button:hover { background: #45a049; }84                .error { color: red; margin-bottom: 15px; }85            </style>86        </head>87        <body>88            <div class="container">89                <h1>Poll Admin Login</h1>90                ${req.query.error ? '<p class="error">Invalid username or password</p>' : ''}91                <form action="/admin/login" method="POST">92                    <label for="username">Username:</label>93                    <input type="text" id="username" name="username" required>94                    95                    <label for="password">Password:</label>96                    <input type="password" id="password" name="password" required>97                    98                    <button type="submit">Login</button>99                </form>100            </div>101        </body>102        </html>103    `);104});105 106// Admin Login Handler107app.post('/admin/login', (req, res) => {108    const { username, password } = req.body;109    110    if (username === ADMIN_USER && password === ADMIN_PASS) {111        req.session.authenticated = true;112        req.session.username = username;113        res.redirect('/admin/dashboard');114    } else {115        res.redirect('/admin?error=1');116    }117});118 119// Admin Logout120app.get('/admin/logout', (req, res) => {121    req.session.destroy();122    res.redirect('/admin');123});124 125// Admin Dashboard (Protected)126app.get('/admin/dashboard', authenticate, (req, res) => {127    const data = readJson(DATA_FILE, {});128    129    let categoriesHtml = '';130    131    CATEGORIES.forEach(category => {132        const entries = data[category] || [];133        const sortedEntries = [...entries].sort((a, b) => b.votes - a.votes);134        135        let tableRows = sortedEntries.map((entry) => `136            <tr>137                <td>${escapeHtml(entry.id)}</td>138                <td>${escapeHtml(entry.name)}</td>139                <td>${entry.votes}</td>140                <td>141                    <a href="/admin/edit/${category}/${entry.id}" class="btn btn-edit">Edit</a>142                    <form action="/admin/delete/${category}/${entry.id}" method="POST" style="display:inline;">143                        <button type="submit" class="btn btn-delete" onclick="return confirm('Are you sure you want to delete this entry?')">Delete</button>144                    </form>145                </td>146            </tr>147        `).join('');148        149        categoriesHtml += `150            <div class="category-section">151                <h2>${category} Category</h2>152                ${sortedEntries.length > 0 ? `153                <table>154                    <thead>155                        <tr>156                            <th>ID</th>157                            <th>Name</th>158                            <th>Votes</th>159                            <th>Actions</th>160                        </tr>161                    </thead>162                    <tbody>163                        ${tableRows}164                    </tbody>165                </table>166                ` : '<p>No entries in this category.</p>'}167            </div>168        `;169    });170    171    res.send(`172        <!DOCTYPE html>173        <html>174        <head>175            <title>Poll Admin Dashboard</title>176            <style>177                body { font-family: Arial, sans-serif; margin: 0; padding: 20px; line-height: 1.6; }178                .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }179                h1 { color: #333; margin: 0; }180                table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }181                table, th, td { border: 1px solid #ddd; }182                th { background-color: #f2f2f2; padding: 10px; text-align: left; }183                td { padding: 10px; }184                .category-section { margin-bottom: 30px; }185                .btn { display: inline-block; padding: 5px 10px; margin-right: 5px; text-decoration: none; border-radius: 3px; color: white; border: none; cursor: pointer; }186                .btn-edit { background-color: #2196F3; }187                .btn-delete { background-color: #f44336; }188                .logout { text-decoration: none; color: #f44336; }189                .nav-links { display: flex; gap: 15px; align-items: center; }190                .nav-link { text-decoration: none; color: #2196F3; }191            </style>192        </head>193        <body>194            <div class="header">195                <h1>Poll Admin Dashboard</h1>196                <div class="nav-links">197                    <a href="/admin/archives" class="nav-link">View Archives</a>198                    <a href="/admin/logout" class="logout">Logout</a>199                </div>200            </div>201            202            ${categoriesHtml}203        </body>204        </html>205    `);206});207 208// Edit Entry Form209app.get('/admin/edit/:category/:id', authenticate, (req, res) => {210    const { category, id } = req.params;211    const data = readJson(DATA_FILE, {});212    213    if (!CATEGORIES.includes(category)) {214        return res.status(400).send('Invalid category');215    }216    217    const entries = data[category] || [];218    const entry = entries.find(e => e.id === id);219    220    if (!entry) {221        return res.status(404).send('Entry not found');222    }223    224    res.send(`225        <!DOCTYPE html>226        <html>227        <head>228            <title>Edit Entry</title>229            <style>230                body { font-family: Arial, sans-serif; margin: 0; padding: 20px; line-height: 1.6; }231                .container { max-width: 600px; margin: 0 auto; }232                h1 { color: #333; }233                label { display: block; margin-bottom: 5px; }234                input[type="text"], input[type="number"] { width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 3px; }235                .buttons { margin-top: 20px; }236                button { padding: 8px 15px; margin-right: 10px; border: none; border-radius: 3px; cursor: pointer; }237                .save { background-color: #4CAF50; color: white; }238                .cancel { background-color: #f44336; color: white; }239            </style>240        </head>241        <body>242            <div class="container">243                <h1>Edit Entry</h1>244                <form action="/admin/edit/${category}/${id}" method="POST">245                    <label for="name">Name:</label>246                    <input type="text" id="name" name="name" value="${escapeHtml(entry.name)}" required>247                    248                    <label for="votes">Votes:</label>249                    <input type="number" id="votes" name="votes" value="${entry.votes}" min="0" required>250                    251                    <div class="buttons">252                        <button type="submit" class="save">Save Changes</button>253                        <a href="/admin/dashboard"><button type="button" class="cancel">Cancel</button></a>254                    </div>255                </form>256            </div>257        </body>258        </html>259    `);260});261 262// Update Entry263app.post('/admin/edit/:category/:id', authenticate, (req, res) => {264    const { category, id } = req.params;265    const { name, votes } = req.body;266    const data = readJson(DATA_FILE, {});267    268    if (!CATEGORIES.includes(category)) {269        return res.status(400).send('Invalid category');270    }271    272    const entries = data[category] || [];273    const entryIndex = entries.findIndex(e => e.id === id);274    275    if (entryIndex === -1) {276        return res.status(404).send('Entry not found');277    }278    279    // Update entry280    entries[entryIndex].name = name.trim();281    entries[entryIndex].votes = parseInt(votes, 10);282    283    // Save data284    writeJson(DATA_FILE, data);285    console.log(`Updated entry: ${category}/${id}`);286    287    res.redirect('/admin/dashboard');288});289 290// Delete Entry291app.post('/admin/delete/:category/:id', authenticate, (req, res) => {292    const { category, id } = req.params;293    const data = readJson(DATA_FILE, {});294    295    if (!CATEGORIES.includes(category)) {296        return res.status(400).send('Invalid category');297    }298    299    const entries = data[category] || [];300    const entryIndex = entries.findIndex(e => e.id === id);301    302    if (entryIndex === -1) {303        return res.status(404).send('Entry not found');304    }305    306    // Remove entry307    entries.splice(entryIndex, 1);308    309    // Save data310    writeJson(DATA_FILE, data);311    console.log(`Deleted entry: ${category}/${id}`);312    313    // Also clean up any IP votes for this entry314    const ips = readJson(IP_FILE, {});315    let ipChanged = false;316    317    // Check each IP entry318    Object.keys(ips).forEach(ipKey => {319        if (typeof ips[ipKey] === 'object' && ips[ipKey][category] === id) {320            delete ips[ipKey][category];321            ipChanged = true;322        }323    });324    325    if (ipChanged) {326        writeJson(IP_FILE, ips);327        console.log('Updated IP tracking file after entry deletion');328    }329    330    res.redirect('/admin/dashboard');331});332 333// Archives Dashboard334app.get('/admin/archives', authenticate, (req, res) => {335    const archivedWeeks = archiver.getArchivedWeeks();336    337    let archivesHtml = '';338    339    if (archivedWeeks.length === 0) {340        archivesHtml = '<p>No archived data available yet.</p>';341    } else {342        let tableRows = archivedWeeks.map(weekId => {343            const archive = archiver.getArchivedWeek(weekId);344            if (!archive) return '';345            346            return `347                <tr>348                    <td>${escapeHtml(archive.weekId)}</td>349                    <td>${escapeHtml(archive.startDate)}</td>350                    <td>${escapeHtml(archive.endDate)}</td>351                    <td>${new Date(archive.archivedAt).toLocaleString()}</td>352                    <td>353                        <a href="/admin/archives/week/${archive.weekId}" class="btn btn-edit">View</a>354                    </td>355                </tr>356            `;357        }).join('');358        359        archivesHtml = `360            <h2>Archived Leaderboards</h2>361            <div class="archive-search">362                <h3>Search Archives by Date Range</h3>363                <form action="/admin/archives/search" method="GET">364                    <div class="form-group">365                        <label for="startDate">Start Date:</label>366                        <input type="date" id="startDate" name="startDate" required>367                    </div>368                    <div class="form-group">369                        <label for="endDate">End Date:</label>370                        <input type="date" id="endDate" name="endDate" required>371                    </div>372                    <button type="submit" class="btn btn-edit">Search</button>373                </form>374            </div>375            376            <h3>All Archived Weeks</h3>377            <table>378                <thead>379                    <tr>380                        <th>Week ID</th>381                        <th>Start Date</th>382                        <th>End Date</th>383                        <th>Archived At</th>384                        <th>Actions</th>385                    </tr>386                </thead>387                <tbody>388                    ${tableRows}389                </tbody>390            </table>391            392            <div class="archive-actions">393                <form action="/admin/archives/create" method="POST" onsubmit="return confirm('Are you sure you want to archive the current leaderboard data?')">394                    <button type="submit" class="btn btn-edit">Archive Current Week</button>395                </form>396            </div>397        `;398    }399    400    res.send(`401        <!DOCTYPE html>402        <html>403        <head>404            <title>Archived Leaderboards</title>405            <style>406                body { font-family: Arial, sans-serif; margin: 0; padding: 20px; line-height: 1.6; }407                .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }408                h1 { color: #333; margin: 0; }409                h2 { color: #333; margin-top: 30px; }410                h3 { color: #555; margin-top: 20px; }411                table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }412                table, th, td { border: 1px solid #ddd; }413                th { background-color: #f2f2f2; padding: 10px; text-align: left; }414                td { padding: 10px; }415                .btn { display: inline-block; padding: 5px 10px; margin-right: 5px; text-decoration: none; border-radius: 3px; color: white; border: none; cursor: pointer; }416                .btn-edit { background-color: #2196F3; }417                .btn-delete { background-color: #f44336; }418                .logout { text-decoration: none; color: #f44336; }419                .nav-links { display: flex; gap: 15px; align-items: center; }420                .nav-link { text-decoration: none; color: #2196F3; }421                .archive-search { margin: 20px 0; padding: 15px; background-color: #f9f9f9; border-radius: 5px; }422                .form-group { margin-bottom: 15px; }423                .form-group label { display: block; margin-bottom: 5px; }424                .form-group input { padding: 8px; width: 200px; }425                .archive-actions { margin-top: 20px; }426            </style>427        </head>428        <body>429            <div class="header">430                <h1>Archived Leaderboards</h1>431                <div class="nav-links">432                    <a href="/admin/dashboard" class="nav-link">Back to Dashboard</a>433                    <a href="/admin/logout" class="logout">Logout</a>434                </div>435            </div>436            437            ${archivesHtml}438        </body>439        </html>440    `);441});442 443// View specific archived week444app.get('/admin/archives/week/:weekId', authenticate, (req, res) => {445    const { weekId } = req.params;446    const archive = archiver.getArchivedWeek(weekId);447    448    if (!archive) {449        return res.status(404).send('Archive not found');450    }451    452    let categoriesHtml = '';453    454    CATEGORIES.forEach(category => {455        const entries = archive.data[category] || [];456        const sortedEntries = [...entries].sort((a, b) => b.votes - a.votes);457        458        let tableRows = sortedEntries.map((entry) => `459            <tr>460                <td>${escapeHtml(entry.id)}</td>461                <td>${escapeHtml(entry.name)}</td>462                <td>${entry.votes}</td>463            </tr>464        `).join('');465        466        categoriesHtml += `467            <div class="category-section">468                <h2>${category} Category</h2>469                ${sortedEntries.length > 0 ? `470                <table>471                    <thead>472                        <tr>473                            <th>ID</th>474                            <th>Name</th>475                            <th>Votes</th>476                        </tr>477                    </thead>478                    <tbody>479                        ${tableRows}480                    </tbody>481                </table>482                ` : '<p>No entries in this category.</p>'}483            </div>484        `;485    });486    487    res.send(`488        <!DOCTYPE html>489        <html>490        <head>491            <title>Archived Week: ${weekId}</title>492            <style>493                body { font-family: Arial, sans-serif; margin: 0; padding: 20px; line-height: 1.6; }494                .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }495                h1 { color: #333; margin: 0; }496                h2 { color: #333; margin-top: 30px; }497                table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }498                table, th, td { border: 1px solid #ddd; }499                th { background-color: #f2f2f2; padding: 10px; text-align: left; }500                td { padding: 10px; }501                .category-section { margin-bottom: 30px; }502                .btn { display: inline-block; padding: 5px 10px; margin-right: 5px; text-decoration: none; border-radius: 3px; color: white; border: none; cursor: pointer; }503                .btn-edit { background-color: #2196F3; }504                .nav-links { display: flex; gap: 15px; align-items: center; }505                .nav-link { text-decoration: none; color: #2196F3; }506                .archive-meta { background-color: #f9f9f9; padding: 15px; border-radius: 5px; margin-bottom: 20px; }507                .archive-meta p { margin: 5px 0; }508            </style>509        </head>510        <body>511            <div class="header">512                <h1>Archived Week: ${weekId}</h1>513                <div class="nav-links">514                    <a href="/admin/archives" class="nav-link">Back to Archives</a>515                    <a href="/admin/dashboard" class="nav-link">Back to Dashboard</a>516                    <a href="/admin/logout" class="logout">Logout</a>517                </div>518            </div>519            520            <div class="archive-meta">521                <p><strong>Week ID:</strong> ${archive.weekId}</p>522                <p><strong>Start Date:</strong> ${archive.startDate}</p>523                <p><strong>End Date:</strong> ${archive.endDate}</p>524                <p><strong>Archived At:</strong> ${new Date(archive.archivedAt).toLocaleString()}</p>525            </div>526            527            ${categoriesHtml}528        </body>529        </html>530    `);531});532 533// Search archives by date range534app.get('/admin/archives/search', authenticate, (req, res) => {535    const { startDate, endDate } = req.query;536    537    if (!startDate || !endDate) {538        return res.redirect('/admin/archives');539    }540    541    try {542        const archives = archiver.getArchivedRange(startDate, endDate);543        544        let resultsHtml = '';545        546        if (archives.length === 0) {547            resultsHtml = '<p>No archives found for the specified date range.</p>';548        } else {549            let tableRows = archives.map(archive => `550                <tr>551                    <td>${escapeHtml(archive.weekId)}</td>552                    <td>${escapeHtml(archive.startDate)}</td>553                    <td>${escapeHtml(archive.endDate)}</td>554                    <td>${new Date(archive.archivedAt).toLocaleString()}</td>555                    <td>556                        <a href="/admin/archives/week/${archive.weekId}" class="btn btn-edit">View</a>557                    </td>558                </tr>559            `).join('');560            561            resultsHtml = `562                <h3>Search Results</h3>563                <p>Found ${archives.length} archive(s) between ${startDate} and ${endDate}</p>564                <table>565                    <thead>566                        <tr>567                            <th>Week ID</th>568                            <th>Start Date</th>569                            <th>End Date</th>570                            <th>Archived At</th>571                            <th>Actions</th>572                        </tr>573                    </thead>574                    <tbody>575                        ${tableRows}576                    </tbody>577                </table>578            `;579        }580        581        res.send(`582            <!DOCTYPE html>583            <html>584            <head>585                <title>Archive Search Results</title>586                <style>587                    body { font-family: Arial, sans-serif; margin: 0; padding: 20px; line-height: 1.6; }588                    .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }589                    h1 { color: #333; margin: 0; }590                    h3 { color: #555; margin-top: 20px; }591                    table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }592                    table, th, td { border: 1px solid #ddd; }593                    th { background-color: #f2f2f2; padding: 10px; text-align: left; }594                    td { padding: 10px; }595                    .btn { display: inline-block; padding: 5px 10px; margin-right: 5px; text-decoration: none; border-radius: 3px; color: white; border: none; cursor: pointer; }596                    .btn-edit { background-color: #2196F3; }597                    .nav-links { display: flex; gap: 15px; align-items: center; }598                    .nav-link { text-decoration: none; color: #2196F3; }599                    .search-form { margin: 20px 0; padding: 15px; background-color: #f9f9f9; border-radius: 5px; }600                    .form-group { margin-bottom: 15px; }601                    .form-group label { display: block; margin-bottom: 5px; }602                    .form-group input { padding: 8px; width: 200px; }603                </style>604            </head>605            <body>606                <div class="header">607                    <h1>Archive Search Results</h1>608                    <div class="nav-links">609                        <a href="/admin/archives" class="nav-link">Back to Archives</a>610                        <a href="/admin/dashboard" class="nav-link">Back to Dashboard</a>611                        <a href="/admin/logout" class="logout">Logout</a>612                    </div>613                </div>614                615                <div class="search-form">616                    <h3>Search Archives by Date Range</h3>617                    <form action="/admin/archives/search" method="GET">618                        <div class="form-group">619                            <label for="startDate">Start Date:</label>620                            <input type="date" id="startDate" name="startDate" value="${startDate}" required>621                        </div>622                        <div class="form-group">623                            <label for="endDate">End Date:</label>624                            <input type="date" id="endDate" name="endDate" value="${endDate}" required>625                        </div>626                        <button type="submit" class="btn btn-edit">Search</button>627                    </form>628                </div>629                630                ${resultsHtml}631            </body>632            </html>633        `);634    } catch (error) {635        console.error('Error searching archives:', error);636        res.redirect('/admin/archives?error=1');637    }638});639 640// Manually create an archive641app.post('/admin/archives/create', authenticate, (req, res) => {642    try {643        const weekId = archiver.archiveCurrentWeek();644        // Reset votes after archiving645        archiver.resetLeaderboard();646        res.redirect(`/admin/archives/week/${weekId}`);647    } catch (error) {648        console.error('Error creating archive:', error);649        res.redirect('/admin/archives?error=1');650    }651});652 653// Helper function to escape HTML (prevent XSS)654function escapeHtml(unsafe) {655    if (typeof unsafe !== 'string') return '';656    return unsafe657        .replace(/&/g, "&amp;")658        .replace(/</g, "&lt;")659        .replace(/>/g, "&gt;")660        .replace(/"/g, "&quot;")661        .replace(/'/g, "&#039;");662}663 664// Start Server665app.listen(PORT, () => {666    console.log(`Admin server listening on http://localhost:${PORT}`);667});