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 %>
+
+