diff --git a/config/databaseConfig.js b/config/databaseConfig.js new file mode 100644 index 0000000..79fe352 --- /dev/null +++ b/config/databaseConfig.js @@ -0,0 +1,8 @@ +const databaseConfig = { + database_host: '100.20.1.45', + database_user: 'DRB_CNC', + database_password: 'baMbC6IAl$Rn7$h0PS', + database_database: 'DRB_CNC' +} + +module.exports = databaseConfig; \ No newline at end of file diff --git a/config/discordConfig.js b/config/discordConfig.js new file mode 100644 index 0000000..06e4004 --- /dev/null +++ b/config/discordConfig.js @@ -0,0 +1,5 @@ +const discordConfig = { + channelID: '367396189529833476' +} + +module.exports = discordConfig; \ No newline at end of file diff --git a/controllers/adminController.js b/controllers/adminController.js new file mode 100644 index 0000000..f630af4 --- /dev/null +++ b/controllers/adminController.js @@ -0,0 +1,129 @@ +// Config +const discordConfig = require("../config/discordConfig"); +// Debug +const { DebugBuilder } = require("../utilities/debugBuilder.js"); +const log = new DebugBuilder("server", "adminController"); +// Utilities +const mysqlHandler = require("../utilities/mysqlHandler"); +const utils = require("../utilities/utils"); +const requests = require("../utilities/httpRequests"); + +/** Get the presets of all online nodes, can be used for functions + * + * @param callback Callback function + * @returns {*} A list of the systems online + */ +async function getPresetsOfOnlineNodes(callback) { + mysqlHandler.getOnlineNodes((onlineNodes) => { + let systems = {}; + onlineNodes.forEach(onlineNode => { + systems[onlineNode.id] = utils.BufferToJson(onlineNode.nearbySystems); + }); + + callback(systems); + }); +} + +async function requestNodeListenToPreset(preset, nodeId, callback) { + mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{ + reqOptions = new requests.requestOptions("/bot/join", "POST", nodeObject.ip, nodeObject.port); + requests.sendHttpRequest(reqOptions, JSON.stringify({ + "channelID": discordConfig.channelID, + "presetName": preset + }), (responseObject) => { + callback(responseObject) + }); + }) +} + +async function getNodeBotStatus(nodeId, callback) { + mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{ + reqOptions = new requests.requestOptions("/bot/status", "GET", nodeObject.ip, nodeObject.port, undefined, 5); + requests.sendHttpRequest(reqOptions, JSON.stringify({}), (responseObject) => { + if (responseObject === false) { + // Bot is joined + } + else { + // Bot is free + } + callback(responseObject); + }); + }); +} + +async function requestNodeLeaveServer(nodeId, callback) { + getNodeBotStatus(nodeId, (responseObject) => { + if (responseObject === false) { + // Bot is joined + mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{ + reqOptions = new requests.requestOptions("/bot/leave", "POST", nodeObject.ip, nodeObject.port); + requests.sendHttpRequest(reqOptions, JSON.stringify({}), (responseObject) => { + callback(responseObject); + }); + }); + } + else { + // Bot is free + callback(false); + } + }) +} + + +/** Return to requests for the presets of all online nodes, cannot be used in functions + * + * @param {*} req Express request parameter + * @param {*} res Express response parameter + */ +exports.getAvailablePresets = async (req, res) => { + await getPresetsOfOnlineNodes((systems) => { + res.status(200).json({ + "systemsOnline": systems + }); + }) +} + +/** Request a node to join the server listening to a specific preset + * + * @param {*} req Express request parameter + * @var {*} req.body.preset The preset to join (REQ) + * @var {*} req.body.nodeId The specific node to join (OPT/REQ if more than one node has the preset) + * @param {*} res Express response parameter + */ +exports.joinPreset = async (req, res) => { + if (!req.body.preset) return res.status(400).json("No preset specified"); + await getPresetsOfOnlineNodes((systems) => { + const systemsWithSelectedPreset = Object.values(systems).filter(nodePresets => nodePresets.includes(req.body.preset)).length + if (!systemsWithSelectedPreset) return res.status(400).json("No system online with that preset"); + if (systemsWithSelectedPreset > 1) { + if (!req.body.nodeId) return res.status(175).json("Multiple locations with the selected channel, please specify a nodeID (nodeId)") + requestNodeListenToPreset(req.body.preset, req.body.nodeId, (responseObject) => { + if (responseObject === false) return res.status(400).json("Timeout reached"); + return res.sendStatus(responseObject.statusCode); + }); + } + else { + let nodeId; + if (!req.body.nodeId) nodeId = utils.getKeyByArrayValue(systems, req.body.preset); + else nodeId = req.body.nodeId; + requestNodeListenToPreset(req.body.preset, nodeId, (responseObject) => { + if (responseObject === false) return res.status(400).json("Timeout reached"); + return res.sendStatus(responseObject.statusCode); + }); + } + }); +} + +/** Request a node to join the server listening to a specific preset + * + * @param {*} req Express request parameter + * @param {*} res Express response parameter + */ +exports.leaveServer = async (req, res) => { + if (!req.body.nodeId) return res.status(400).json("No nodeID specified"); + + requestNodeLeaveServer(req.body.nodeId, (responseObject) => { + if (responseObject === false) return res.status(400).json("Bot not joined to server"); + return res.sendStatus(responseObject.statusCode); + }); +} \ No newline at end of file diff --git a/controllers/nodesController.js b/controllers/nodesController.js new file mode 100644 index 0000000..7387e07 --- /dev/null +++ b/controllers/nodesController.js @@ -0,0 +1,80 @@ +// Debug +const { DebugBuilder } = require("../utilities/debugBuilder.js"); +const log = new DebugBuilder("server", "nodesController"); +// Utilities +const mysqlHander = require("../utilities/mysqlHandler"); +const utils = require("../utilities/utils"); + +exports.listAllNodes = async (req, res) => { + mysqlHander.getAllNodes((allNodes) => { + res.status(200).json({ + "nodes_online": allNodes + }); + }); +} + +// Add a new node to the +exports.newNode = async (req, res) => { + if (!req.body.name) return res.send(400) + + try { + // Try to add the new user with defaults if missing options + mysqlHander.addNewNode({ + 'name': req.body.name, + 'ip': req.body.ip ?? null, + 'port': req.body.port ?? null, + 'location': req.body.location ?? null, + 'nearbySystems': req.body.nearbySystems ?? null, + 'online': req.body.online ?? 0 + }, (queryResults) => { + // Send back a success if the user has been added and the ID for the client to keep track of + res.status(202).json({"nodeId": queryResults.insertId}); + }) + } + catch (err) { + // Catch any errors + if (err === "No name provided") { + return res.sendStatus(400); + } + else log.ERROR(err) + return res.sendStatus(500); + } +} + +// Get the known info for the node specified +exports.getNodeInfo = async (req, res) => { + if (!req.query.id) return res.status(400).json("No id specified"); + mysqlHander.getNodeInfoFromId(req.query.id, (nodeInfo) => { + res.status(200).json(nodeInfo); + }) +} + +// Updates the information received from the client based on ID +exports.nodeCheckIn = async (req, res) => { + if (!req.body.id) return res.status(400).json("No id specified"); + mysqlHander.getNodeInfoFromId(req.body.id, (nodeInfo) => { + let nodeObject = {}; + // Convert the DB systems buffer to a JSON object to be worked with + nodeInfo.nearbySystems = utils.BufferToJson(nodeInfo.nearbySystems) + // Convert the online status to a boolean to be worked with + nodeInfo.online = nodeInfo.online !== 0; + + if (req.body.name && req.body.name !== nodeInfo.name) nodeObject.name = req.body.name + if (req.body.ip && req.body.ip !== nodeInfo.ip) nodeObject.ip = req.body.ip + if (req.body.port && req.body.port !== nodeInfo.port) nodeObject.port = req.body.port + if (req.body.location && req.body.location !== nodeInfo.location) nodeObject.location = req.body.location + if (req.body.nearbySystems && JSON.stringify(req.body.nearbySystems) !== JSON.stringify(nodeInfo.nearbySystems)) nodeObject.nearbySystems = req.body.nearbySystems + if (req.body.online && req.body.online !== nodeInfo.online) nodeObject.online = req.body.online + + // If no changes are made tell the client + if (Object.keys(nodeObject).length === 0) return res.status(200).json("No keys updated"); + + log.INFO("Updating the following keys for ID: ", req.body.id, nodeObject); + // Adding the ID key to the body so that the client can double-check their ID + nodeObject.id = req.body.id; + mysqlHander.updateNodeInfo(nodeObject, () => { + return res.status(202).json({"updatedKeys": nodeObject}); + }) + }) + +} \ No newline at end of file diff --git a/index.js b/index.js index c284aa9..48d0036 100644 --- a/index.js +++ b/index.js @@ -1,14 +1,22 @@ +var createError = require('http-errors'); +var express = require('express'); +var path = require('path'); +var cookieParser = require('cookie-parser'); +var logger = require('morgan'); +var http = require('http'); + const fs = require('fs'); -const path = require('node:path'); require('dotenv').config(); -prefix = process.env.PREFIX -token = process.env.TOKEN; +const libCore = require("./libCore"); +const libUtils = require("./libUtils"); + +const { DebugBuilder } = require("./utilities/debugBuilder"); +const log = new DebugBuilder("server", "index"); + const { Routes } = require('discord-api-types/v9'); -const { - quotes -} = require('./quotes.json'); + //const Discord = require('discord.js');Client, Collection, Intents const { Client, @@ -18,54 +26,81 @@ const { MessageButton } = require('discord.js'); //const client = new Discord.Client(); + const client = new Client({ intents: [Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILDS] }); -const PORT = process.env.PORT || 3000; -const express = require("express"); -const server = express(); -var libCore = require("./libCore.js"); -let linkFlayerMap = []; +prefix = process.env.PREFIX +discordToken = process.env.TOKEN; -server.all("/", (req, res) => { - var htmlOutput = "LinkFlayer Bot is Ready - Sources loading
"; +var indexRouter = require('./routes/index'); +var nodesRouter = require('./routes/nodes'); +var adminRouter = require('./routes/admin'); - var sources = libCore.getSources(); - sources.forEach(source => { - htmlOutput += ` -
+// HTTP Server Config +var app = express(); -
Title: ${source.title}
-
Link: ${source.link}
-
category: ${source.category}
+// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'ejs'); -
-
-
+app.use(logger('dev')); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(express.static(path.join(__dirname, 'public'))); -
+// Web Interface +app.use('/', indexRouter); - ` - }); - res.send(htmlOutput); +// Nodes API +app.use('/nodes', nodesRouter); +// Admin API +app.use('/admin', adminRouter); + +// catch 404 and forward to error handler +app.use((req, res, next) => { + next(createError(404)); }); -function keepAlive() { - server.listen(PORT, () => { - console.log("Keep Alive Server Running"); +var port = libUtils.normalizePort(process.env.HTTP_PORT || '3000'); +app.set('port', port); + +// error handler +app.use((err, req, res, next) => { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page + res.status(err.status || 500); + res.render('error'); +}); + +/** + * Start the HTTP background server + */ +function runHTTPServer() { + var server = http.createServer(app); + server.listen(port); + + server.on('error', libUtils.onError); + + server.on('listening', () => { + log.INFO("Local HTTP Server Running"); try { - libCore.loadFeeds(); libCore.feedArray = libCore.getFeeds(); } catch (error) { - console.log(error); - } + log.ERROR(error); + } }) } +// Discord bot config - +// Setup commands for the Discord bot client.commands = new Collection(); const commandsPath = path.join(__dirname, 'commands'); const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); @@ -78,8 +113,13 @@ for (const file of commandFiles) { client.commands.set(command.name, command); } -client.on('ready', () => { - console.log(`Logged in as ${client.user.tag}!`); +client.on('ready', () => { + log.DEBUG(`Discord server up and running with client: ${client.user.tag}`); + log.DEBUG(`Starting HTTP Server`); + runHTTPServer(); + + log.INFO(`Logged in as ${client.user.tag}!`); + log.INFO("HTTP server started!"); }); client.on('interactionCreate', async interaction => { @@ -95,6 +135,7 @@ client.on('interactionCreate', async interaction => { try { //await command.execute(interaction); } catch (error) { + log.ERROR(error); //console.error(error); //await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true }); } @@ -111,17 +152,15 @@ client.on('messageCreate', message => { try { client.commands.get(command).execute(message, args); } catch (error) { - console.error(error); + log.ERROR(error); //message.reply('there was an error trying to execute that command!'); } }); -console.log("Link Flayer Bot Activating"); -keepAlive(); -client.login(token); //Load Client Discord Token +client.login(discordToken); //Load Client Discord Token try { - console.log("Loading initial startup feeds"); + log.INFO("Loading initial startup feeds"); libCore.loadFeeds(); } catch (error) { - console.log(error); + log.ERROR(error); } \ No newline at end of file diff --git a/libCore.js b/libCore.js index 1677095..5622552 100644 --- a/libCore.js +++ b/libCore.js @@ -4,6 +4,9 @@ const axios = require('axios'); let parser = new Parser(); const storageHandler = require("./libStorage"); +const { DebugBuilder } = require("./utilities/debugBuilder"); +const log = new DebugBuilder("server", "libCore"); + /* OpenAI config const { Configuration, OpenAIApi } = require('openai'); const configuration = new Configuration({ @@ -48,11 +51,11 @@ exports.addSource = function (title, link, category, callback) { } }], function (err, record) { if (err) { - console.error("Error in create:", err); + log.ERROR("Error in create:", err); callback(err, undefined); } - console.log("Record ID:", record.getId()); + log.DEBUG("Record ID:", record.getId()); var linkData = { title: `${title}`, @@ -61,11 +64,11 @@ exports.addSource = function (title, link, category, callback) { id: record.getId() } - console.log("Link Data:", linkData); + log.DEBUG("Link Data:", linkData); feeds.push(linkData); - console.log("pushed item to feeds"); + log.DEBUG("pushed item to feeds"); callback(undefined, true); }); @@ -85,10 +88,10 @@ exports.deleteSource = function (title, callback) { } storageSource.destroy(deleteRecord, function (err, deletedRecord) { if (err) { - console.error(err); + log.ERROR(err); callback(err, undefined); } - console.log(deletedRecord.id); + log.DEBUG(deletedRecord.id); callback(undefined, deletedRecord); }); } @@ -124,9 +127,9 @@ exports.loadFeeds = function () { storageSource.getAllRecords(function (err, records) { records.forEach(function (record) { try { - console.log('Retrieved title: ', record.get('title')); - console.log('Retrieved link:', record.get('link')); - console.log('Retrieved category:', record.get('category')); + log.DEBUG('Retrieved title: ', record.get('title')); + log.DEBUG('Retrieved link:', record.get('link')); + log.DEBUG('Retrieved category:', record.get('category')); var feedData = { title: `${unescape(record.get('title'))}`, @@ -158,7 +161,7 @@ exports.loadFeeds = function () { } } catch (error) { - console.log(error); + log.DEBUG(error); } }); @@ -167,7 +170,7 @@ exports.loadFeeds = function () { try { const feed = parser.parseURL(feedBlock.link, function (err, feed) { if (err) { - console.log(err + " " + feedBlock.link); + log.DEBUG(err + " " + feedBlock.link); //return; } @@ -191,19 +194,19 @@ exports.loadFeeds = function () { }); } else { - console.log('error parsing :' + feedBlock.link); + log.DEBUG('error parsing :' + feedBlock.link); } }) } catch (error) { - console.log(error); + log.DEBUG(error); } })().then(); }); return; //fetchNextPage(); }, function done(error) { - console.log(error); + log.DEBUG(error); }); } @@ -216,7 +219,7 @@ exports.loadFeeds = function () { exports.weatherAlert = async function (state) { var answerURL = `https://api.weather.gov/alerts/active?area=${state}`; - console.log(answerURL); + log.DEBUG(answerURL); answerData = []; await axios.get(answerURL) @@ -228,7 +231,7 @@ exports.weatherAlert = async function (state) { return answerData; }) .catch(error => { - console.log(error); + log.DEBUG(error); }); return answerData; } @@ -241,15 +244,15 @@ exports.weatherAlert = async function (state) { exports.getFood = async function () { var answerURL = `https://www.themealdb.com/api/json/v1/1/random.php`; - console.log(answerURL); + log.DEBUG(answerURL); answerData = { text: `No answer found try using a simpler search term`, source: `` } await axios.get(answerURL) .then(response => { - //console.log(response.data.RelatedTopics[0].Text); - //console.log(response.data.RelatedTopics[0].FirstURL); + //log.DEBUG(response.data.RelatedTopics[0].Text); + //log.DEBUG(response.data.RelatedTopics[0].FirstURL); // if (response.data.meals.length != 0) { @@ -274,7 +277,7 @@ exports.getFood = async function () { return answerData; }) .catch(error => { - console.log(error); + log.DEBUG(error); }); return answerData; } @@ -288,14 +291,14 @@ exports.getFood = async function () { exports.getSlang = async function (question) { var answerURL = `https://api.urbandictionary.com/v0/define?term=${question}`; - console.log(answerURL); + log.DEBUG(answerURL); slangData = { definition: `No answer found try using a simpler search term`, example: `` } await axios.get(answerURL) .then(response => { - console.log(response.data.list[0]); + log.DEBUG(response.data.list[0]); slangData = { definition: `${unescape(response.data.list[0].definition) ? unescape(response.data.list[0].definition) : ''}`, @@ -307,7 +310,7 @@ exports.getSlang = async function (question) { return slangData; }) .catch(error => { - console.log(error); + log.DEBUG(error); }); return slangData; } @@ -349,14 +352,14 @@ exports.getQuotes = async function (quote_url) { var data = []; await axios.get(quote_url) .then(response => { - console.log(response.data[0].q); - console.log(response.data[0].a); + log.DEBUG(response.data[0].q); + log.DEBUG(response.data[0].a); data = response.data; return data; }) .catch(error => { - console.log(error); + log.DEBUG(error); }); return data; } diff --git a/libStorage.js b/libStorage.js index 9ef19c4..d409c09 100644 --- a/libStorage.js +++ b/libStorage.js @@ -2,6 +2,8 @@ // Update the functions here to change the storage medium // Import modules +const { DebugBuilder } = require("./utilities/debugBuilder"); +const log = new DebugBuilder("server", "libStorage"); // Storage Specific Modules // MySQL @@ -11,17 +13,17 @@ const mysql = require("mysql"); // Helper Functions // Function to run and handle SQL errors function runSQL(sqlQuery, connection, callback = (err, rows) => { - console.log(err); + log.ERROR(err); throw err; }) { // Start the MySQL Connection //connection.connect(); connection.query(sqlQuery, (err, rows) => { if (err) { - console.log("SQL Error:", err) + log.ERROR("SQL Error:", err) callback(err, undefined); } - console.log("RunSQL Returned Rows:", rows); + log.DEBUG("RunSQL Returned Rows:", rows); callback(undefined, rows); }) //connection. @@ -73,18 +75,18 @@ exports.Storage = class Storage { * @param {function} callback The callback function to be called with the record when saved */ create(toBeSaved, callback) { - console.log("To be saved:", toBeSaved); - console.log("to be saved length:", toBeSaved.length); + log.DEBUG("To be saved:", toBeSaved); + log.DEBUG("to be saved length:", toBeSaved.length); if (!toBeSaved[0].fields?.title) callback(Error("No title given"), undefined); let newRecords = [] for (var entry of toBeSaved) { entry = entry.fields - console.log("Entry:", entry); + log.DEBUG("Entry:", entry); this.returnRecord(undefined, entry.title, entry.link, entry.category, (err, record) => { if (err) callback(err, undefined); newRecords.push(record); if (toBeSaved.length === 1) { - console.log("One record to callback with:", record); + log.DEBUG("One record to callback with:", record); callback(undefined, record); } }) @@ -150,14 +152,14 @@ exports.Storage = class Storage { * @param {function} callback The callback to be called with either an error or undefined if successful */ saveEntry(entryObject, callback) { - console.log("Saving entry:", entryObject); + log.DEBUG("Saving entry:", entryObject); if (!entryObject?.title || !entryObject?.link || !entryObject?.category) { callback(new Error("Entry object malformed, check the object before saving it"), undefined) } const sqlQuery = `INSERT INTO ${this.dbTable} (title, link, category) VALUES ('${entryObject.title}', '${entryObject.link}', '${entryObject.category}');`; - console.log(`Adding new entry with SQL query: '${sqlQuery}'`) + log.DEBUG(`Adding new entry with SQL query: '${sqlQuery}'`) runSQL(sqlQuery, this.connection, (err, rows) => { if (err) callback(err, undefined); @@ -194,7 +196,7 @@ exports.Storage = class Storage { sqlQuery = `${sqlQuery} WHERE title = '${entryObject.title}';` - console.log(`Updating entry with SQL query: '${sqlQuery}'`) + log.DEBUG(`Updating entry with SQL query: '${sqlQuery}'`) runSQL(sqlQuery, this.connection, (err, rows) => { if (err) callback(err, undefined); @@ -229,7 +231,7 @@ exports.Storage = class Storage { * @param {*} callback Callback function to return an error or the record */ returnRecord(_id, _title, _link, _category, callback) { - console.log(`Return record for these values: ID: '${_id}', Title: '${_title}', Category: '${_category}', Link: '${_link}'`) + log.DEBUG(`Return record for these values: ID: '${_id}', Title: '${_title}', Category: '${_category}', Link: '${_link}'`) if (!_link && !_title) callback(new Error("No link or title given when creating a record"), undefined); let entryObject = { "title": _title, @@ -250,7 +252,7 @@ exports.Storage = class Storage { else { this.checkForTitle(_title, (err, titleExists) => { if (!titleExists) { - console.log("Entry doesn't exist, making one now", entryObject); + log.DEBUG("Entry doesn't exist, making one now", entryObject); this.saveEntry(entryObject, (err, rows) => { if (err) callback(err, undefined); this.getRecordBy("title", entryObject.title, (err, record) => { @@ -277,7 +279,7 @@ exports.Storage = class Storage { * @param {function} callback */ getAllRecords(callback) { - console.log("Getting all records"); + log.INFO("Getting all records"); const sqlQuery = `SELECT * FROM ${this.dbTable}` let rssRecords = []; @@ -285,10 +287,10 @@ exports.Storage = class Storage { runSQL(sqlQuery, this.connection, (err, rows) => { if (err) callback(err, undefined); for (const row of rows) { - console.log("Row from SQL query:", row); + log.DEBUG("Row from SQL query:", row); rssRecords.push(new RSSRecord(row.id, row.title, row.link, row.category)); } - console.log("All records:", rssRecords); + log.DEBUG("All records:", rssRecords); callback(undefined, rssRecords); }); } diff --git a/libUtils.js b/libUtils.js index 93ca30e..4cf4494 100644 --- a/libUtils.js +++ b/libUtils.js @@ -1,7 +1,59 @@ +const { DebugBuilder } = require("./utilities/debugBuilder"); +const log = new DebugBuilder("server", "libUtils"); + /** * sleep - sleep/wait * @constructor */ exports.sleep = (ms) => new Promise((resolve) => { setTimeout(resolve, ms); - }) \ No newline at end of file + }) + +/** + * Normalize a port into a number, string, or false. + * + * @param {*} val Value to be normalized + * @returns Normalized value + */ +exports.normalizePort = (val) => { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ +exports.onError = (error) => { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + log.ERROR(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + log.ERROR(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 062a998..db848bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,17 +11,21 @@ "dependencies": { "@discordjs/builders": "^0.15.0", "@discordjs/rest": "^0.5.0", - "airtable": "^0.11.1", "axios": "^0.24.0", "chatgpt": "^1.4.0", + "cookie-parser": "~1.4.4", + "debug": "~2.6.9", "discord-api-types": "^0.35.0", "discord.js": "^13.8.1", "dotenv": "^10.0.0", + "ejs": "~2.6.1", "express": "^4.17.1", "fs": "^0.0.1-security", + "http-errors": "~1.6.3", "js-doc": "^0.5.0", "jsonfile": "^6.1.0", "mathjs": "^10.6.4", + "morgan": "~1.9.1", "mysql": "2.18.1", "openai": "^3.1.0", "parse-files": "^0.1.1", @@ -232,22 +236,6 @@ "@types/node": "*" } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/abortcontroller-polyfill": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", - "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==" - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -260,21 +248,6 @@ "node": ">= 0.6" } }, - "node_modules/airtable": { - "version": "0.11.6", - "resolved": "https://registry.npmjs.org/airtable/-/airtable-0.11.6.tgz", - "integrity": "sha512-Na67L2TO1DflIJ1yOGhQG5ilMfL2beHpsR+NW/jhaYOa4QcoxZOtDFs08cpSd1tBMsLpz5/rrz/VMX/pGL/now==", - "dependencies": { - "@types/node": ">=8.0.0 <15", - "abort-controller": "^3.0.0", - "abortcontroller-polyfill": "^1.4.0", - "lodash": "^4.17.21", - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/ansi-regex": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", @@ -376,6 +349,22 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/bignumber.js": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", @@ -413,6 +402,21 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -568,6 +572,26 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -713,6 +737,14 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/ejs": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.2.tgz", + "integrity": "sha512-PcW2a0tyTuPHz3tWyYqtK6r1fZ3gp+3Sop8Ph+ZYN81Ob5rwmbHEzaqs10N3BEsaGTkh/ooniXK+WwszGlc2+Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -770,14 +802,6 @@ "through": "^2.3.8" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, "node_modules/eventsource-parser": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-0.0.5.tgz", @@ -838,6 +862,21 @@ "node": ">= 0.10.0" } }, + "node_modules/express/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -1032,18 +1071,43 @@ } }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "node_modules/http-errors/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" } }, "node_modules/iconv-lite": { @@ -1890,6 +1954,40 @@ "node": ">=10" } }, + "node_modules/morgan": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", + "dependencies": { + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -1968,6 +2066,14 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2170,6 +2276,21 @@ "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -2323,6 +2444,21 @@ "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index 5aa4539..a746f52 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "dependencies": { "@discordjs/builders": "^0.15.0", "@discordjs/rest": "^0.5.0", - "airtable": "^0.11.1", "axios": "^0.24.0", "chatgpt": "^1.4.0", "discord-api-types": "^0.35.0", @@ -20,7 +19,12 @@ "openai": "^3.1.0", "parse-files": "^0.1.1", "rss-parser": "^3.12.0", - "mysql": "2.18.1" + "mysql": "2.18.1", + "cookie-parser": "~1.4.4", + "debug": "~2.6.9", + "ejs": "~2.6.1", + "http-errors": "~1.6.3", + "morgan": "~1.9.1" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", diff --git a/routes/admin.js b/routes/admin.js new file mode 100644 index 0000000..918a24d --- /dev/null +++ b/routes/admin.js @@ -0,0 +1,19 @@ +// Debug +const { DebugBuilder } = require("../utilities/debugBuilder.js"); +const log = new DebugBuilder("server", "admin"); +// Modules +var express = require('express'); +var router = express.Router(); +var adminController = require("../controllers/adminController"); + +/* GET */ +router.get('/presets', adminController.getAvailablePresets); + +/* POST */ +router.post('/join', adminController.joinPreset); + +/* POST */ +router.post('/leave', adminController.leaveServer); + + +module.exports = router; diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000..a188406 --- /dev/null +++ b/routes/index.js @@ -0,0 +1,31 @@ +const libCore = require("../libCore"); +var express = require('express'); +var router = express.Router(); + +/* GET home page. */ +router.get('/', (req, res) => { + var sources = libCore.getSources(); + //res.render('index', { "sources": sources }); + + var htmlOutput = ""; + + sources.forEach(source => { + htmlOutput += ` +
+ +
Title: ${source.title}
+
Link: ${source.link}
+
category: ${source.category}
+ +
+
+
+ +
+ + ` + }); + res.send(htmlOutput); +}); + +module.exports = router; diff --git a/routes/nodes.js b/routes/nodes.js new file mode 100644 index 0000000..fc7f146 --- /dev/null +++ b/routes/nodes.js @@ -0,0 +1,31 @@ +const express = require('express'); +const router = express.Router(); +const nodesController = require('../controllers/nodesController'); + +/* GET nodes the server knows */ +router.get('/', nodesController.listAllNodes); + +// TODO Need to authenticate this request +/* POST a new node to the server +* +* Will create a new DB entry for the node for the server to reference later +* Req. body: { +* "serverInfo": {"ip": "x.x.x.x", port: 0000} +* } +* +* Will return a token for the client to reference when the bot is making requests +* Res. body { +* "serverToken": "" +* } +*/ +router.post('/newNode', nodesController.newNode); + +// TODO Need to authenticate this request +/* GET the information the server has on a particular node */ +router.get('/nodeInfo', nodesController.getNodeInfo); + +// TODO Need to authenticate this request +// Client checkin with the server to update information +router.post('/nodeCheckIn', nodesController.nodeCheckIn); + +module.exports = router; diff --git a/utilities/debugBuilder.js b/utilities/debugBuilder.js new file mode 100644 index 0000000..1c2a621 --- /dev/null +++ b/utilities/debugBuilder.js @@ -0,0 +1,17 @@ +// Debug +const debug = require('debug'); + +/** + * Create the different logging methods for a function + * Namespace template = ("[app]:[fileName]:['INFO', 'WARNING', 'DEBUG', 'ERROR']") + * @param {string} appName The name of the app to be used in the 'app' portion of the namespace + * @param {string} fileName The name of the file calling the builder to be used in the 'fileName' portion of the namespace + */ +exports.DebugBuilder = class DebugBuilder { + constructor(appName, fileName) { + this.INFO = debug(`${appName}:${fileName}:INFO`); + this.DEBUG = debug(`${appName}:${fileName}:DEBUG`); + this.WARN = debug(`${appName}:${fileName}:WARNING`); + this.ERROR = debug(`${appName}:${fileName}:ERROR`); + } +} \ No newline at end of file diff --git a/utilities/httpRequests.js b/utilities/httpRequests.js new file mode 100644 index 0000000..4fef264 --- /dev/null +++ b/utilities/httpRequests.js @@ -0,0 +1,68 @@ +// Debug +const { DebugBuilder } = require("../utilities/debugBuilder.js"); +const log = new DebugBuilder("server", "httpRequests"); +// Modules +const http = require("http"); + +exports.requestOptions = class requestOptions { + /** + * Construct an HTTP request + * @param {*} method Method of request to use [GET, POST] + * @param {*} headers Headers of the request, not required but will get filled with default values if not set + * @param {*} hostname The destination of the request + * @param {*} port The port for the destination, will use 3001 by default + */ + constructor(path, method, hostname, port = 3001, headers = undefined, timeout = undefined) { + this.hostname = hostname; + this.path = path; + this.port = port; + this.method = method; + this.timeout = timeout; + if (method === "POST"){ + this.headers = headers ?? { + 'Content-Type': 'application/json', + } + } + } +} + +/** + * Send the HTTP request to the server + * @param requestOptions + * @param data + * @param callback + */ +exports.sendHttpRequest = function sendHttpRequest(requestOptions, data, callback){ + log.DEBUG("Sending a request to: ", requestOptions.hostname, requestOptions.port) + // Create the request + const req = http.request(requestOptions, res => { + res.on('data', (data) => { + const responseObject = { + "statusCode": res.statusCode, + "body": data + }; + + try { + responseObject.body = JSON.parse(responseObject.body) + } + catch (err) { + } + + log.DEBUG("Response Object: ", responseObject); + callback(responseObject); + }) + }).on('error', err => { + log.ERROR('Error: ', err.message) + // TODO need to handle if the server is down + }) + + if (requestOptions.timeout) { + req.setTimeout(requestOptions.timeout, () => { + callback(false); + }); + } + + // Write the data to the request and send it + req.write(data) + req.end() +} \ No newline at end of file diff --git a/utilities/mysqlHandler.js b/utilities/mysqlHandler.js new file mode 100644 index 0000000..7a1225b --- /dev/null +++ b/utilities/mysqlHandler.js @@ -0,0 +1,134 @@ +const mysql = require('mysql'); +const databaseConfig = require('../config/databaseConfig'); +const utils = require('./utils'); + +const connection = mysql.createConnection({ + host: databaseConfig.database_host, + user: databaseConfig.database_user, + password: databaseConfig.database_password, + database: databaseConfig.database_database +}); + +const nodesTable = `${databaseConfig.database_database}.nodes`; + +connection.connect() + +/** Get all nodes the server knows about regardless of status + * @param {*} callback Callback function + */ +exports.getAllNodes = (callback) => { + const sqlQuery = `SELECT * FROM ${nodesTable}` + runSQL(sqlQuery, (rows) => { + callback(rows); + }) +} + +/** Get all nodes that have the online status set true (are online) + * @param callback Callback function + */ +exports.getOnlineNodes = (callback) => { + const sqlQuery = `SELECT * FROM ${nodesTable} WHERE online = 1;` + runSQL(sqlQuery, (rows) => { + callback(rows); + }) +} + +/** Get info on a node based on ID + * @param nodeId The ID of the node + * @param callback Callback function + */ +exports.getNodeInfoFromId = (nodeId, callback) => { + const sqlQuery = `SELECT * FROM ${nodesTable} WHERE id = ${nodeId}` + runSQL(sqlQuery, (rows) => { + // Call back the first (and theoretically only) row + // Specify 0 so downstream functions don't have to worry about it + callback(rows[0]); + }) +} + +/** Add a new node to the DB + * @param nodeObject Node information object + * @param callback Callback function + */ +exports.addNewNode = (nodeObject, callback) => { + if (!nodeObject.name) throw new Error("No name provided"); + const name = nodeObject.name, + ip = nodeObject.ip, + port = nodeObject.port, + location = nodeObject.location, + nearbySystems = utils.JsonToBuffer(nodeObject.nearbySystems), + online = nodeObject.online; + const sqlQuery = `INSERT INTO ${nodesTable} (name, ip, port, location, nearbySystems, online) VALUES ('${name}', '${ip}', ${port}, '${location}', '${nearbySystems}', ${online})`; + + runSQL(sqlQuery, (rows) => { + callback(rows); + }) +} + +/** Update the known info on a node + * @param nodeObject Node information object + * @param callback Callback function + */ +exports.updateNodeInfo = (nodeObject, callback) => { + const name = nodeObject.name, + ip = nodeObject.ip, + port = nodeObject.port, + location = nodeObject.location, + online = nodeObject.online; + let queryParams = [], + nearbySystems = nodeObject.nearbySystems; + + if (name) queryParams.push(`name = '${name}'`); + if (ip) queryParams.push(`ip = '${ip}'`); + if (port) queryParams.push(`port = ${port}`); + if (location) queryParams.push(`location = '${location}'`); + if (nearbySystems) { + nearbySystems = utils.JsonToBuffer(nearbySystems) + queryParams.push(`nearbySystems = '${nearbySystems}'`); + } + if (typeof online === "boolean") { + if (online) queryParams.push(`online = 1`); + else queryParams.push(`online = 0`); + } + + let sqlQuery = `UPDATE ${nodesTable} SET` + if (!queryParams || queryParams.length === 0) return callback(undefined); + if (queryParams.length === 1) { + sqlQuery = `${sqlQuery} ${queryParams[0]}` + } else { + let i = 0; + for (const param of queryParams) { + if (i === queryParams.length-1) { + sqlQuery = `${sqlQuery} ${param}` + i += 1; + } + else { + sqlQuery = `${sqlQuery} ${param},` + i += 1; + } + } + } + + sqlQuery = `${sqlQuery} WHERE id = ${nodeObject.id};` + + runSQL(sqlQuery, (rows) => { + if (rows.affectedRows === 1) callback(true); + else callback(rows); + }) +} + +// Function to run and handle SQL errors +function runSQL(sqlQuery, callback, error = (err) => { + console.log(err); + throw err; +}) { + connection.query(sqlQuery, (err, rows) => { + if (err) return error(err); + //console.log('The rows are:', rows); + return callback(rows); + }) +} + +exports.closeConnection = () => { + connection.end() +} \ No newline at end of file diff --git a/utilities/utils.js b/utilities/utils.js new file mode 100644 index 0000000..640c3fe --- /dev/null +++ b/utilities/utils.js @@ -0,0 +1,19 @@ +// Convert a JSON object to a buffer for the DB +exports.JsonToBuffer = (jsonObject) => { + return Buffer.from(JSON.stringify(jsonObject)) +} + +// Convert a buffer from the DB to JSON object +exports.BufferToJson = (buffer) => { + return JSON.parse(buffer.toString()); +} + +/** Find a key in an object by its value + * + * @param {*} object The object to search + * @param {*} value The value to search the arrays in the object for + * @returns The key of the object that contains the value + */ +exports.getKeyByArrayValue = (object, value) => { + return Object.keys(object).find(key => object[key].includes(value)); + } \ No newline at end of file diff --git a/views/error.ejs b/views/error.ejs new file mode 100644 index 0000000..7cf94ed --- /dev/null +++ b/views/error.ejs @@ -0,0 +1,3 @@ +

<%= message %>

+

<%= error.status %>

+
<%= error.stack %>
diff --git a/views/index.ejs b/views/index.ejs new file mode 100644 index 0000000..7b7a1d6 --- /dev/null +++ b/views/index.ejs @@ -0,0 +1,11 @@ + + + + <%= title %> + + + +

<%= title %>

+

Welcome to <%= title %>

+ +