Spaces:
Configuration error
Configuration error
File size: 5,221 Bytes
bb5c88a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | // BOU 2026 - Best Project voting app
// Serves the voting page and logs every vote (Name, UID, Email, Project, Timestamp)
// into an Excel file on disk, one row per voter (re-voting updates their row).
const path = require("path");
const fs = require("fs");
const express = require("express");
const ExcelJS = require("exceljs");
const PORT = process.env.PORT || 3000;
const ADMIN_KEY = process.env.ADMIN_KEY || "change-me"; // set a real secret before deploying
const VOTES_FILE = path.join(__dirname, "data", "votes.xlsx");
const PROJECTS = [
{ id: "1", name: "Global Account – DAS (Adecco)", who: "Sathiaseelan Arumugam" },
{ id: "2", name: "PCB & IC Package Team", who: "Syam" },
{ id: "3", name: "Product Engineering / Embedded", who: "Munajat Abdul Hadi" },
{ id: "4", name: "CIS", who: "Shunmuganainar Chockalingam" },
{ id: "5", name: "ATE", who: "Kother" },
{ id: "6", name: "Manufacturing POD", who: "Rohan Chowdhary" },
{ id: "7", name: "Manufacturing", who: "Hasrul Hasni" },
];
const PROJECT_IDS = new Set(PROJECTS.map((p) => p.id));
const HEADERS = ["Name", "UID", "Email", "Project ID", "Project Name", "Timestamp"];
// --- tiny write queue so concurrent votes never corrupt the Excel file ---
let writeQueue = Promise.resolve();
function withWriteLock(fn) {
const result = writeQueue.then(fn, fn);
writeQueue = result.catch(() => {});
return result;
}
async function loadWorkbook() {
const wb = new ExcelJS.Workbook();
if (fs.existsSync(VOTES_FILE)) {
await wb.xlsx.readFile(VOTES_FILE);
} else {
fs.mkdirSync(path.dirname(VOTES_FILE), { recursive: true });
const ws = wb.addWorksheet("Votes");
ws.addRow(HEADERS);
ws.getRow(1).font = { bold: true };
}
const ws = wb.getWorksheet("Votes") || wb.addWorksheet("Votes");
if (ws.rowCount === 0) {
ws.addRow(HEADERS);
ws.getRow(1).font = { bold: true };
}
return { wb, ws };
}
async function recordVote({ name, uid, email, projectId }) {
return withWriteLock(async () => {
const { wb, ws } = await loadWorkbook();
const project = PROJECTS.find((p) => p.id === projectId);
// find an existing row for this UID (column 2) so re-voting updates it
let existingRow = null;
ws.eachRow((row, rowNumber) => {
if (rowNumber === 1) return;
if (String(row.getCell(2).value) === String(uid)) existingRow = row;
});
const values = [name, uid, email, project.id, project.name, new Date().toISOString()];
if (existingRow) {
existingRow.values = [, ...values]; // keep 1-based column alignment
} else {
ws.addRow(values);
}
await wb.xlsx.writeFile(VOTES_FILE);
});
}
async function tallyResults() {
const { ws } = await loadWorkbook();
const counts = {};
let total = 0;
const voters = [];
ws.eachRow((row, rowNumber) => {
if (rowNumber === 1) return;
const projectId = String(row.getCell(4).value || "");
if (!projectId) return;
counts[projectId] = (counts[projectId] || 0) + 1;
total += 1;
voters.push({
name: row.getCell(1).value,
uid: row.getCell(2).value,
email: row.getCell(3).value,
projectId,
});
});
return { counts, total, voters };
}
// --- app ---
const app = express();
app.use(express.json());
app.use(express.static(path.join(__dirname, "public")));
app.get("/api/projects", (req, res) => res.json(PROJECTS));
app.post("/api/vote", async (req, res) => {
const { name, uid, email, projectId } = req.body || {};
if (!name || !uid || !email || !projectId) {
return res.status(400).json({ ok: false, error: "Name, UID, email and a project choice are all required." });
}
if (!/^\S+@\S+\.\S+$/.test(email)) {
return res.status(400).json({ ok: false, error: "That email address doesn't look valid." });
}
if (!PROJECT_IDS.has(String(projectId))) {
return res.status(400).json({ ok: false, error: "Unknown project." });
}
try {
await recordVote({ name: String(name).trim(), uid: String(uid).trim(), email: String(email).trim(), projectId: String(projectId) });
res.json({ ok: true });
} catch (err) {
console.error(err);
res.status(500).json({ ok: false, error: "Could not save your vote. Please try again." });
}
});
function requireAdmin(req, res, next) {
const key = req.query.key || req.headers["x-admin-key"];
if (key !== ADMIN_KEY) return res.status(401).json({ ok: false, error: "Invalid admin key." });
next();
}
app.get("/api/results", requireAdmin, async (req, res) => {
try {
const { counts, total } = await tallyResults();
res.json({ ok: true, projects: PROJECTS, counts, total });
} catch (err) {
console.error(err);
res.status(500).json({ ok: false, error: "Could not load results." });
}
});
app.get("/api/export", requireAdmin, async (req, res) => {
if (!fs.existsSync(VOTES_FILE)) {
return res.status(404).json({ ok: false, error: "No votes recorded yet." });
}
res.download(VOTES_FILE, "bou2026-votes.xlsx");
});
app.listen(PORT, () => {
console.log(`BOU 2026 voting app listening on port ${PORT}`);
if (ADMIN_KEY === "change-me") {
console.warn("WARNING: set a real ADMIN_KEY environment variable before sharing this app.");
}
});
|