Implement Discord CnC Server into Emmelia

This commit is contained in:
Logan Cusano
2023-02-24 21:27:55 -05:00
parent 0ee5c4293f
commit 24b16d87ea
19 changed files with 926 additions and 135 deletions

8
config/databaseConfig.js Normal file
View File

@@ -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;

5
config/discordConfig.js Normal file
View File

@@ -0,0 +1,5 @@
const discordConfig = {
channelID: '367396189529833476'
}
module.exports = discordConfig;

View File

@@ -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);
});
}

View File

@@ -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});
})
})
}

119
index.js
View File

@@ -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 <br />";
var indexRouter = require('./routes/index');
var nodesRouter = require('./routes/nodes');
var adminRouter = require('./routes/admin');
var sources = libCore.getSources();
sources.forEach(source => {
htmlOutput += `
<div style='margin-bottom:15px;'>
// HTTP Server Config
var app = express();
<div> Title: ${source.title} </div>
<div> Link: ${source.link} </div>
<div> category: ${source.category} </div>
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
</div>
<div>
<hr />
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')));
</div>
// 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'));
@@ -79,7 +114,12 @@ for (const file of commandFiles) {
}
client.on('ready', () => {
console.log(`Logged in as ${client.user.tag}!`);
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);
}

View File

@@ -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;
}

View File

@@ -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);
});
}

View File

@@ -1,3 +1,6 @@
const { DebugBuilder } = require("./utilities/debugBuilder");
const log = new DebugBuilder("server", "libUtils");
/**
* sleep - sleep/wait
* @constructor
@@ -5,3 +8,52 @@
exports.sleep = (ms) => new Promise((resolve) => {
setTimeout(resolve, ms);
})
/**
* 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;
}
}

234
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

19
routes/admin.js Normal file
View File

@@ -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;

31
routes/index.js Normal file
View File

@@ -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 += `
<div style='margin-bottom:15px;'>
<div> Title: ${source.title} </div>
<div> Link: ${source.link} </div>
<div> category: ${source.category} </div>
</div>
<div>
<hr />
</div>
`
});
res.send(htmlOutput);
});
module.exports = router;

31
routes/nodes.js Normal file
View File

@@ -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;

17
utilities/debugBuilder.js Normal file
View File

@@ -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`);
}
}

68
utilities/httpRequests.js Normal file
View File

@@ -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()
}

134
utilities/mysqlHandler.js Normal file
View File

@@ -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()
}

19
utilities/utils.js Normal file
View File

@@ -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));
}

3
views/error.ejs Normal file
View File

@@ -0,0 +1,3 @@
<h1><%= message %></h1>
<h2><%= error.status %></h2>
<pre><%= error.stack %></pre>

11
views/index.ejs Normal file
View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
</body>
</html>