Files
DRB-CnC/Server/utilities/mysqlHandler.js
2023-06-17 18:40:09 -04:00

368 lines
13 KiB
JavaScript

require('dotenv').config();
const mysql = require('mysql2');
const utils = require('./utils');
const { nodeObject, clientObject, connectionObject } = require("./recordHelper");
const { DebugBuilder } = require("../utilities/debugBuilder");
const { BufferToJson, getClientObjectByClientID } = require("../utilities/utils");
const log = new DebugBuilder("server", "mysSQLHandler");
const connection = mysql.createPool({
host: process.env.NODE_DB_HOST,
user: process.env.NODE_DB_USER,
password: process.env.NODE_DB_PASS,
database: process.env.NODE_DB_NAME
});
const nodesTable = `${process.env.NODE_DB_NAME}.nodes`;
const nodeConnectionsTable = `${process.env.NODE_DB_NAME}.node_connections`;
/**
* Return a node object from a single SQL row
*
* @param {object} row The row to convert to a node object
* @returns {nodeObject} The converted node object to be used downstream
*/
function returnNodeObjectFromRow(row) {
return new nodeObject({
_id: row.id,
_name: row.name,
_ip: row.ip,
_port: row.port,
_location: row.location,
_nearbySystems: BufferToJson(row.nearbySystems),
_online: (row.online === 1) ? true : false,
});
}
/**
* Wrapper to convert an array of rows to an array of nodeObjects
*
* @param {array} rows The array of SQL results to be converted into node objects
* @returns {array} An array of node objects
*/
function returnNodeObjectFromRows(rows) {
var i = 0;
for (var row of rows){
log.DEBUG("Row: ", row);
rows[i] = returnNodeObjectFromRow(row);
i += 1;
}
log.DEBUG("Converted Objects from Rows: ", rows);
return rows;
}
/**
* Returns a connection object from an SQL row
*
* @param {*} row The SQL row to convert to a connection object
* @returns {connectionObject}
*/
async function returnConnectionObjectFromRow(row) {
if (Array.isArray(row)) row = row[0]
log.DEBUG("Connection row: ", row);
return new connectionObject({
_connection_id: row.connection_id,
_node: await getNodeInfoFromId(row.id),
_client_object: await getClientObjectByClientID(row.discord_client_id)
});
}
/** 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) => {
if(!rows || rows.length == 0) callback(undefined);
return callback(returnNodeObjectFromRows(rows));
})
}
/**
* Get all Nodes synchronously **May not be working**
*
* @returns
*/
exports.getAllNodesSync = async () => {
const sqlQuery = `SELECT * FROM ${nodesTable}`
const rows = await runSQL(sqlQuery);
console.log("Rows: ", rows);
return returnNodeObjectFromRows(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) => {
return callback(returnNodeObjectFromRows(rows));
})
}
/** Get info on a node based on ID
* @param nodeId The ID of the node
* @param callback Callback function
*/
async function getNodeInfoFromId(nodeId, callback = undefined) {
if (!nodeId) throw new Error("No node ID given when trying to fetch node");
log.DEBUG("Getting node from ID: ", nodeId);
const sqlQuery = `SELECT * FROM ${nodesTable} WHERE id = ${nodeId}`
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
// Call back the first (and theoretically only) row
// Specify 0 so downstream functions don't have to worry about it
return (callback) ? callback(returnNodeObjectFromRow(sqlResponse[0])) : returnNodeObjectFromRow(sqlResponse[0]);
}
exports.getNodeInfoFromId = getNodeInfoFromId
/** Add a new node to the DB
* @param nodeObject Node information object
* @param callback Callback function
*/
exports.addNewNode = async (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,
connected = 0;
const sqlQuery = `INSERT INTO ${nodesTable} (name, ip, port, location, nearbySystems, online, connected) VALUES ('${name}', '${ip}', ${port}, '${location}', '${nearbySystems}', ${online}, ${connected})`;
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
// Call back the first (and theoretically only) row
// Specify 0 so downstream functions don't have to worry about it
return (callback) ? callback(returnNodeObjectFromRow(sqlResponse)) : returnNodeObjectFromRow(sqlResponse);
}
/** Update the known info on a node
* @param nodeObject Node information object
* @param callback Callback function
*/
exports.updateNodeInfo = async (nodeObject, callback = undefined) => {
if(!nodeObject.id) throw new Error("Attempted to updated node without providing ID", nodeObject);
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" || typeof online === "number") {
if (online || online === 1) queryParams.push(`online = 1`);
else queryParams.push(`online = 0`);
}
let sqlQuery = `UPDATE ${nodesTable} SET`
if (!queryParams || queryParams.length === 0) return (callback) ? callback(undefined) : 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};`
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
if (sqlResponse.affectedRows === 1) return (callback) ? callback(true) : true;
else return (callback) ? callback(returnNodeObjectFromRows(sqlResponse)) : returnNodeObjectFromRows(sqlResponse);
}
/**
* Add a new connection to the DB when a bot has been connected to the server
*
* @param {*} nodeObject The node object that is being used for this connection
* @param {*} clientId The client ID Object being used for this connection
* @param {*} callback [OPTIONAL] The callback function to be called with the results, will return otherwise
*/
exports.addNodeConnection = (nodeObject, clientObject, callback = undefined) => {
if (!nodeObject.id || !clientObject.clientId) throw new Error("Tried to add a connection without a client and/or node ID");
const sqlQuery = `INSERT INTO ${nodeConnectionsTable} (id, discord_client_id) VALUES (${nodeObject.id}, '${clientObject.clientId}')`;
const sqlResponse = new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
if (!sqlResponse) throw new Error("No result from added connection");
return (callback) ? callback(true) : true;
}
/**
* Check what node is connected with a given client ID object
*
* @param {*} clientId The client ID object used to search for a connected node
* @param {*} callback [OPTIONAL] The callback function to be called with the results, return will be used otherwise
*/
exports.checkNodeConnectionByClientId = async (clientId, callback = undefined) => {
if (!clientId.clientId) throw new Error("Tried to check a connection without a client ID");
const sqlQuery = `SELECT * FROM ${nodeConnectionsTable} WHERE discord_client_id = '${clientId.clientId}'`;
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
log.VERBOSE("SQL Response from checking connection: ", sqlResponse);
if (!sqlResponse) return (callback) ? callback(undefined) : undefined;
const newNodeObject = await getNodeInfoFromId(sqlResponse[0].id);
log.DEBUG("Node Object from SQL Response: ", newNodeObject);
return (callback) ? callback(newNodeObject) : newNodeObject;
}
/**
* Get a connection by node ID
*
* @param {*} nodeId The ID to search for a connection with
* @param {*} callback [OPTIONAL] The callback function to be called with the results, return will be used otherwise
* @returns {connectionObject}
*/
exports.getConnectionByNodeId = async (nodeId, callback = undefined) => {
const sqlQuery = `SELECT * FROM ${nodeConnectionsTable} WHERE id = '${nodeId}'`;
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
log.VERBOSE("SQL Response from checking connection: ", sqlResponse);
if (!sqlResponse | sqlResponse.length == 0) return (callback) ? callback(undefined) : undefined;
const newConnectionObject = await returnConnectionObjectFromRow(sqlResponse)
log.DEBUG("Connection Object from SQL Response: ", newConnectionObject);
return (callback) ? callback(newConnectionObject) : newConnectionObject;
}
/**
* Remove a node connection by the node
*
* @param {*} nodeId The node ID of the node to remove connections of
* @param {*} callback [OPTIONAL] The callback function to callback with the results, return will be used otherwise
* @returns
*/
exports.removeNodeConnectionByNodeId = async (nodeId, callback = undefined) => {
const sqlQuery = `DELETE FROM ${nodeConnectionsTable} WHERE id = '${nodeId}'`;
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
log.VERBOSE("SQL Response from removing connection: ", sqlResponse);
if (!sqlResponse) return (callback) ? callback(undefined) : undefined;
return (callback) ? callback(sqlResponse) : sqlResponse;
}
/**
* Gets all connected nodes
*
* @param {*} callback [OPTIONAL] The callback function to callback with the results, return will be used otherwise
* @returns {nodeObject}
*/
exports.getConnectedNodes = async (callback = undefined) => {
const sqlQuery = `SELECT * FROM ${nodeConnectionsTable}`;
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
log.VERBOSE("SQL Response from checking connection: ", sqlResponse);
if (!sqlResponse) return (callback) ? callback(undefined) : undefined;
var nodeObjects = []
for (const row of sqlResponse) {
const newNodeObject = await getNodeInfoFromId(row.id);
log.DEBUG("Node Object from SQL Response: ", newNodeObject);
nodeObjects.push(newNodeObject);
}
return (callback) ? callback(nodeObjects) : nodeObjects;
}
/**
* Returns all connections
*
* @param {*} callback [OPTIONAL] The callback function to callback with the results, return will be used otherwise
* @returns {connectionObject}
*/
exports.getAllConnections = async (callback = undefined) => {
const sqlQuery = `SELECT * FROM ${nodeConnectionsTable}`;
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
log.VERBOSE("SQL Response from checking connection: ", sqlResponse);
if (!sqlResponse) return (callback) ? callback(undefined) : undefined;
var connectionObjects = []
for (const row of sqlResponse) {
connectionObjects.push(await returnConnectionObjectFromRow(row));
}
return (callback) ? callback(connectionObjects) : connectionObjects;
}
// Function to run and handle SQL errors
function runSQL(sqlQuery, callback = undefined, 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) ? callback(rows) : rows
})
}
exports.closeConnection = () => {
connection.end()
}