const express = require("express"); const fs = require("fs"); const path = require("path"); const morgan = require("morgan"); const bodyParser = require("body-parser"); const sqlite3 = require("sqlite3").verbose(); const app = express(); const port = 3000; // Create an SQLite database and initialize tables const db = new sqlite3.Database("db/results.db", (err) => { if (err) { console.error("Error opening SQLite database:", err.message); } else { console.log("Connected to SQLite database"); db.run(` CREATE TABLE IF NOT EXISTS button_clicks ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT, animal_name TEXT, button_name TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, time_difference INTEGER -- Add this column for time difference ) `); db.run(` CREATE TABLE IF NOT EXISTS animals ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL ) `); } }); var accessLogStream = fs.createWriteStream( path.join(__dirname, "log", "access.log"), { flags: "a" }, ); app.use(bodyParser.json()); app.use(morgan("combined", { stream: accessLogStream })); var animals; // check and load animals into redis try { fs.readFile("./animals.json", function (err, data) { if (err) { throw err; } var jsondata = JSON.parse(data); animals = jsondata.animals; for (const animal of animals) { db.run( ` INSERT INTO animals(name) SELECT '${animal}' WHERE NOT EXISTS(SELECT 1 FROM animals WHERE name = '${animal}'); `, (err) => { if (err) { console.error(`Error inserting animal ${animal}: `, err.message); } else { console.log(`Success inserting animal ${animal}`); } }, ); } }); } catch (error) { console.error("Error loading animals:", error); animals = ["Dog", "Cat", "Elephant", "Lion", "Giraffe"]; } // Serve the HTML file app.get("/", (req, res) => { res.sendFile(__dirname + "/index.html"); }); app.get("/asset/frontend.js", (req, res) => { res.sendFile(__dirname + "/asset/frontend.js"); }); // Route to get a random animal name app.get("/getNextAnimal", async (req, res) => { try { // TODO this is currently random, and should have a bit of reasoning behind the next choice const randomIndex = Math.floor(Math.random() * animals.length); const randomAnimal = animals[randomIndex]; res.json({ animalName: randomAnimal }); } catch (error) { console.error("Error fetching random animal:", error); res.status(500).json({ error: "Internal server error" }); } }); // Route to record button clicks along with session IDs in SQLite app.post("/recordButtonClick", (req, res) => { try { //const { buttonName, sessionId } = req.body; const result = req.body; console.error(result); db.run( "INSERT INTO button_clicks (session_id, animal_name, button_name, timestamp, time_difference) VALUES (?, ?, ?, ?, ?)", [ result.session, result.animal, result.button, result.time, result.difference, ], (err) => { if (err) { console.error("Error recording button click:", err.message); res.status(500).json({ error: "Internal server error" }); } else { res.sendStatus(200); } }, ); } catch (error) { console.error("Error recording button click:", error); res.status(500).json({ error: "Internal server error" }); } }); // Route to show the current results from SQLite app.get("/results", async (req, res) => { try { const results = { count: {}, avgTimes: {}, }; const getCount = new Promise((resolve, reject) => { db.all( ` SELECT animal_name, button_name, COUNT(*) AS count FROM button_clicks GROUP BY button_name, animal_name `, (err, rows) => { if (err) { reject("getCount: " + err.message); } else { rows.forEach((row) => { if (typeof results.count[row.animal_name] == "undefined") { results.count[row.animal_name] = {}; } results.count[row.animal_name][row.button_name] = row.count; }); resolve(); } }, ); }); const getAvgTime = new Promise((resolve, reject) => { db.all( ` SELECT animal_name, button_name, AVG(time_difference) AS time FROM button_clicks GROUP BY animal_name, button_name; `, (err, rows) => { if (err) { reject("getAvgTime: " + err.message); } else { rows.forEach((row) => { if (typeof results.avgTimes[row.animal_name] == "undefined") { results.avgTimes[row.animal_name] = {}; } results.avgTimes[row.animal_name][row.button_name] = row.time; }); resolve(); } }, ); }); const getTotalAvgTime = new Promise((resolve, reject) => { db.all( ` SELECT animal_name, AVG(time_difference) AS time FROM button_clicks GROUP BY animal_name; `, (err, rows) => { if (err) { reject("getTotalAvgTime: " + err.message); } else { rows.forEach((row) => { if (typeof results.avgTimes[row.animal_name] == "undefined") { results.avgTimes[row.animal_name] = {}; } results.avgTimes[row.animal_name]["total"] = row.time; }); resolve(); } }, ); }); await Promise.all([getCount, getTotalAvgTime, getAvgTime]); res.json(results); } catch (error) { console.error("Error fetching results:", error); res.status(500).json({ error: "Internal server error" }); } }); app.listen(port, () => { console.log(`Server is running on port ${port}`); });