diff --git a/index.js b/index.js index 0e825f6..f1483bf 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,9 @@ const colors = require("colors"); require("dotenv").config(); const fs = require("fs"); const express = require("express"); +const expressWs = require("express-ws"); const app = express(); +const ws = expressWs(app); const port = process.env.PORT || 3000; const { exec } = require("child_process"); const sqlite3 = require("sqlite3").verbose(); @@ -10,12 +12,19 @@ const db = new sqlite3.Database("./database.db"); const ttsCommands = require("./tts.json") // Find all migrations .sql files in ./migrations, execute them in order db.on("open", () => { + db.on("error", (err) => { + console.log(`${colors.red("[DB]")} ${err}`); + }); console.log(`${colors.cyan("[DB]")} Connected to the database`); fs.readdirSync("./migrations").forEach((file) => { if (file.endsWith(".sql")) { const migration = fs.readFileSync(`./migrations/${file}`, "utf8"); - db.run(migration); + try { + db.run(migration) console.log(`${colors.cyan("[DB]")} ${file} ${colors.green("executed")}`); + } catch (error) { + console.log(`${colors.red("[DB]")} ${file} ${colors.red("failed")}`); + } } }); }); @@ -43,6 +52,8 @@ app.use((req, res, next) => { next(); }); + + // Vars var handledTransactions = []; @@ -722,5 +733,95 @@ app.post("/api/v1/tts", (req, res) => { }); }); +app.ws("/api/v1/websocket/:accountNumber", (ws, req) => { + var userData; + // Check if account number is valid and is verified + db.get("SELECT * FROM accounts WHERE id = ? AND verified = 1", req.params.accountNumber, (err, row) => { + if (err) { + console.error(err); + ws.send(JSON.stringify({status: "error", code: 500, message: "Internal Server Error", timestamp: new Date().toISOString()})); + ws.close(); + return; + } else if (row) { + userData = row; + ws.send(JSON.stringify({status: "connected", code: 200, timestamp: new Date().toISOString()})); + } else { + ws.send(JSON.stringify({status: "error", code: 404, message: "Account not found or not verified", timestamp: new Date().toISOString()})); + ws.close(); + return; + } + }); + + ws.on("message", (msg) => { + // Validate json + try { + msg = JSON.parse(msg); + } catch (error) { + console.error(error); + ws.send(JSON.stringify({status: "error", message: "Invalid JSON", timestamp: new Date().toISOString()})); + return; + } + switch(msg.action) { + case "close": + ws.send(JSON.stringify({response: "closed", timestamp: new Date().toISOString()})); + ws.close(); + break; + case "ping": + ws.send(JSON.stringify({response: "pong", timestamp: new Date().toISOString()})); + break; + case "getStatus": + // Get the armState column from the account database + db.get("SELECT armState FROM accounts WHERE id = ?", req.params.accountNumber, (err, row) => { + if (err) { + console.error(err); + ws.send(JSON.stringify({action: msg.action, status: "error", code: 500, message: "Internal Server Error", timestamp: new Date().toISOString()})); + } else { + ws.send(JSON.stringify({action: msg.action, status: "success", code: 200, armState: row.armState, timestamp: new Date().toISOString()})); + } + }); + break; + case "report": + console.log(msg) + // Check length of inputs, if any are over 500 characters, return 400 + if (msg.data.placeName.length > (process.env.MAX_LENGTH || 500) || msg.data.systemName.length > (process.env.MAX_LENGTH || 500) || msg.data.zoneName.length > (process.env.MAX_LENGTH || 500) || msg.data.event.length > (process.env.MAX_LENGTH || 500)) { + console.log(`${colors.red("[ERROR]")} Input too long.`); + console.log(`${colors.red("[ERROR]")} PlaceName: ${msg.data.placeName.length} SystemName: ${msg.data.systemName.length} ZoneName: ${msg.data.zoneName.length} EventName: ${msg.data.event.length}`); + ws.send(JSON.stringify({ action: msg.action, status: "error", code: 400, message: "Input too long", timestamp: new Date().toISOString() })); + return; + } + + // Check cooldown + if (new Date(userData.cooldown) > new Date()) { + console.log("Cooldown") + ws.send(JSON.stringify({ action: msg.action, status: "error", code: 429, message: "Cooldown", timestamp: new Date().toISOString() })); + return; + } else { + console.log("Not cooldown") + newCooldown = new Date(); + newCooldown.setMinutes(newCooldown.getMinutes() + 5); + } + + sendAlert(req.params.accountNumber, generateTransactionNumber(), msg.data.placeName, msg.data.systemName, msg.data.zoneNumber, msg.data.zoneName, msg.data.event).then(() => { + ws.send(JSON.stringify({ action: msg.action, status: "success", code: 200, timestamp: new Date().toISOString() })); + }).catch((error) => { + ws.send(JSON.stringify({ action: msg.action, status: "error", code: 500, message: error, timestamp: new Date().toISOString() })); + }); + break; + case "updateState": + // Update the armState column in the account database + db.run("UPDATE accounts SET armState = ? WHERE id = ?", msg.armState, req.params.accountNumber, (err) => { + if (err) { + console.error(err); + ws.send(JSON.stringify({action: msg.action, status: "error", code: 500, message: "Internal Server Error", timestamp: new Date().toISOString()})); + } else { + ws.send(JSON.stringify({action: msg.action, status: "success", code: 200, timestamp: new Date().toISOString()})); + } + }); + break; + + } + }); +}); + startTime = new Date(); client.login(process.env.DISCORD_TOKEN); \ No newline at end of file diff --git a/migrations/5 - Add ArmState.sql b/migrations/5 - Add ArmState.sql new file mode 100644 index 0000000..19f38a3 --- /dev/null +++ b/migrations/5 - Add ArmState.sql @@ -0,0 +1,10 @@ +-- Add ttsOverride column to the accounts table +--ALTER TABLE 'accounts' ADD COLUMN 'cooldown' TEXT; +-- Step 1: Check if 'cooldown' column exists in 'accounts' table +-- Step 1: Check if 'cooldown' column exists in 'accounts' table +SELECT COUNT(*) +FROM pragma_table_info('accounts') +WHERE name = 'armState'; + +-- Step 2: If the count is 0, add the 'cooldown' column +ALTER TABLE accounts ADD COLUMN armState INTEGER DEFAULT 0; diff --git a/package-lock.json b/package-lock.json index ec4bbc2..af118e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "discord.js": "^14.15.3", "dotenv": "^16.4.5", "express": "^4.19.2", + "express-ws": "^5.0.2", "sqlite3": "^5.1.7" } }, @@ -835,6 +836,40 @@ "node": ">= 0.10.0" } }, + "node_modules/express-ws": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/express-ws/-/express-ws-5.0.2.tgz", + "integrity": "sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==", + "dependencies": { + "ws": "^7.4.6" + }, + "engines": { + "node": ">=4.5.0" + }, + "peerDependencies": { + "express": "^4.0.0 || ^5.0.0-alpha.1" + } + }, + "node_modules/express-ws/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", diff --git a/package.json b/package.json index c24023d..7f8cd8c 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "discord.js": "^14.15.3", "dotenv": "^16.4.5", "express": "^4.19.2", + "express-ws": "^5.0.2", "sqlite3": "^5.1.7" } }