LLMChoice / server.js
Peacemanguy's picture
First commit
fe02ff1
// server.js — one‑vote‑per‑IP edition
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const path = require('path');
const requestIp = require('request-ip'); // NEW
const crypto = require('crypto'); // we'll hash IPs before saving
const archiver = require('./leaderboard_archiver');
const https = require('https'); // For Hugging Face API requests
const PORT = process.env.PORT || 3000;
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"];
function validateCategory(cat) {
return CATEGORIES.includes(cat);
}
const app = express();
app.use(bodyParser.json());
app.use(requestIp.mw()); // adds req.clientIp
app.use(express.static(path.join(__dirname, 'public')));
/* ---------- tiny helpers ---------- */
function readJson(file, fallback) {
try { return JSON.parse(fs.readFileSync(file)); }
catch { return fallback; }
}
function writeJson(file, obj) {
fs.writeFileSync(file, JSON.stringify(obj, null, 2));
}
function hash(ip) { // do not store raw IP
return crypto.createHash('sha256').update(ip).digest('hex');
}
/* ---------- IP‑limit middleware ---------- */
function oneVotePerIP(req, res, next) {
const ipList = readJson(IP_FILE, {});
const key = hash(req.clientIp || 'unknown');
if (ipList[key]) return res.status(409)
.json({ error: 'You have already voted from this IP' });
req._ipKey = key; // remember for later
next();
}
/* ---------- Ensure IP tracking is properly formatted ---------- */
function ensureValidIpTracking() {
const ips = readJson(IP_FILE, {});
let changed = false;
// Convert any string values to objects
Object.keys(ips).forEach(key => {
if (typeof ips[key] === 'string') {
ips[key] = {};
changed = true;
}
});
if (changed) {
writeJson(IP_FILE, ips);
}
return ips;
}
/* ---------- API ---------- */
app.get('/api/entries', (req, res) => {
const category = req.query.category;
const data = readJson(DATA_FILE, {});
if (!validateCategory(category)) {
return res.status(400).json({ error: 'Invalid category' });
}
const entries = (data[category] || []).sort((a, b) => b.votes - a.votes);
res.json(entries);
});
/* Add new entry + cast initial vote */
app.post('/api/add', (req, res) => {
const name = (req.body.name || '').trim();
const category = req.body.category;
if (!name) return res.status(400).json({ error: 'Name required' });
if (!validateCategory(category)) return res.status(400).json({ error: 'Invalid category' });
const data = readJson(DATA_FILE, {});
const list = data[category] = data[category] || [];
if (list.find(e => e.name.toLowerCase() === name.toLowerCase()))
return res.status(400).json({ error: 'Entry already exists' });
const ips = ensureValidIpTracking();
const ipKey = hash(req.clientIp || 'unknown');
if (!ips[ipKey] || typeof ips[ipKey] !== 'object') ips[ipKey] = {};
const prevVotedId = ips[ipKey][category];
// If user has already voted for another entry, decrement its votes
if (prevVotedId) {
const prevItem = list.find(e => e.id === prevVotedId);
if (prevItem && prevItem.votes > 0) prevItem.votes -= 1;
}
// Add new entry with 1 vote
const entry = { id: Date.now().toString(), name, votes: 1 };
list.push(entry);
writeJson(DATA_FILE, data);
// Update IP record to new entry id for this category
ips[ipKey][category] = entry.id;
writeJson(IP_FILE, ips);
res.json(entry);
});
/* Vote for existing entry */
app.post('/api/vote', (req, res) => {
const { id, category } = req.body;
if (!validateCategory(category)) return res.status(400).json({ error: 'Invalid category' });
const data = readJson(DATA_FILE, {});
const list = data[category] = data[category] || [];
const item = list.find(e => e.id === id);
if (!item) return res.status(404).json({ error: 'Entry not found' });
const ips = ensureValidIpTracking();
const ipKey = hash(req.clientIp || 'unknown');
if (!ips[ipKey] || typeof ips[ipKey] !== 'object') ips[ipKey] = {};
const prevVotedId = ips[ipKey][category];
if (prevVotedId === id) {
// Already voted for this option
return res.status(409).json({ error: 'You have already voted for this option' });
}
// If user has voted for a different option, decrement that vote
if (prevVotedId) {
const prevItem = list.find(e => e.id === prevVotedId);
if (prevItem && prevItem.votes > 0) prevItem.votes -= 1;
}
// Increment vote for the new option
item.votes += 1;
writeJson(DATA_FILE, data);
// Update IP record to new voted id for this category
ips[ipKey][category] = id;
writeJson(IP_FILE, ips);
res.json(item);
});
/* ---------- Archive API ---------- */
// Get list of archived weeks
app.get('/api/archives/weeks', (req, res) => {
try {
const weeks = archiver.getArchivedWeeks();
res.json(weeks);
} catch (error) {
console.error('Error getting archived weeks:', error);
res.status(500).json({ error: 'Failed to retrieve archived weeks' });
}
});
// Get archived data for a specific week
app.get('/api/archives/week/:weekId', (req, res) => {
try {
const { weekId } = req.params;
const archive = archiver.getArchivedWeek(weekId);
if (!archive) {
return res.status(404).json({ error: 'Archive not found for the specified week' });
}
res.json(archive);
} catch (error) {
console.error('Error getting archived week:', error);
res.status(500).json({ error: 'Failed to retrieve archived data' });
}
});
// Get archived data for a specific week and category
app.get('/api/archives/week/:weekId/category/:category', (req, res) => {
try {
const { weekId, category } = req.params;
const archive = archiver.getArchivedWeek(weekId);
if (!archive) {
return res.status(404).json({ error: 'Archive not found for the specified week' });
}
if (!validateCategory(category)) {
return res.status(400).json({ error: 'Invalid category' });
}
const entries = (archive.data[category] || []).sort((a, b) => b.votes - a.votes);
res.json(entries);
} catch (error) {
console.error('Error getting archived category:', error);
res.status(500).json({ error: 'Failed to retrieve archived data' });
}
});
// Get archived data for a date range
app.get('/api/archives/range', (req, res) => {
try {
const { startDate, endDate } = req.query;
if (!startDate || !endDate) {
return res.status(400).json({ error: 'Both startDate and endDate are required' });
}
const archives = archiver.getArchivedRange(startDate, endDate);
res.json(archives);
} catch (error) {
console.error('Error getting archived range:', error);
res.status(500).json({ error: 'Failed to retrieve archived data for the specified range' });
}
});
/* ---------- Hugging Face API Proxy ---------- */
app.get('/api/huggingface/models', (req, res) => {
const query = req.query.query;
if (!query || query.length < 2) {
return res.status(400).json({ error: 'Query must be at least 2 characters' });
}
const options = {
hostname: 'huggingface.co',
path: `/api/models?search=${encodeURIComponent(query)}`,
method: 'GET',
headers: {
'Accept': 'application/json'
}
};
const hfRequest = https.request(options, (hfResponse) => {
let data = '';
hfResponse.on('data', (chunk) => {
data += chunk;
});
hfResponse.on('end', () => {
try {
const parsedData = JSON.parse(data);
// Format the response to include only necessary information
const formattedResults = parsedData.map(model => ({
id: model.id,
modelId: model.modelId,
name: model.name || model.id,
author: model.author?.name || 'Unknown',
downloads: model.downloads || 0,
likes: model.likes || 0
})).slice(0, 10); // Limit to 10 results
res.json(formattedResults);
} catch (error) {
console.error('Error parsing Hugging Face API response:', error);
res.status(500).json({ error: 'Failed to parse Hugging Face API response' });
}
});
});
hfRequest.on('error', (error) => {
console.error('Error fetching from Hugging Face API:', error);
res.status(500).json({ error: 'Failed to fetch from Hugging Face API' });
});
hfRequest.end();
});
/* ---------- start ---------- */
app.listen(PORT, () => console.log('Leaderboard running on', PORT));