#!/usr/bin/env node const express = require("express"); const http = require("http"); const socketio = require("socket.io"); const fs = require("fs"); const [DBFILE, PORT] = (() => { if (process.argv.length == 3) { return [process.argv[2], 8000]; } else if (process.argv.length == 4) { return [process.argv[2], +process.argv[3]]; } else { console.log("Usage: ./server.js [port]"); process.exit(1); } })(); const app = express(); const httpServer = http.Server(app); const io = socketio(httpServer); const STATIC = __dirname + "/static"; // ------------------------------ // "database" // ------------------------------ class Database { #filename; #list; #lastbackup; constructor(filename) { this.#filename = filename; this.#list = JSON.parse(fs.readFileSync(this.#filename)); this.#lastbackup = new Date(); } add_new(string) { for (let i = 0; i < this.#list.length; i++) { if (this.#list[i][1] == string) { return "already existed"; } } this.#list.push([0, string]); this.#sort(); this.#persist(); return null; } set_votes(string, votes) { for (let i = 0; i < this.#list.length; i++) { if (this.#list[i][1] == string) { this.#list[i][0] = votes; this.#sort(); this.#persist(); return null; } } return `'${string}' does not exist`; } remove(string) { for (let i = 0; i < this.#list.length; i++) { if (this.#list[i][1] == string) { this.#list.splice(i, 1); this.#persist(); return null; } } return "does not exist"; } get() { return this.#list; } #sort() { this.#list.sort((a, b) => { if (a[0] != b[0]) return b[0] - a[0]; if (a[1] < b[1]) return -1; if (a[1] > b[1]) return 1; return 0; }); } #persist() { fs.writeFile(this.#filename, JSON.stringify(this.#list), err => { if (err != null) { console.error("Error writing to file:", err); console.log(JSON.stringify(this.#list)); } }); const now = new Date(); if (now - this.#lastbackup > 60 * 1000) { const dotidx = this.#filename.lastIndexOf("."); const bakfname = dotidx == -1 ? this.#filename + now.getTime() : this.#filename.slice(0, dotidx) + "." + now.getTime() + this.#filename.slice(dotidx); fs.writeFile(bakfname, JSON.stringify(this.#list), err => { if (err != null) { console.error("Error copying to backup file:", err); } else { console.log(`Copied to ${bakfname}`); } }); this.#lastbackup = now; } } }; const database = new Database(DBFILE); // ------------------------------ // Express routes // ------------------------------ app.use(express.static(STATIC)); app.get("/", (req, res) => { res.sendFile(STATIC + "/index.html"); }); // ------------------------------ // socket.io bindings // ------------------------------ let next_socket_id = 1; let num_connected = 0; io.on("connection", socket => { const socket_id = next_socket_id++; if (++num_connected >= 2) { socket.emit("alert", `You're user number ${num_connected}, multi-user settings will mess up!`); } console.log(`new socket connection id=${socket_id} (nc=${num_connected})`); let logged_in = false; socket.on("disconnect", () => { num_connected--; console.log(`disconnect id=${socket_id} (nc=${num_connected})`); }); socket.on("login", pass => { if (pass == fs.readFileSync(__dirname + "/password.txt").toString().trim()) { console.log(`login id=${socket_id}`); logged_in = true; socket.emit("init", database.get()); } else { socket.emit("unauth"); } }); socket.on("new", string => { if (!logged_in) { socket.emit("unauth"); return; } const res = database.add_new(string); if (res != null) socket.emit("error", res); }); socket.on("set_votes", (string, votes) => { if (!logged_in) { socket.emit("unauth"); return; } const res = database.set_votes(string, votes); if (res != null) socket.emit("error", res); }); socket.on("delete", string =>{ if (!logged_in) { socket.emit("unauth"); return; } const res = database.remove(string); if (res != null) socket.emit("error", res); }); }); // ------------------------------ // setup // ------------------------------ const server = httpServer.listen(PORT, () => { let host = server.address().address; if (host == "::") host = "localhost"; const port = server.address().port; console.log("Server listening at http://" + host + ":" + port); });