浏览代码

Rewrote job system partly

Kristian Vos 4 年之前
父节点
当前提交
110271df79

+ 218 - 77
backend/core.js

@@ -11,6 +11,133 @@ class DeferredPromise {
 	}
 }
 
+class QueueTask {
+	// eslint-disable-next-line require-jsdoc
+	constructor(job, priority) {
+		this.job = job;
+		this.priority = priority;
+	}
+}
+
+class Queue {
+	// eslint-disable-next-line require-jsdoc
+	constructor(handleTaskFunction, concurrency) {
+		this.handleTaskFunction = handleTaskFunction;
+		this.concurrency = concurrency;
+		this.queue = [];
+		this.running = [];
+		this.paused = false;
+	}
+
+	/**
+	 * Pauses the queue, meaning no new jobs can be started. Jobs can still be added to the queue, and already running tasks won't be paused.
+	 */
+	pause() {
+		this.paused = true;
+	}
+
+	/**
+	 * Resumes the queue.
+	 */
+	resume() {
+		this.paused = false;
+		this._handleQueue();
+	}
+
+	/**
+	 * Returns the amount of jobs in the queue.
+	 *
+	 * @returns {number} - amount of jobs in queue
+	 */
+	lengthQueue() {
+		return this.queue.length;
+	}
+
+	/**
+	 * Returns the amount of running jobs.
+	 *
+	 * @returns {number} - amount of running jobs
+	 */
+	lengthRunning() {
+		return this.running.length;
+	}
+
+	/**
+	 * Adds a job to the queue, with a given priority.
+	 *
+	 * @param {object} job - the job that is to be added
+	 * @param {number} priority - the priority of the to be added job
+	 */
+	push(job, priority) {
+		this.queue.push(new QueueTask(job, priority));
+		setTimeout(() => {
+			this._handleQueue();
+		}, 0);
+	}
+
+	/**
+	 * Removes a job currently running from the queue.
+	 *
+	 * @param {object} job - the job to be removed
+	 */
+	removeRunningJob(job) {
+		this.running.remove(this.running.find(task => task.job.toString() === job.toString()));
+	}
+
+	/**
+	 * Check if there's room for a job to be processed, and if there is, run it.
+	 */
+	_handleQueue() {
+		if (!this.paused && this.running.length < this.concurrency && this.queue.length > 0) {
+			const task = this.queue.reduce((a, b) => (a.priority < b.priority ? b : a));
+			this.queue.remove(task);
+			this.running.push(task);
+			this._handleTask(task);
+			setTimeout(() => {
+				this._handleQueue();
+			}, 0);
+		}
+	}
+
+	_handleTask(task) {
+		this.handleTaskFunction(task.job).finally(() => {
+			this.running.remove(task);
+			this._handleQueue();
+		});
+	}
+}
+
+class Job {
+	// eslint-disable-next-line require-jsdoc
+	constructor(name, payload, onFinish, module, parentJob) {
+		this.name = name;
+		this.payload = payload;
+		this.onFinish = onFinish;
+		this.module = module;
+		this.parentJob = parentJob;
+		this.childJobs = [];
+		/* eslint-disable no-bitwise, eqeqeq */
+		this.uniqueId = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
+			const r = (Math.random() * 16) | 0;
+			const v = c == "x" ? r : (r & 0x3) | 0x8;
+			return v.toString(16);
+		});
+		this.status = "INITIALIZED";
+	}
+
+	addChildJob(childJob) {
+		this.childJobs.push(childJob);
+	}
+
+	setStatus(status) {
+		this.status = status;
+	}
+
+	toString() {
+		return this.uniqueId;
+	}
+}
+
 class MovingAverageCalculator {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
@@ -53,10 +180,7 @@ export default class CoreClass {
 		this.name = name;
 		this.status = "UNINITIALIZED";
 		// this.log("Core constructor");
-		this.jobQueue = async.priorityQueue(
-			({ job, options }, callback) => this._runJob(job, options, callback),
-			10 // How many jobs can run concurrently
-		);
+		this.jobQueue = new Queue(job => this._runJob(job), 10);
 		this.jobQueue.pause();
 		this.runningJobs = [];
 		this.priorities = {};
@@ -181,14 +305,34 @@ export default class CoreClass {
 	 *
 	 * @param {string} name - the name of the job e.g. GET_PLAYLIST
 	 * @param {object} payload - any expected payload for the job itself
-	 * @param {object} options - object containing any additional options for the job
-	 * @param {boolean} options.isQuiet - whether or not the job should be advertised in the logs, useful for repetitive/unimportant jobs
-	 * @param {boolean} options.bypassQueue - UNKNOWN
+	 * @param {object} parentJob - the parent job, if any
 	 * @returns {Promise} - returns a promise
 	 */
-	runJob(name, payload, options = { isQuiet: false, bypassQueue: false }) {
+	runJob(name, payload, parentJob) {
 		const deferredPromise = new DeferredPromise();
-		const job = { name, payload, onFinish: deferredPromise };
+		const job = new Job(name, payload, deferredPromise, this, parentJob);
+		this.log("INFO", `Queuing job ${name} (${job.toString()})`);
+		if (parentJob) {
+			this.log(
+				"INFO",
+				`Removing job ${
+					parentJob.name
+				} (${parentJob.toString()}) from running jobs since a child job has to run first`
+			);
+			parentJob.addChildJob(job);
+			parentJob.setStatus("WAITING_ON_CHILD_JOB");
+			parentJob.module.runningJobs.remove(job);
+			parentJob.module.jobQueue.removeRunningJob(job);
+			// console.log(111, parentJob.module.jobQueue.length());
+			// console.log(
+			// 	222,
+			// 	parentJob.module.jobQueue.workersList().map(data => data.data.job)
+			// );
+		}
+
+		// console.log(this);
+
+		// console.log(321, parentJob);
 
 		if (
 			config.debug &&
@@ -199,11 +343,13 @@ export default class CoreClass {
 			this.moduleManager.debugJobs.all.push(job);
 		}
 
-		if (options.bypassQueue) this._runJob(job, options, () => { });
-		else {
-			const priority = this.priorities[name] ? this.priorities[name] : 10;
-			this.jobQueue.push({ job, options }, priority);
-		}
+		job.setStatus("QUEUED");
+
+		// if (options.bypassQueue) this._runJob(job, options, () => {});
+		// else {
+		const priority = this.priorities[name] ? this.priorities[name] : 10;
+		this.jobQueue.push(job, priority);
+		// }
 
 		return deferredPromise.promise;
 	}
@@ -225,69 +371,64 @@ export default class CoreClass {
 	 * @param {string} job.payload - any expected payload for the job itself
 	 * @param {Promise} job.onFinish - deferred promise when the job is complete
 	 * @param {object} options - object containing any additional options for the job
-	 * @param {boolean} options.isQuiet - whether or not the job should be advertised in the logs, useful for repetitive/unimportant jobs
-	 * @param {boolean} options.bypassQueue - UNKNOWN
-	 * @param {Function} cb - Callback after the job has completed
+	 * @returns {Promise} - returns a promise
 	 */
-	_runJob(job, options, cb) {
-		if (!options.isQuiet) this.log("INFO", `Running job ${job.name}`);
-
-		const startTime = Date.now();
-
-		this.runningJobs.push(job);
-
-		const newThis = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
-
-		newThis.runJob = (...args) => {
-			if (args.length === 1) args.push({});
-			args[1].bypassQueue = true;
-
-			return this.runJob(...args);
-		};
-
-		this[job.name]
-			.apply(newThis, [job.payload])
-			.then(response => {
-				if (!options.isQuiet) this.log("INFO", `Ran job ${job.name} successfully`);
-				this.jobStatistics[job.name].successful += 1;
-				if (
-					config.debug &&
-					config.debug.stationIssue === true &&
-					config.debug.captureJobs &&
-					config.debug.captureJobs.indexOf(job.name) !== -1
-				) {
-					this.moduleManager.debugJobs.completed.push({
-						status: "success",
-						job,
-						response
-					});
-				}
-				job.onFinish.resolve(response);
-			})
-			.catch(error => {
-				this.log("INFO", `Running job ${job.name} failed`);
-				this.jobStatistics[job.name].failed += 1;
-				if (
-					config.debug &&
-					config.debug.stationIssue === true &&
-					config.debug.captureJobs &&
-					config.debug.captureJobs.indexOf(job.name) !== -1
-				) {
-					this.moduleManager.debugJobs.completed.push({
-						status: "error",
-						job,
-						error
-					});
-				}
-				job.onFinish.reject(error);
-			})
-			.finally(() => {
-				const endTime = Date.now();
-				const executionTime = endTime - startTime;
-				this.jobStatistics[job.name].total += 1;
-				this.jobStatistics[job.name].averageTiming.update(executionTime);
-				this.runningJobs.splice(this.runningJobs.indexOf(job), 1);
-				cb();
-			});
+	_runJob(job, options) {
+		// if (!options.isQuiet)
+		this.log("INFO", `Running job ${job.name} (${job.toString()})`);
+		return new Promise((resolve, reject) => {
+			const startTime = Date.now();
+
+			job.setStatus("RUNNING");
+			this.runningJobs.push(job);
+
+			this[job.name]
+				.apply(job, [job.payload])
+				.then(response => {
+					// if (!options.isQuiet)
+					this.log("INFO", `Ran job ${job.name} (${job.toString()}) successfully`);
+					job.setStatus("FINISHED");
+					this.jobStatistics[job.name].successful += 1;
+					if (
+						config.debug &&
+						config.debug.stationIssue === true &&
+						config.debug.captureJobs &&
+						config.debug.captureJobs.indexOf(job.name) !== -1
+					) {
+						this.moduleManager.debugJobs.completed.push({
+							status: "success",
+							job,
+							response
+						});
+					}
+					job.onFinish.resolve(response);
+				})
+				.catch(error => {
+					this.log("INFO", `Running job ${job.name} (${job.toString()}) failed`);
+					job.setStatus("FINISHED");
+					this.jobStatistics[job.name].failed += 1;
+					if (
+						config.debug &&
+						config.debug.stationIssue === true &&
+						config.debug.captureJobs &&
+						config.debug.captureJobs.indexOf(job.name) !== -1
+					) {
+						this.moduleManager.debugJobs.completed.push({
+							status: "error",
+							job,
+							error
+						});
+					}
+					job.onFinish.reject(error);
+				})
+				.finally(() => {
+					const endTime = Date.now();
+					const executionTime = endTime - startTime;
+					this.jobStatistics[job.name].total += 1;
+					this.jobStatistics[job.name].averageTiming.update(executionTime);
+					this.runningJobs.splice(this.runningJobs.indexOf(job), 1);
+					resolve();
+				});
+		});
 	}
 }

+ 44 - 12
backend/index.js

@@ -3,6 +3,10 @@ import "./loadEnvVariables.js";
 import util from "util";
 import config from "config";
 
+Array.prototype.remove = function (item) {
+	this.splice(this.indexOf(item), 1);
+};
+
 process.on("uncaughtException", err => {
 	if (err.code === "ECONNREFUSED" || err.code === "UNCERTAIN_STATE") return;
 	console.log(`UNCAUGHT EXCEPTION: ${err.stack}`);
@@ -245,6 +249,7 @@ class ModuleManager {
 			all: [],
 			completed: []
 		};
+		this.name = "MODULE_MANAGER";
 	}
 
 	/**
@@ -253,7 +258,7 @@ class ModuleManager {
 	 * @param {string} moduleName - the name of the module (also needs to be the same as the filename of a module located in the logic folder or "logic/moduleName/index.js")
 	 */
 	async addModule(moduleName) {
-		console.log("add module", moduleName);
+		this.log("INFO", "Adding module", moduleName);
 		// import(`./logic/${moduleName}`).then(Module => {
 		// 	// eslint-disable-next-line new-cap
 
@@ -320,8 +325,8 @@ class ModuleManager {
 		if (this.modulesNotInitialized.indexOf(module) !== -1) {
 			this.modulesNotInitialized.splice(this.modulesNotInitialized.indexOf(module), 1);
 
-			console.log(
-				"MODULE_MANAGER",
+			this.log(
+				"INFO",
 				`Initialized: ${Object.keys(this.modules).length - this.modulesNotInitialized.length}/${
 					Object.keys(this.modules).length
 				}.`
@@ -338,7 +343,7 @@ class ModuleManager {
 	 */
 	onFail(module) {
 		if (this.modulesNotInitialized.indexOf(module) !== -1) {
-			console.log("A module failed to initialize!");
+			this.log("ERROR", "A module failed to initialize!");
 		}
 	}
 
@@ -347,14 +352,41 @@ class ModuleManager {
 	 *
 	 */
 	onAllModulesInitialized() {
-		console.log("All modules initialized!");
-		this.modules.discord.runJob("SEND_ADMIN_ALERT_MESSAGE", {
-			message: "The backend server started successfully.",
-			color: "#00AA00",
-			type: "Startup",
-			critical: false,
-			extraFields: []
-		});
+		this.log("INFO", "All modules initialized!");
+		if (this.modules.discord) {
+			this.modules.discord.runJob("SEND_ADMIN_ALERT_MESSAGE", {
+				message: "The backend server started successfully.",
+				color: "#00AA00",
+				type: "Startup",
+				critical: false,
+				extraFields: []
+			});
+		} else this.log("INFO", "No Discord module, so not sending an admin alert message.");
+	}
+
+	/**
+	 * Creates a new log message
+	 *
+	 * @param {...any} args - anything to be included in the log message, the first argument is the type of log
+	 */
+	log(...args) {
+		const _arguments = Array.from(args);
+		const type = _arguments[0];
+
+		_arguments.splice(0, 1);
+		const start = `|${this.name.toUpperCase()}|`;
+		const numberOfSpacesNeeded = 20 - start.length;
+		_arguments.unshift(`${start}${Array(numberOfSpacesNeeded).join(" ")}`);
+
+		if (type === "INFO") {
+			_arguments[0] += "\x1b[36m";
+			_arguments.push("\x1b[0m");
+			console.log.apply(null, _arguments);
+		} else if (type === "ERROR") {
+			_arguments[0] += "\x1b[31m";
+			_arguments.push("\x1b[0m");
+			console.error.apply(null, _arguments);
+		}
 	}
 }
 

+ 24 - 13
backend/logic/activities.js

@@ -2,10 +2,16 @@ import async from "async";
 
 import CoreClass from "../core";
 
-class ActivitiesModule extends CoreClass {
+let ActivitiesModule;
+let DBModule;
+let UtilsModule;
+
+class _ActivitiesModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("activities");
+
+		ActivitiesModule = this;
 	}
 
 	/**
@@ -15,9 +21,8 @@ class ActivitiesModule extends CoreClass {
 	 */
 	initialize() {
 		return new Promise(resolve => {
-			this.db = this.moduleManager.modules.db;
-			this.io = this.moduleManager.modules.io;
-			this.utils = this.moduleManager.modules.utils;
+			DBModule = this.moduleManager.modules.db;
+			UtilsModule = this.moduleManager.modules.utils;
 
 			resolve();
 		});
@@ -38,8 +43,7 @@ class ActivitiesModule extends CoreClass {
 			async.waterfall(
 				[
 					next => {
-						this.db
-							.runJob("GET_MODEL", { modelName: "activity" })
+						DBModule.runJob("GET_MODEL", { modelName: "activity" }, this)
 							.then(res => next(null, res))
 							.catch(next);
 					},
@@ -57,10 +61,13 @@ class ActivitiesModule extends CoreClass {
 					},
 
 					(activity, next) => {
-						this.utils
-							.runJob("SOCKETS_FROM_USER", {
+						UtilsModule.runJob(
+							"SOCKETS_FROM_USER",
+							{
 								userId: activity.userId
-							})
+							},
+							this
+						)
 							.then(response => {
 								response.sockets.forEach(socket => {
 									socket.emit("event:activity.create", activity);
@@ -72,9 +79,13 @@ class ActivitiesModule extends CoreClass {
 				],
 				async (err, activity) => {
 					if (err) {
-						err = await this.utils.runJob("GET_ERROR", {
-							error: err
-						});
+						err = await UtilsModule.runJob(
+							"GET_ERROR",
+							{
+								error: err
+							},
+							this
+						);
 						reject(new Error(err));
 					} else {
 						resolve({ activity });
@@ -85,4 +96,4 @@ class ActivitiesModule extends CoreClass {
 	}
 }
 
-export default new ActivitiesModule();
+export default new _ActivitiesModule();

+ 40 - 36
backend/logic/api.js

@@ -4,10 +4,21 @@ import async from "async";
 
 import CoreClass from "../core";
 
-class APIModule extends CoreClass {
+// let APIModule;
+let AppModule;
+// let DBModule;
+let PlaylistsModule;
+let UtilsModule;
+let PunishmentsModule;
+let CacheModule;
+// let NotificationsModule;
+
+class _APIModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("api");
+
+		// APIModule = this;
 	}
 
 	/**
@@ -17,14 +28,13 @@ class APIModule extends CoreClass {
 	 */
 	initialize() {
 		return new Promise((resolve, reject) => {
-			this.app = this.moduleManager.modules.app;
-			this.stations = this.moduleManager.modules.stations;
-			this.db = this.moduleManager.modules.db;
-			this.playlists = this.moduleManager.modules.playlists;
-			this.utils = this.moduleManager.modules.utils;
-			this.punishments = this.moduleManager.modules.punishments;
-			this.cache = this.moduleManager.modules.cache;
-			this.notifications = this.moduleManager.modules.notifications;
+			AppModule = this.moduleManager.modules.app;
+			// DBModule = this.moduleManager.modules.db;
+			PlaylistsModule = this.moduleManager.modules.playlists;
+			UtilsModule = this.moduleManager.modules.utils;
+			PunishmentsModule = this.moduleManager.modules.punishments;
+			CacheModule = this.moduleManager.modules.cache;
+			// NotificationsModule = this.moduleManager.modules.notifications;
 
 			const SIDname = config.get("cookie.SIDname");
 
@@ -33,10 +43,9 @@ class APIModule extends CoreClass {
 				async.waterfall(
 					[
 						next => {
-							this.utils
-								.runJob("PARSE_COOKIES", {
-									cookieString: req.headers.cookie
-								})
+							UtilsModule.runJob("PARSE_COOKIES", {
+								cookieString: req.headers.cookie
+							})
 								.then(res => {
 									SID = res[SIDname];
 									next(null);
@@ -50,9 +59,9 @@ class APIModule extends CoreClass {
 						},
 
 						next => {
-							this.cache
-								.runJob("HGET", { table: "sessions", key: SID })
-								.then(session => next(null, session));
+							CacheModule.runJob("HGET", { table: "sessions", key: SID }).then(session =>
+								next(null, session)
+							);
 						},
 
 						(session, next) => {
@@ -62,21 +71,18 @@ class APIModule extends CoreClass {
 
 							req.session = session;
 
-							return this.cache
-								.runJob("HSET", {
-									table: "sessions",
-									key: SID,
-									value: session
-								})
-								.then(session => {
-									next(null, session);
-								});
+							return CacheModule.runJob("HSET", {
+								table: "sessions",
+								key: SID,
+								value: session
+							}).then(session => {
+								next(null, session);
+							});
 						},
 
 						(res, next) => {
 							// check if a session's user / IP is banned
-							this.punishments
-								.runJob("GET_PUNISHMENTS", {})
+							PunishmentsModule.runJob("GET_PUNISHMENTS", {})
 								.then(punishments => {
 									const isLoggedIn = !!(req.session && req.session.refreshDate);
 									const userId = isLoggedIn ? req.session.userId : null;
@@ -111,8 +117,7 @@ class APIModule extends CoreClass {
 				);
 			};
 
-			this.app
-				.runJob("GET_APP", {})
+			AppModule.runJob("GET_APP", {})
 				.then(response => {
 					response.app.get("/", (req, res) => {
 						res.json({
@@ -123,8 +128,7 @@ class APIModule extends CoreClass {
 
 					response.app.get("/export/privatePlaylist/:playlistId", isLoggedIn, (req, res) => {
 						const { playlistId } = req.params;
-						this.playlists
-							.runJob("GET_PLAYLIST", { playlistId })
+						PlaylistsModule.runJob("GET_PLAYLIST", { playlistId })
 							.then(playlist => {
 								if (playlist.createdBy === req.session.userId)
 									res.json({ status: "success", playlist });
@@ -138,7 +142,7 @@ class APIModule extends CoreClass {
 					// response.app.get("/debug_station", async (req, res) => {
 					//     const responseObject = {};
 
-					//     const stationModel = await this.db.runJob(
+					//     const stationModel = await DBModule.runJob(
 					//         "GET_MODEL",
 					//         {
 					//             modelName: "station",
@@ -158,7 +162,7 @@ class APIModule extends CoreClass {
 					//         },
 
 					//         next => {
-					//             this.cache
+					//             CacheModule
 					//                 .runJob("HGETALL", { table: "stations" })
 					//                 .then(stations => {
 					//                     next(null, stations);
@@ -186,7 +190,7 @@ class APIModule extends CoreClass {
 					//         },
 
 					//         next => {
-					//             this.notifications.pub.keys("*", next);
+					//             NotificationModule.pub.keys("*", next);
 					//         },
 
 					//         (redisKeys, next) => {
@@ -195,7 +199,7 @@ class APIModule extends CoreClass {
 					//                 ttl: {}
 					//             };
 					//             async.eachLimit(redisKeys, 1, (redisKey, next) => {
-					//                 this.notifications.pub.ttl(redisKey, (err, ttl) => {
+					//                 NotificationModule.pub.ttl(redisKey, (err, ttl) => {
 					//                     responseObject.redis.ttl[redisKey] = ttl;
 					//                     next(err);
 					//                 })
@@ -246,4 +250,4 @@ class APIModule extends CoreClass {
 	}
 }
 
-export default new APIModule();
+export default new _APIModule();

+ 44 - 42
backend/logic/app.js

@@ -12,10 +12,19 @@ import CoreClass from "../core";
 
 const { OAuth2 } = oauth;
 
-class AppModule extends CoreClass {
+let AppModule;
+let MailModule;
+let CacheModule;
+let DBModule;
+let ActivitiesModule;
+let UtilsModule;
+
+class _AppModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("app");
+
+		AppModule = this;
 	}
 
 	/**
@@ -25,12 +34,11 @@ class AppModule extends CoreClass {
 	 */
 	initialize() {
 		return new Promise(resolve => {
-			const { mail } = this.moduleManager.modules;
-			const { cache } = this.moduleManager.modules;
-			const { db } = this.moduleManager.modules;
-			const { activities } = this.moduleManager.modules;
-
-			this.utils = this.moduleManager.modules.utils;
+			MailModule = this.moduleManager.modules.mail;
+			CacheModule = this.moduleManager.modules.cache;
+			DBModule = this.moduleManager.modules.db;
+			ActivitiesModule = this.moduleManager.modules.activities;
+			UtilsModule = this.moduleManager.modules.utils;
 
 			const app = (this.app = express());
 			const SIDname = config.get("cookie.SIDname");
@@ -42,7 +50,7 @@ class AppModule extends CoreClass {
 			app.use(bodyParser.urlencoded({ extended: true }));
 
 			let userModel;
-			db.runJob("GET_MODEL", { modelName: "user" })
+			DBModule.runJob("GET_MODEL", { modelName: "user" })
 				.then(model => {
 					userModel = model;
 				})
@@ -126,7 +134,7 @@ class AppModule extends CoreClass {
 
 				const { state } = req.query;
 
-				const verificationToken = await this.utils.runJob("GENERATE_RANDOM_STRING", { length: 64 });
+				const verificationToken = await UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 64 });
 
 				return async.waterfall(
 					[
@@ -165,11 +173,10 @@ class AppModule extends CoreClass {
 								return async.waterfall(
 									[
 										next => {
-											cache
-												.runJob("HGET", {
-													table: "sessions",
-													key: state
-												})
+											CacheModule.runJob("HGET", {
+												table: "sessions",
+												key: state
+											})
 												.then(session => next(null, session))
 												.catch(next);
 										},
@@ -203,7 +210,7 @@ class AppModule extends CoreClass {
 										},
 
 										user => {
-											cache.runJob("PUB", {
+											CacheModule.runJob("PUB", {
 												channel: "user.linkGithub",
 												value: user._id
 											});
@@ -260,11 +267,9 @@ class AppModule extends CoreClass {
 						},
 
 						(user, next) => {
-							this.utils
-								.runJob("GENERATE_RANDOM_STRING", {
-									length: 12
-								})
-								.then(_id => next(null, user, _id));
+							UtilsModule.runJob("GENERATE_RANDOM_STRING", {
+								length: 12
+							}).then(_id => next(null, user, _id));
 						},
 
 						(user, _id, next) => {
@@ -294,14 +299,12 @@ class AppModule extends CoreClass {
 
 						// generate the url for gravatar avatar
 						(user, next) => {
-							this.utils
-								.runJob("CREATE_GRAVATAR", {
-									email: user.email.address
-								})
-								.then(url => {
-									user.avatar = { type: "gravatar", url };
-									next(null, user);
-								});
+							UtilsModule.runJob("CREATE_GRAVATAR", {
+								email: user.email.address
+							}).then(url => {
+								user.avatar = { type: "gravatar", url };
+								next(null, user);
+							});
 						},
 
 						// save the new user to the database
@@ -311,7 +314,7 @@ class AppModule extends CoreClass {
 
 						// add the activity of account creation
 						(user, next) => {
-							activities.runJob("ADD_ACTIVITY", {
+							ActivitiesModule.runJob("ADD_ACTIVITY", {
 								userId: user._id,
 								activityType: "created_account"
 							});
@@ -319,7 +322,7 @@ class AppModule extends CoreClass {
 						},
 
 						(user, next) => {
-							mail.runJob("GET_SCHEMA", {
+							MailModule.runJob("GET_SCHEMA", {
 								schemaName: "verifyEmail"
 							}).then(verifyEmailSchema => {
 								verifyEmailSchema(address, body.login, user.email.verificationToken, err => {
@@ -330,7 +333,7 @@ class AppModule extends CoreClass {
 					],
 					async (err, userId) => {
 						if (err && err !== true) {
-							err = await this.utils.runJob("GET_ERROR", {
+							err = await UtilsModule.runJob("GET_ERROR", {
 								error: err
 							});
 
@@ -343,17 +346,16 @@ class AppModule extends CoreClass {
 							return redirectOnErr(res, err);
 						}
 
-						const sessionId = await this.utils.runJob("GUID", {});
-						const sessionSchema = await cache.runJob("GET_SCHEMA", {
+						const sessionId = await UtilsModule.runJob("GUID", {});
+						const sessionSchema = await CacheModule.runJob("GET_SCHEMA", {
 							schemaName: "session"
 						});
 
-						return cache
-							.runJob("HSET", {
-								table: "sessions",
-								key: sessionId,
-								value: sessionSchema(sessionId, userId)
-							})
+						return CacheModule.runJob("HSET", {
+							table: "sessions",
+							key: sessionId,
+							value: sessionSchema(sessionId, userId)
+						})
 							.then(() => {
 								const date = new Date();
 								date.setTime(new Date().getTime() + 2 * 365 * 24 * 60 * 60 * 1000);
@@ -449,7 +451,7 @@ class AppModule extends CoreClass {
 	 */
 	SERVER() {
 		return new Promise(resolve => {
-			resolve(this.server);
+			resolve(AppModule.server);
 		});
 	}
 
@@ -460,7 +462,7 @@ class AppModule extends CoreClass {
 	 */
 	GET_APP() {
 		return new Promise(resolve => {
-			resolve({ app: this.app });
+			resolve({ app: AppModule.app });
 		});
 	}
 
@@ -472,4 +474,4 @@ class AppModule extends CoreClass {
 	// }
 }
 
-export default new AppModule();
+export default new _AppModule();

+ 17 - 13
backend/logic/cache/index.js

@@ -9,10 +9,14 @@ import CoreClass from "../../core";
 const pubs = {};
 const subs = {};
 
-class CacheModule extends CoreClass {
+let CacheModule;
+
+class _CacheModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("cache");
+
+		CacheModule = this;
 	}
 
 	/**
@@ -84,8 +88,8 @@ class CacheModule extends CoreClass {
 	 */
 	QUIT() {
 		return new Promise(resolve => {
-			if (this.client.connected) {
-				this.client.quit();
+			if (CacheModule.client.connected) {
+				CacheModule.client.quit();
 				Object.keys(pubs).forEach(channel => pubs[channel].quit());
 				Object.keys(subs).forEach(channel => subs[channel].client.quit());
 			}
@@ -113,7 +117,7 @@ class CacheModule extends CoreClass {
 			// automatically stringify objects and arrays into JSON
 			if (["object", "array"].includes(typeof value)) value = JSON.stringify(value);
 
-			this.client.hset(payload.table, key, value, err => {
+			CacheModule.client.hset(payload.table, key, value, err => {
 				if (err) return reject(new Error(err));
 				return resolve(JSON.parse(value));
 			});
@@ -136,7 +140,7 @@ class CacheModule extends CoreClass {
 
 			if (mongoose.Types.ObjectId.isValid(key)) key = key.toString();
 
-			this.client.hget(payload.table, key, (err, value) => {
+			CacheModule.client.hget(payload.table, key, (err, value) => {
 				if (err) return reject(new Error(err));
 				try {
 					value = JSON.parse(value);
@@ -167,7 +171,7 @@ class CacheModule extends CoreClass {
 
 			if (mongoose.Types.ObjectId.isValid(key)) key = key.toString();
 
-			this.client.hdel(payload.table, key, err => {
+			CacheModule.client.hdel(payload.table, key, err => {
 				if (err) return reject(new Error(err));
 				return resolve();
 			});
@@ -185,7 +189,7 @@ class CacheModule extends CoreClass {
 	HGETALL(payload) {
 		// table, cb, parseJson = true
 		return new Promise((resolve, reject) => {
-			this.client.hgetall(payload.table, (err, obj) => {
+			CacheModule.client.hgetall(payload.table, (err, obj) => {
 				if (err) return reject(new Error(err));
 				if (obj)
 					Object.keys(obj).forEach(key => {
@@ -211,7 +215,7 @@ class CacheModule extends CoreClass {
 		// channel, value, stringifyJson = true
 		return new Promise((resolve, reject) => {
 			/* if (pubs[channel] === undefined) {
-            pubs[channel] = redis.createClient({ url: this.url });
+            pubs[channel] = redis.createClient({ url: CacheModule.url });
             pubs[channel].on('error', (err) => console.error);
             } */
 
@@ -220,7 +224,7 @@ class CacheModule extends CoreClass {
 			if (["object", "array"].includes(typeof value)) value = JSON.stringify(value);
 
 			// pubs[channel].publish(channel, value);
-			this.client.publish(payload.channel, value, err => {
+			CacheModule.client.publish(payload.channel, value, err => {
 				if (err) reject(err);
 				else resolve();
 			});
@@ -241,8 +245,8 @@ class CacheModule extends CoreClass {
 			if (subs[payload.channel] === undefined) {
 				subs[payload.channel] = {
 					client: redis.createClient({
-						url: this.url,
-						password: this.password
+						url: CacheModule.url,
+						password: CacheModule.password
 					}),
 					cbs: []
 				};
@@ -275,9 +279,9 @@ class CacheModule extends CoreClass {
 	 */
 	GET_SCHEMA(payload) {
 		return new Promise(resolve => {
-			resolve(this.schemas[payload.schemaName]);
+			resolve(CacheModule.schemas[payload.schemaName]);
 		});
 	}
 }
 
-export default new CacheModule();
+export default new _CacheModule();

+ 8 - 4
backend/logic/db/index.js

@@ -16,10 +16,14 @@ const isLength = (string, min, max) => !(typeof string !== "string" || string.le
 
 mongoose.Promise = bluebird;
 
-class DBModule extends CoreClass {
+let DBModule;
+
+class _DBModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("db");
+
+		DBModule = this;
 	}
 
 	/**
@@ -271,7 +275,7 @@ class DBModule extends CoreClass {
 	 */
 	GET_MODEL(payload) {
 		return new Promise(resolve => {
-			resolve(this.models[payload.modelName]);
+			resolve(DBModule.models[payload.modelName]);
 		});
 	}
 
@@ -284,7 +288,7 @@ class DBModule extends CoreClass {
 	 */
 	GET_SCHEMA(payload) {
 		return new Promise(resolve => {
-			resolve(this.schemas[payload.schemaName]);
+			resolve(DBModule.schemas[payload.schemaName]);
 		});
 	}
 
@@ -299,4 +303,4 @@ class DBModule extends CoreClass {
 	}
 }
 
-export default new DBModule();
+export default new _DBModule();

+ 9 - 3
backend/logic/discord.js

@@ -3,10 +3,14 @@ import Discord from "discord.js";
 
 import CoreClass from "../core";
 
-class DiscordModule extends CoreClass {
+let DiscordModule;
+
+class _DiscordModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("discord");
+
+		DiscordModule = this;
 	}
 
 	/**
@@ -67,7 +71,9 @@ class DiscordModule extends CoreClass {
 	 */
 	SEND_ADMIN_ALERT_MESSAGE(payload) {
 		return new Promise((resolve, reject) => {
-			const channel = this.client.channels.find(channel => channel.id === this.adminAlertChannelId);
+			const channel = DiscordModule.client.channels.find(
+				channel => channel.id === DiscordModule.adminAlertChannelId
+			);
 
 			if (channel !== null) {
 				const richEmbed = new Discord.RichEmbed();
@@ -111,4 +117,4 @@ class DiscordModule extends CoreClass {
 	}
 }
 
-export default new DiscordModule();
+export default new _DiscordModule();

+ 42 - 40
backend/logic/io.js

@@ -9,10 +9,19 @@ import socketio from "socket.io";
 import actions from "./actions";
 import CoreClass from "../core";
 
-class IOModule extends CoreClass {
+let IOModule;
+let AppModule;
+let CacheModule;
+let UtilsModule;
+let DBModule;
+let PunishmentsModule;
+
+class _IOModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("io");
+
+		IOModule = this;
 	}
 
 	/**
@@ -23,18 +32,18 @@ class IOModule extends CoreClass {
 	async initialize() {
 		this.setStage(1);
 
-		const { app } = this.moduleManager.modules;
-		const { cache } = this.moduleManager.modules;
-		const { utils } = this.moduleManager.modules;
-		const { db } = this.moduleManager.modules;
-		const { punishments } = this.moduleManager.modules;
+		AppModule = this.moduleManager.modules.app;
+		CacheModule = this.moduleManager.modules.cache;
+		UtilsModule = this.moduleManager.modules.utils;
+		DBModule = this.moduleManager.modules.db;
+		PunishmentsModule = this.moduleManager.modules.punishments;
 
 		this.setStage(2);
 
 		const SIDname = config.get("cookie.SIDname");
 
 		// TODO: Check every 30s/, for all sockets, if they are still allowed to be in the rooms they are in, and on socket at all (permission changing/banning)
-		const server = await app.runJob("SERVER");
+		const server = await AppModule.runJob("SERVER");
 		this._io = socketio(server);
 
 		return new Promise(resolve => {
@@ -57,14 +66,12 @@ class IOModule extends CoreClass {
 				return async.waterfall(
 					[
 						next => {
-							utils
-								.runJob("PARSE_COOKIES", {
-									cookieString: socket.request.headers.cookie
-								})
-								.then(res => {
-									SID = res[SIDname];
-									next(null);
-								});
+							UtilsModule.runJob("PARSE_COOKIES", {
+								cookieString: socket.request.headers.cookie
+							}).then(res => {
+								SID = res[SIDname];
+								next(null);
+							});
 						},
 
 						next => {
@@ -73,7 +80,7 @@ class IOModule extends CoreClass {
 						},
 
 						next => {
-							cache.runJob("HGET", { table: "sessions", key: SID }).then(session => {
+							CacheModule.runJob("HGET", { table: "sessions", key: SID }).then(session => {
 								next(null, session);
 							});
 						},
@@ -85,21 +92,18 @@ class IOModule extends CoreClass {
 
 							socket.session = session;
 
-							return cache
-								.runJob("HSET", {
-									table: "sessions",
-									key: SID,
-									value: session
-								})
-								.then(session => {
-									next(null, session);
-								});
+							return CacheModule.runJob("HSET", {
+								table: "sessions",
+								key: SID,
+								value: session
+							}).then(session => {
+								next(null, session);
+							});
 						},
 
 						(res, next) => {
 							// check if a session's user / IP is banned
-							punishments
-								.runJob("GET_PUNISHMENTS", {})
+							PunishmentsModule.runJob("GET_PUNISHMENTS", {})
 								.then(punishments => {
 									const isLoggedIn = !!(socket.session && socket.session.refreshDate);
 									const userId = isLoggedIn ? socket.session.userId : null;
@@ -202,14 +206,13 @@ class IOModule extends CoreClass {
 				socket.on("error", console.error);
 
 				if (socket.session.sessionId) {
-					cache
-						.runJob("HGET", {
-							table: "sessions",
-							key: socket.session.sessionId
-						})
+					CacheModule.runJob("HGET", {
+						table: "sessions",
+						key: socket.session.sessionId
+					})
 						.then(session => {
 							if (session && session.userId) {
-								db.runJob("GET_MODEL", { modelName: "user" }).then(userModel => {
+								DBModule.runJob("GET_MODEL", { modelName: "user" }).then(userModel => {
 									userModel.findOne({ _id: session.userId }, (err, user) => {
 										if (err || !user) return socket.emit("ready", false);
 
@@ -257,11 +260,10 @@ class IOModule extends CoreClass {
 							this.log("INFO", "IO_ACTION", `A user executed an action. Action: ${namespace}.${action}.`);
 
 							// load the session from the cache
-							cache
-								.runJob("HGET", {
-									table: "sessions",
-									key: socket.session.sessionId
-								})
+							CacheModule.runJob("HGET", {
+								table: "sessions",
+								key: socket.session.sessionId
+							})
 								.then(session => {
 									// make sure the sockets sessionId isn't set if there is no session
 									if (socket.session.sessionId && session === null) delete socket.session.sessionId;
@@ -321,9 +323,9 @@ class IOModule extends CoreClass {
 	 */
 	IO() {
 		return new Promise(resolve => {
-			resolve(this._io);
+			resolve(IOModule._io);
 		});
 	}
 }
 
-export default new IOModule();
+export default new _IOModule();

+ 9 - 5
backend/logic/mail/index.js

@@ -4,10 +4,14 @@ import Mailgun from "mailgun-js";
 
 import CoreClass from "../../core";
 
-class MailModule extends CoreClass {
+let MailModule;
+
+class _MailModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("mail");
+
+		MailModule = this;
 	}
 
 	/**
@@ -47,8 +51,8 @@ class MailModule extends CoreClass {
 	 */
 	SEND_MAIL(payload) {
 		return new Promise(resolve => {
-			if (this.enabled)
-				this.mailgun.messages().send(payload.data, () => {
+			if (MailModule.enabled)
+				MailModule.mailgun.messages().send(payload.data, () => {
 					resolve();
 				});
 			else resolve();
@@ -64,9 +68,9 @@ class MailModule extends CoreClass {
 	 */
 	GET_SCHEMA(payload) {
 		return new Promise(resolve => {
-			resolve(this.schemas[payload.schemaName]);
+			resolve(MailModule.schemas[payload.schemaName]);
 		});
 	}
 }
 
-export default new MailModule();
+export default new _MailModule();

+ 38 - 20
backend/logic/notifications.js

@@ -4,14 +4,18 @@ import crypto from "crypto";
 import redis from "redis";
 
 import CoreClass from "../core";
-import utils from "./utils";
 
-class NotificationsModule extends CoreClass {
+let NotificationsModule;
+let UtilsModule;
+
+class _NotificationsModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("notifications");
 
 		this.subscriptions = [];
+
+		NotificationsModule = this;
 	}
 
 	/**
@@ -24,6 +28,8 @@ class NotificationsModule extends CoreClass {
 			const url = (this.url = config.get("redis").url);
 			const password = (this.password = config.get("redis").password);
 
+			UtilsModule = this.moduleManager.modules.utils;
+
 			this.pub = redis.createClient({
 				url,
 				password,
@@ -90,9 +96,13 @@ class NotificationsModule extends CoreClass {
 
 				this.pub.config("GET", "notify-keyspace-events", async (err, response) => {
 					if (err) {
-						const formattedErr = await utils.runJob("GET_ERROR", {
-							error: err
-						});
+						const formattedErr = await UtilsModule.runJob(
+							"GET_ERROR",
+							{
+								error: err
+							},
+							this
+						);
 						this.log(
 							"ERROR",
 							"NOTIFICATIONS_INITIALIZE",
@@ -159,14 +169,14 @@ class NotificationsModule extends CoreClass {
 	SCHEDULE(payload) {
 		return new Promise((resolve, reject) => {
 			const time = Math.round(payload.time);
-			this.log(
+			NotificationsModule.log(
 				"STATION_ISSUE",
 				`SCHEDULE - Time: ${time}; Name: ${payload.name}; Key: ${crypto
 					.createHash("md5")
 					.update(`_notification:${payload.name}_`)
 					.digest("hex")}; StationId: ${payload.station._id}; StationName: ${payload.station.name}`
 			);
-			this.pub.set(
+			NotificationsModule.pub.set(
 				crypto.createHash("md5").update(`_notification:${payload.name}_`).digest("hex"),
 				"",
 				"PX",
@@ -191,20 +201,25 @@ class NotificationsModule extends CoreClass {
 	 */
 	SUBSCRIBE(payload) {
 		return new Promise(resolve => {
-			this.log(
+			NotificationsModule.log(
 				"STATION_ISSUE",
 				`SUBSCRIBE - Name: ${payload.name}; Key: ${crypto
 					.createHash("md5")
 					.update(`_notification:${payload.name}_`)
 					.digest("hex")}, StationId: ${payload.station._id}; StationName: ${payload.station.name}; Unique: ${
 					payload.unique
-				}; SubscriptionExists: ${!!this.subscriptions.find(
+				}; SubscriptionExists: ${!!NotificationsModule.subscriptions.find(
 					subscription => subscription.originalName === payload.name
 				)};`
 			);
-			if (payload.unique && !!this.subscriptions.find(subscription => subscription.originalName === payload.name))
+			if (
+				payload.unique &&
+				!!NotificationsModule.subscriptions.find(subscription => subscription.originalName === payload.name)
+			)
 				return resolve({
-					subscription: this.subscriptions.find(subscription => subscription.originalName === payload.name)
+					subscription: NotificationsModule.subscriptions.find(
+						subscription => subscription.originalName === payload.name
+					)
 				});
 
 			const subscription = {
@@ -213,7 +228,7 @@ class NotificationsModule extends CoreClass {
 				cb: payload.cb
 			};
 
-			this.subscriptions.push(subscription);
+			NotificationsModule.subscriptions.push(subscription);
 
 			return resolve({ subscription });
 		});
@@ -229,8 +244,8 @@ class NotificationsModule extends CoreClass {
 	REMOVE(payload) {
 		// subscription
 		return new Promise(resolve => {
-			const index = this.subscriptions.indexOf(payload.subscription);
-			if (index) this.subscriptions.splice(index, 1);
+			const index = NotificationsModule.subscriptions.indexOf(payload.subscription);
+			if (index) NotificationsModule.subscriptions.splice(index, 1);
 			resolve();
 		});
 	}
@@ -245,19 +260,22 @@ class NotificationsModule extends CoreClass {
 	UNSCHEDULE(payload) {
 		// name
 		return new Promise((resolve, reject) => {
-			this.log(
+			NotificationsModule.log(
 				"STATION_ISSUE",
 				`UNSCHEDULE - Name: ${payload.name}; Key: ${crypto
 					.createHash("md5")
 					.update(`_notification:${payload.name}_`)
 					.digest("hex")}`
 			);
-			this.pub.del(crypto.createHash("md5").update(`_notification:${payload.name}_`).digest("hex"), err => {
-				if (err) reject(err);
-				else resolve();
-			});
+			NotificationsModule.pub.del(
+				crypto.createHash("md5").update(`_notification:${payload.name}_`).digest("hex"),
+				err => {
+					if (err) reject(err);
+					else resolve();
+				}
+			);
 		});
 	}
 }
 
-export default new NotificationsModule();
+export default new _NotificationsModule();

+ 63 - 45
backend/logic/playlists.js

@@ -2,10 +2,17 @@ import async from "async";
 
 import CoreClass from "../core";
 
-class PlaylistsModule extends CoreClass {
+let PlaylistsModule;
+let CacheModule;
+let DBModule;
+let UtilsModule;
+
+class _PlaylistsModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("playlists");
+
+		PlaylistsModule = this;
 	}
 
 	/**
@@ -16,12 +23,12 @@ class PlaylistsModule extends CoreClass {
 	async initialize() {
 		this.setStage(1);
 
-		this.cache = this.moduleManager.modules.cache;
-		this.db = this.moduleManager.modules.db;
-		this.utils = this.moduleManager.modules.utils;
+		CacheModule = this.moduleManager.modules.cache;
+		DBModule = this.moduleManager.modules.db;
+		UtilsModule = this.moduleManager.modules.utils;
 
-		const playlistModel = await this.db.runJob("GET_MODEL", { modelName: "playlist" });
-		const playlistSchema = await this.cache.runJob("GET_SCHEMA", { schemaName: "playlist" });
+		const playlistModel = await DBModule.runJob("GET_MODEL", { modelName: "playlist" });
+		const playlistSchema = await CacheModule.runJob("GET_SCHEMA", { schemaName: "playlist" });
 
 		this.setStage(2);
 
@@ -30,8 +37,7 @@ class PlaylistsModule extends CoreClass {
 				[
 					next => {
 						this.setStage(3);
-						this.cache
-							.runJob("HGETALL", { table: "playlists" })
+						CacheModule.runJob("HGETALL", { table: "playlists" })
 							.then(playlists => {
 								next(null, playlists);
 							})
@@ -51,11 +57,10 @@ class PlaylistsModule extends CoreClass {
 								playlistModel.findOne({ _id: playlistId }, (err, playlist) => {
 									if (err) next(err);
 									else if (!playlist) {
-										this.cache
-											.runJob("HDEL", {
-												table: "playlists",
-												key: playlistId
-											})
+										CacheModule.runJob("HDEL", {
+											table: "playlists",
+											key: playlistId
+										})
 											.then(() => next())
 											.catch(next);
 									} else next();
@@ -75,12 +80,11 @@ class PlaylistsModule extends CoreClass {
 						async.each(
 							playlists,
 							(playlist, cb) => {
-								this.cache
-									.runJob("HSET", {
-										table: "playlists",
-										key: playlist._id,
-										value: playlistSchema(playlist)
-									})
+								CacheModule.runJob("HSET", {
+									table: "playlists",
+									key: playlist._id,
+									value: playlistSchema(playlist)
+								})
 									.then(() => cb())
 									.catch(next);
 							},
@@ -90,7 +94,7 @@ class PlaylistsModule extends CoreClass {
 				],
 				async err => {
 					if (err) {
-						const formattedErr = await this.utils.runJob("GET_ERROR", {
+						const formattedErr = await UtilsModule.runJob("GET_ERROR", {
 							error: err
 						});
 						reject(new Error(formattedErr));
@@ -111,8 +115,8 @@ class PlaylistsModule extends CoreClass {
 		return new Promise((resolve, reject) => {
 			let playlistModel;
 
-			this.db
-				.runJob("GET_MODEL", { modelName: "playlist" })
+			PlaylistsModule.log("ERROR", "HOW DOES THIS WORK!?!?!??!?!?!?!??!?!??!?!?!?!");
+			DBModule.runJob("GET_MODEL", { modelName: "playlist" }, this)
 				.then(model => {
 					playlistModel = model;
 				})
@@ -121,8 +125,7 @@ class PlaylistsModule extends CoreClass {
 			return async.waterfall(
 				[
 					next => {
-						this.cache
-							.runJob("HGETALL", { table: "playlists" })
+						CacheModule.runJob("HGETALL", { table: "playlists" }, this)
 							.then(playlists => {
 								next(null, playlists);
 							})
@@ -140,11 +143,14 @@ class PlaylistsModule extends CoreClass {
 								playlistModel.findOne({ _id: playlistId }, (err, playlist) => {
 									if (err) next(err);
 									else if (!playlist) {
-										this.cache
-											.runJob("HDEL", {
+										CacheModule.runJob(
+											"HDEL",
+											{
 												table: "playlists",
 												key: playlistId
-											})
+											},
+											this
+										)
 											.then(() => next())
 											.catch(next);
 									} else next();
@@ -155,11 +161,14 @@ class PlaylistsModule extends CoreClass {
 					},
 
 					next => {
-						this.cache
-							.runJob("HGET", {
+						CacheModule.runJob(
+							"HGET",
+							{
 								table: "playlists",
 								key: payload.playlistId
-							})
+							},
+							this
+						)
 							.then(playlist => next(null, playlist))
 							.catch(next);
 					},
@@ -171,12 +180,15 @@ class PlaylistsModule extends CoreClass {
 
 					(playlist, next) => {
 						if (playlist) {
-							this.cache
-								.runJob("HSET", {
+							CacheModule.runJob(
+								"HSET",
+								{
 									table: "playlists",
 									key: payload.playlistId,
 									value: playlist
-								})
+								},
+								this
+							)
 								.then(playlist => {
 									next(null, playlist);
 								})
@@ -204,8 +216,8 @@ class PlaylistsModule extends CoreClass {
 		return new Promise((resolve, reject) => {
 			let playlistModel;
 
-			this.db
-				.runJob("GET_MODEL", { modelName: "playlist" })
+			PunishmentsModule.log("ERROR", "HOW DOES THIS WORK!?!?!??!?!?!?!??!?!??!?!?!?!");
+			DBModule.runJob("GET_MODEL", { modelName: "playlist" }, this)
 				.then(model => {
 					playlistModel = model;
 				})
@@ -219,7 +231,7 @@ class PlaylistsModule extends CoreClass {
 
 					(playlist, next) => {
 						if (!playlist) {
-							this.cache.runJob("HDEL", {
+							CacheModule.runJob("HDEL", {
 								table: "playlists",
 								key: payload.playlistId
 							});
@@ -227,12 +239,15 @@ class PlaylistsModule extends CoreClass {
 							return next("Playlist not found");
 						}
 
-						return this.cache
-							.runJob("HSET", {
+						return CacheModule.runJob(
+							"HSET",
+							{
 								table: "playlists",
 								key: payload.playlistId,
 								value: playlist
-							})
+							},
+							this
+						)
 							.then(playlist => {
 								next(null, playlist);
 							})
@@ -259,8 +274,8 @@ class PlaylistsModule extends CoreClass {
 		return new Promise((resolve, reject) => {
 			let playlistModel;
 
-			this.db
-				.runJob("GET_MODEL", { modelName: "playlist" })
+			PunishmentsModule.log("ERROR", "HOW DOES THIS WORK!?!?!??!?!?!?!??!?!??!?!?!?!");
+			DBModule.runJob("GET_MODEL", { modelName: "playlist" }, this)
 				.then(model => {
 					playlistModel = model;
 				})
@@ -273,11 +288,14 @@ class PlaylistsModule extends CoreClass {
 					},
 
 					(res, next) => {
-						this.cache
-							.runJob("HDEL", {
+						CacheModule.runJob(
+							"HDEL",
+							{
 								table: "playlists",
 								key: payload.playlistId
-							})
+							},
+							this
+						)
 							.then(() => next())
 							.catch(next);
 					}
@@ -291,4 +309,4 @@ class PlaylistsModule extends CoreClass {
 	}
 }
 
-export default new PlaylistsModule();
+export default new _PlaylistsModule();

+ 57 - 44
backend/logic/punishments.js

@@ -2,10 +2,17 @@ import async from "async";
 import mongoose from "mongoose";
 import CoreClass from "../core";
 
-class PunishmentsModule extends CoreClass {
+let PunishmentsModule;
+let CacheModule;
+let DBModule;
+let UtilsModule;
+
+class _PunishmentsModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("punishments");
+
+		PunishmentsModule = this;
 	}
 
 	/**
@@ -16,21 +23,19 @@ class PunishmentsModule extends CoreClass {
 	async initialize() {
 		this.setStage(1);
 
-		this.cache = this.moduleManager.modules.cache;
-		this.db = this.moduleManager.modules.db;
-		this.io = this.moduleManager.modules.io;
-		this.utils = this.moduleManager.modules.utils;
+		CacheModule = this.moduleManager.modules.cache;
+		DBModule = this.moduleManager.modules.db;
+		UtilsModule = this.moduleManager.modules.utils;
 
-		const punishmentModel = await this.db.runJob("GET_MODEL", { modelName: "punishment" });
-		const punishmentSchema = await this.db.runJob("GET_SCHEMA", { schemaName: "punishment" });
+		const punishmentModel = await DBModule.runJob("GET_MODEL", { modelName: "punishment" });
+		const punishmentSchema = await DBModule.runJob("GET_SCHEMA", { schemaName: "punishment" });
 
 		return new Promise((resolve, reject) =>
 			async.waterfall(
 				[
 					next => {
 						this.setStage(2);
-						this.cache
-							.runJob("HGETALL", { table: "punishments" })
+						CacheModule.runJob("HGETALL", { table: "punishments" })
 							.then(punishments => {
 								next(null, punishments);
 							})
@@ -50,11 +55,10 @@ class PunishmentsModule extends CoreClass {
 								punishmentModel.findOne({ _id: punishmentId }, (err, punishment) => {
 									if (err) next(err);
 									else if (!punishment)
-										this.cache
-											.runJob("HDEL", {
-												table: "punishments",
-												key: punishmentId
-											})
+										CacheModule.runJob("HDEL", {
+											table: "punishments",
+											key: punishmentId
+										})
 											.then(() => {
 												cb();
 											})
@@ -78,12 +82,11 @@ class PunishmentsModule extends CoreClass {
 							(punishment, next) => {
 								if (punishment.active === false || punishment.expiresAt < Date.now()) return next();
 
-								return this.cache
-									.runJob("HSET", {
-										table: "punishments",
-										key: punishment._id,
-										value: punishmentSchema(punishment, punishment._id)
-									})
+								return CacheModule.runJob("HSET", {
+									table: "punishments",
+									key: punishment._id,
+									value: punishmentSchema(punishment, punishment._id)
+								})
 									.then(() => next())
 									.catch(next);
 							},
@@ -93,7 +96,7 @@ class PunishmentsModule extends CoreClass {
 				],
 				async err => {
 					if (err) {
-						const formattedErr = await this.utils.runJob("GET_ERROR", { error: err });
+						const formattedErr = await UtilsModule.runJob("GET_ERROR", { error: err });
 						reject(new Error(formattedErr));
 					} else resolve();
 				}
@@ -112,8 +115,7 @@ class PunishmentsModule extends CoreClass {
 			async.waterfall(
 				[
 					next => {
-						this.cache
-							.runJob("HGETALL", { table: "punishments" })
+						CacheModule.runJob("HGETALL", { table: "punishments" }, this)
 							.then(punishmentsObj => next(null, punishmentsObj))
 							.catch(next);
 					},
@@ -143,12 +145,14 @@ class PunishmentsModule extends CoreClass {
 						async.each(
 							punishmentsToRemove,
 							(punishment, next2) => {
-								this.cache
-									.runJob("HDEL", {
+								CacheModule.runJob(
+									"HDEL",
+									{
 										table: "punishments",
 										key: punishment.punishmentId
-									})
-									.finally(() => next2());
+									},
+									this
+								).finally(() => next2());
 							},
 							() => {
 								next(null, punishments);
@@ -175,8 +179,8 @@ class PunishmentsModule extends CoreClass {
 		return new Promise((resolve, reject) => {
 			let punishmentModel;
 
-			this.db
-				.runJob("GET_MODEL", { modelName: "punishment" })
+			PunishmentsModule.log("ERROR", "HOW DOES THIS WORK!?!?!??!?!?!?!??!?!??!?!?!?!");
+			DBModule.runJob("GET_MODEL", { modelName: "punishment" }, this)
 				.then(model => {
 					punishmentModel = model;
 				})
@@ -186,11 +190,14 @@ class PunishmentsModule extends CoreClass {
 				[
 					next => {
 						if (!mongoose.Types.ObjectId.isValid(payload.id)) return next("Id is not a valid ObjectId.");
-						return this.cache
-							.runJob("HGET", {
+						return CacheModule.runJob(
+							"HGET",
+							{
 								table: "punishments",
 								key: payload.id
-							})
+							},
+							this
+						)
 							.then(punishment => next(null, punishment))
 							.catch(next);
 					},
@@ -202,12 +209,15 @@ class PunishmentsModule extends CoreClass {
 
 					(punishment, next) => {
 						if (punishment) {
-							this.cache
-								.runJob("HSET", {
+							CacheModule.runJob(
+								"HSET",
+								{
 									table: "punishments",
 									key: payload.id,
 									value: punishment
-								})
+								},
+								this
+							)
 								.then(punishment => {
 									next(null, punishment);
 								})
@@ -235,7 +245,7 @@ class PunishmentsModule extends CoreClass {
 			async.waterfall(
 				[
 					next => {
-						this.runJob("GET_PUNISHMENTS", {})
+						PunishmentsModule.runJob("GET_PUNISHMENTS", {}, this)
 							.then(punishments => {
 								next(null, punishments);
 							})
@@ -272,15 +282,15 @@ class PunishmentsModule extends CoreClass {
 			let PunishmentModel;
 			let PunishmentSchema;
 
-			this.db
-				.runJob("GET_MODEL", { modelName: "punishment" })
+			PunishmentsModule.log("ERROR", "HOW DOES THIS WORK!?!?!??!?!?!?!??!?!??!?!?!?!");
+			DBModule.runJob("GET_MODEL", { modelName: "punishment" }, this)
 				.then(model => {
 					PunishmentModel = model;
 				})
 				.catch(console.error);
 
-			this.db
-				.runJob("GET_SCHEMA", { schemaName: "punishment" })
+			PunishmentsModule.log("ERROR", "HOW DOES THIS WORK!?!?!??!?!?!?!??!?!??!?!?!?!");
+			DBModule.runJob("GET_SCHEMA", { schemaName: "punishment" }, this)
 				.then(model => {
 					PunishmentSchema = model;
 				})
@@ -305,12 +315,15 @@ class PunishmentsModule extends CoreClass {
 					},
 
 					(punishment, next) => {
-						this.cache
-							.runJob("HSET", {
+						CacheModule.runJob(
+							"HSET",
+							{
 								table: "punishments",
 								key: punishment._id,
 								value: PunishmentSchema(punishment, punishment._id)
-							})
+							},
+							this
+						)
 							.then(() => next())
 							.catch(next);
 					},
@@ -329,4 +342,4 @@ class PunishmentsModule extends CoreClass {
 	}
 }
 
-export default new PunishmentsModule();
+export default new _PunishmentsModule();

+ 53 - 43
backend/logic/songs.js

@@ -2,10 +2,17 @@ import async from "async";
 import mongoose from "mongoose";
 import CoreClass from "../core";
 
-class SongsModule extends CoreClass {
+let SongsModule;
+let CacheModule;
+let DBModule;
+let UtilsModule;
+
+class _SongsModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("songs");
+
+		SongsModule = this;
 	}
 
 	/**
@@ -16,13 +23,12 @@ class SongsModule extends CoreClass {
 	async initialize() {
 		this.setStage(1);
 
-		this.cache = this.moduleManager.modules.cache;
-		this.db = this.moduleManager.modules.db;
-		this.io = this.moduleManager.modules.io;
-		this.utils = this.moduleManager.modules.utils;
+		CacheModule = this.moduleManager.modules.cache;
+		DBModule = this.moduleManager.modules.db;
+		UtilsModule = this.moduleManager.modules.utils;
 
-		const songModel = await this.db.runJob("GET_MODEL", { modelName: "song" });
-		const songSchema = await this.cache.runJob("GET_SCHEMA", { schemaName: "song" });
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
+		const songSchema = await CacheModule.runJob("GET_SCHEMA", { schemaName: "song" });
 
 		this.setStage(2);
 
@@ -31,8 +37,7 @@ class SongsModule extends CoreClass {
 				[
 					next => {
 						this.setStage(2);
-						this.cache
-							.runJob("HGETALL", { table: "songs" })
+						CacheModule.runJob("HGETALL", { table: "songs" })
 							.then(songs => {
 								next(null, songs);
 							})
@@ -52,11 +57,10 @@ class SongsModule extends CoreClass {
 								songModel.findOne({ songId }, (err, song) => {
 									if (err) next(err);
 									else if (!song)
-										this.cache
-											.runJob("HDEL", {
-												table: "songs",
-												key: songId
-											})
+										CacheModule.runJob("HDEL", {
+											table: "songs",
+											key: songId
+										})
 											.then(() => next())
 											.catch(next);
 									else next();
@@ -76,12 +80,11 @@ class SongsModule extends CoreClass {
 						async.each(
 							songs,
 							(song, next) => {
-								this.cache
-									.runJob("HSET", {
-										table: "songs",
-										key: song.songId,
-										value: songSchema(song)
-									})
+								CacheModule.runJob("HSET", {
+									table: "songs",
+									key: song.songId,
+									value: songSchema(song)
+								})
 									.then(() => next())
 									.catch(next);
 							},
@@ -91,7 +94,7 @@ class SongsModule extends CoreClass {
 				],
 				async err => {
 					if (err) {
-						err = await this.utils.runJob("GET_ERROR", { error: err });
+						err = await UtilsModule.runJob("GET_ERROR", { error: err });
 						reject(new Error(err));
 					} else resolve();
 				}
@@ -110,8 +113,8 @@ class SongsModule extends CoreClass {
 		return new Promise((resolve, reject) => {
 			let songModel;
 
-			this.db
-				.runJob("GET_MODEL", { modelName: "song" })
+			SongsModule.log("ERROR", "HOW DOES THIS WORK!?!?!??!?!?!?!??!?!??!?!?!?!");
+			DBModule.runJob("GET_MODEL", { modelName: "song" }, this)
 				.then(model => {
 					songModel = model;
 				})
@@ -121,8 +124,7 @@ class SongsModule extends CoreClass {
 				[
 					next => {
 						if (!mongoose.Types.ObjectId.isValid(payload.id)) return next("Id is not a valid ObjectId.");
-						return this.cache
-							.runJob("HGET", { table: "songs", key: payload.id })
+						return CacheModule.runJob("HGET", { table: "songs", key: payload.id }, this)
 							.then(song => {
 								next(null, song);
 							})
@@ -136,13 +138,15 @@ class SongsModule extends CoreClass {
 
 					(song, next) => {
 						if (song) {
-							this.cache
-								.runJob("HSET", {
+							CacheModule.runJob(
+								"HSET",
+								{
 									table: "songs",
 									key: payload.id,
 									value: song
-								})
-								.then(song => next(null, song));
+								},
+								this
+							).then(song => next(null, song));
 						} else next("Song not found.");
 					}
 				],
@@ -165,8 +169,8 @@ class SongsModule extends CoreClass {
 		return new Promise((resolve, reject) => {
 			let songModel;
 
-			this.db
-				.runJob("GET_MODEL", { modelName: "song" })
+			SongsModule.log("ERROR", "HOW DOES THIS WORK!?!?!??!?!?!?!??!?!??!?!?!?!");
+			DBModule.runJob("GET_MODEL", { modelName: "song" }, this)
 				.then(model => {
 					songModel = model;
 				})
@@ -198,8 +202,8 @@ class SongsModule extends CoreClass {
 		return new Promise((resolve, reject) => {
 			let songModel;
 
-			this.db
-				.runJob("GET_MODEL", { modelName: "song" })
+			SongsModule.log("ERROR", "HOW DOES THIS WORK!?!?!??!?!?!?!??!?!??!?!?!?!");
+			DBModule.runJob("GET_MODEL", { modelName: "song" }, this)
 				.then(model => {
 					songModel = model;
 				})
@@ -213,19 +217,22 @@ class SongsModule extends CoreClass {
 
 					(song, next) => {
 						if (!song) {
-							this.cache.runJob("HDEL", {
+							CacheModule.runJob("HDEL", {
 								table: "songs",
 								key: payload.songId
 							});
 							return next("Song not found.");
 						}
 
-						return this.cache
-							.runJob("HSET", {
+						return CacheModule.runJob(
+							"HSET",
+							{
 								table: "songs",
 								key: payload.songId,
 								value: song
-							})
+							},
+							this
+						)
 							.then(song => {
 								next(null, song);
 							})
@@ -252,8 +259,8 @@ class SongsModule extends CoreClass {
 		return new Promise((resolve, reject) => {
 			let songModel;
 
-			this.db
-				.runJob("GET_MODEL", { modelName: "song" })
+			SongsModule.log("ERROR", "HOW DOES THIS WORK!?!?!??!?!?!?!??!?!??!?!?!?!");
+			DBModule.runJob("GET_MODEL", { modelName: "song" })
 				.then(model => {
 					songModel = model;
 				})
@@ -266,11 +273,14 @@ class SongsModule extends CoreClass {
 					},
 
 					next => {
-						this.cache
-							.runJob("HDEL", {
+						CacheModule.runJob(
+							"HDEL",
+							{
 								table: "songs",
 								key: payload.songId
-							})
+							},
+							this
+						)
 							.then(() => next())
 							.catch(next);
 					}
@@ -284,4 +294,4 @@ class SongsModule extends CoreClass {
 	}
 }
 
-export default new SongsModule();
+export default new _SongsModule();

+ 21 - 14
backend/logic/spotify.js

@@ -14,10 +14,16 @@ let apiResults = {
 	scope: ""
 };
 
-class SpotifyModule extends CoreClass {
+let SpotifyModule;
+let CacheModule;
+let UtilsModule;
+
+class _SpotifyModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("spotify");
+
+		SpotifyModule = this;
 	}
 
 	/**
@@ -27,8 +33,8 @@ class SpotifyModule extends CoreClass {
 	 */
 	initialize() {
 		return new Promise((resolve, reject) => {
-			this.cache = this.moduleManager.modules.cache;
-			this.utils = this.moduleManager.modules.utils;
+			CacheModule = this.moduleManager.modules.cache;
+			UtilsModule = this.moduleManager.modules.utils;
 
 			const client = config.get("apis.spotify.client");
 			const secret = config.get("apis.spotify.secret");
@@ -39,8 +45,7 @@ class SpotifyModule extends CoreClass {
 				[
 					next => {
 						this.setStage(2);
-						this.cache
-							.runJob("HGET", { table: "api", key: "spotify" })
+						CacheModule.runJob("HGET", { table: "api", key: "spotify" })
 							.then(data => {
 								next(null, data);
 							})
@@ -55,7 +60,7 @@ class SpotifyModule extends CoreClass {
 				],
 				async err => {
 					if (err) {
-						err = await this.utils.runJob("GET_ERROR", {
+						err = await UtilsModule.runJob("GET_ERROR", {
 							error: err
 						});
 						reject(new Error(err));
@@ -75,7 +80,7 @@ class SpotifyModule extends CoreClass {
 	GET_TOKEN() {
 		return new Promise(resolve => {
 			if (Date.now() > apiResults.expires_at) {
-				this.runJob("REQUEST_TOKEN").then(() => {
+				SpotifyModule.runJob("REQUEST_TOKEN", null, this).then(() => {
 					resolve(apiResults.access_token);
 				});
 			} else resolve(apiResults.access_token);
@@ -92,21 +97,23 @@ class SpotifyModule extends CoreClass {
 			async.waterfall(
 				[
 					next => {
-						this.log("INFO", "SPOTIFY_REQUEST_TOKEN", "Requesting new Spotify token.");
-						this.SpotifyOauth.getOAuthAccessToken("", { grant_type: "client_credentials" }, next);
+						SpotifyModule.log("INFO", "SPOTIFY_REQUEST_TOKEN", "Requesting new Spotify token.");
+						SpotifyModule.SpotifyOauth.getOAuthAccessToken("", { grant_type: "client_credentials" }, next);
 					},
 					(accessToken, refreshToken, results, next) => {
 						apiResults = results;
 						apiResults.expires_at = Date.now() + results.expires_in * 1000;
 
-						this.cache
-							.runJob("HSET", {
+						CacheModule.runJob(
+							"HSET",
+							{
 								table: "api",
 								key: "spotify",
 								value: apiResults,
 								stringifyJson: true
-							})
-							.finally(() => next());
+							},
+							this
+						).finally(() => next());
 					}
 				],
 				() => resolve()
@@ -115,4 +122,4 @@ class SpotifyModule extends CoreClass {
 	}
 }
 
-export default new SpotifyModule();
+export default new _SpotifyModule();

+ 264 - 203
backend/logic/stations.js

@@ -2,10 +2,19 @@ import async from "async";
 
 import CoreClass from "../core";
 
-class StationsModule extends CoreClass {
+let StationsModule;
+let CacheModule;
+let DBModule;
+let UtilsModule;
+let SongsModule;
+let NotificationsModule;
+
+class _StationsModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("stations");
+
+		StationsModule = this;
 	}
 
 	/**
@@ -14,11 +23,11 @@ class StationsModule extends CoreClass {
 	 * @returns {Promise} - returns promise (reject, resolve)
 	 */
 	async initialize() {
-		this.cache = this.moduleManager.modules.cache;
-		this.db = this.moduleManager.modules.db;
-		this.utils = this.moduleManager.modules.utils;
-		this.songs = this.moduleManager.modules.songs;
-		this.notifications = this.moduleManager.modules.notifications;
+		CacheModule = this.moduleManager.modules.cache;
+		DBModule = this.moduleManager.modules.db;
+		UtilsModule = this.moduleManager.modules.utils;
+		SongsModule = this.moduleManager.modules.songs;
+		NotificationsModule = this.moduleManager.modules.notifications;
 
 		this.defaultSong = {
 			songId: "60ItHLz5WEA",
@@ -30,30 +39,28 @@ class StationsModule extends CoreClass {
 		};
 
 		// TEMP
-		this.cache.runJob("SUB", {
+		CacheModule.runJob("SUB", {
 			channel: "station.pause",
 			cb: async stationId => {
-				this.notifications
-					.runJob("REMOVE", {
-						subscription: `stations.nextSong?id=${stationId}`
-					})
-					.then();
+				NotificationsModule.runJob("REMOVE", {
+					subscription: `stations.nextSong?id=${stationId}`
+				}).then();
 			}
 		});
 
-		this.cache.runJob("SUB", {
+		CacheModule.runJob("SUB", {
 			channel: "station.resume",
 			cb: async stationId => {
-				this.runJob("INITIALIZE_STATION", { stationId }).then();
+				StationsModule.runJob("INITIALIZE_STATION", { stationId }).then();
 			}
 		});
 
-		this.cache.runJob("SUB", {
+		CacheModule.runJob("SUB", {
 			channel: "station.queueUpdate",
 			cb: async stationId => {
-				this.runJob("GET_STATION", { stationId }).then(station => {
+				StationsModule.runJob("GET_STATION", { stationId }).then(station => {
 					if (!station.currentSong && station.queue.length > 0) {
-						this.runJob("INITIALIZE_STATION", {
+						StationsModule.runJob("INITIALIZE_STATION", {
 							stationId
 						}).then();
 					}
@@ -61,35 +68,32 @@ class StationsModule extends CoreClass {
 			}
 		});
 
-		this.cache.runJob("SUB", {
+		CacheModule.runJob("SUB", {
 			channel: "station.newOfficialPlaylist",
 			cb: async stationId => {
-				this.cache
-					.runJob("HGET", {
-						table: "officialPlaylists",
-						key: stationId
-					})
-					.then(playlistObj => {
-						if (playlistObj) {
-							this.utils.runJob("EMIT_TO_ROOM", {
-								room: `station.${stationId}`,
-								args: ["event:newOfficialPlaylist", playlistObj.songs]
-							});
-						}
-					});
+				CacheModule.runJob("HGET", {
+					table: "officialPlaylists",
+					key: stationId
+				}).then(playlistObj => {
+					if (playlistObj) {
+						UtilsModule.runJob("EMIT_TO_ROOM", {
+							room: `station.${stationId}`,
+							args: ["event:newOfficialPlaylist", playlistObj.songs]
+						});
+					}
+				});
 			}
 		});
 
-		const stationModel = (this.stationModel = await this.db.runJob("GET_MODEL", { modelName: "station" }));
-		const stationSchema = (this.stationSchema = await this.cache.runJob("GET_SCHEMA", { schemaName: "station" }));
+		const stationModel = (this.stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }));
+		const stationSchema = (this.stationSchema = await CacheModule.runJob("GET_SCHEMA", { schemaName: "station" }));
 
 		return new Promise((resolve, reject) =>
 			async.waterfall(
 				[
 					next => {
 						this.setStage(2);
-						this.cache
-							.runJob("HGETALL", { table: "stations" })
+						CacheModule.runJob("HGETALL", { table: "stations" })
 							.then(stations => {
 								next(null, stations);
 							})
@@ -109,11 +113,10 @@ class StationsModule extends CoreClass {
 								stationModel.findOne({ _id: stationId }, (err, station) => {
 									if (err) next(err);
 									else if (!station) {
-										this.cache
-											.runJob("HDEL", {
-												table: "stations",
-												key: stationId
-											})
+										CacheModule.runJob("HDEL", {
+											table: "stations",
+											key: stationId
+										})
 											.then(() => {
 												next();
 											})
@@ -138,18 +141,17 @@ class StationsModule extends CoreClass {
 								async.waterfall(
 									[
 										next => {
-											this.cache
-												.runJob("HSET", {
-													table: "stations",
-													key: station._id,
-													value: stationSchema(station)
-												})
+											CacheModule.runJob("HSET", {
+												table: "stations",
+												key: station._id,
+												value: stationSchema(station)
+											})
 												.then(station => next(null, station))
 												.catch(next);
 										},
 
 										(station, next) => {
-											this.runJob(
+											StationsModule.runJob(
 												"INITIALIZE_STATION",
 												{
 													stationId: station._id,
@@ -174,7 +176,7 @@ class StationsModule extends CoreClass {
 				],
 				async err => {
 					if (err) {
-						err = await this.utils.runJob("GET_ERROR", {
+						err = await UtilsModule.runJob("GET_ERROR", {
 							error: err
 						});
 						reject(new Error(err));
@@ -201,13 +203,14 @@ class StationsModule extends CoreClass {
 			async.waterfall(
 				[
 					next => {
-						this.runJob(
+						StationsModule.runJob(
 							"GET_STATION",
 							{
 								stationId: payload.stationId,
 								bypassQueue: payload.bypassQueue
 							},
-							{ bypassQueue: payload.bypassQueue }
+							{ bypassQueue: payload.bypassQueue },
+							this
 						)
 							.then(station => {
 								next(null, station);
@@ -217,23 +220,33 @@ class StationsModule extends CoreClass {
 					(station, next) => {
 						if (!station) return next("Station not found.");
 
-						this.notifications
-							.runJob("UNSCHEDULE", {
+						NotificationsModule.runJob(
+							"UNSCHEDULE",
+							{
 								name: `stations.nextSong?id=${station._id}`
-							})
+							},
+							this
+						)
 							.then()
 							.catch();
 
-						this.notifications
-							.runJob("SUBSCRIBE", {
+						NotificationsModule.runJob(
+							"SUBSCRIBE",
+							{
 								name: `stations.nextSong?id=${station._id}`,
 								cb: () =>
-									this.runJob("SKIP_STATION", {
-										stationId: station._id
-									}),
+									StationsModule.runJob(
+										"SKIP_STATION",
+										{
+											stationId: station._id
+										},
+										this
+									),
 								unique: true,
 								station
-							})
+							},
+							this
+						)
 							.then()
 							.catch();
 
@@ -243,13 +256,13 @@ class StationsModule extends CoreClass {
 					},
 					(station, next) => {
 						if (!station.currentSong) {
-							return this.runJob(
+							return StationsModule.runJob(
 								"SKIP_STATION",
 								{
 									stationId: station._id,
 									bypassQueue: payload.bypassQueue
 								},
-								{ bypassQueue: payload.bypassQueue }
+								this
 							)
 								.then(station => {
 									next(true, station);
@@ -264,13 +277,13 @@ class StationsModule extends CoreClass {
 						if (Number.isNaN(timeLeft)) timeLeft = -1;
 
 						if (station.currentSong.duration * 1000 < timeLeft || timeLeft < 0) {
-							return this.runJob(
+							return StationsModule.runJob(
 								"SKIP_STATION",
 								{
 									stationId: station._id,
 									bypassQueue: payload.bypassQueue
 								},
-								{ bypassQueue: payload.bypassQueue }
+								this
 							)
 								.then(station => {
 									next(null, station);
@@ -278,20 +291,28 @@ class StationsModule extends CoreClass {
 								.catch(next);
 						}
 						// name, time, cb, station
-						this.notifications.runJob("SCHEDULE", {
-							name: `stations.nextSong?id=${station._id}`,
-							time: timeLeft,
-							station
-						});
+						NotificationsModule.runJob(
+							"SCHEDULE",
+							{
+								name: `stations.nextSong?id=${station._id}`,
+								time: timeLeft,
+								station
+							},
+							this
+						);
 
 						return next(null, station);
 					}
 				],
 				async (err, station) => {
 					if (err && err !== true) {
-						err = await this.utils.runJob("GET_ERROR", {
-							error: err
-						});
+						err = await UtilsModule.runJob(
+							"GET_ERROR",
+							{
+								error: err
+							},
+							this
+						);
 						reject(new Error(err));
 					} else resolve(station);
 				}
@@ -309,8 +330,8 @@ class StationsModule extends CoreClass {
 	 */
 	async CALCULATE_SONG_FOR_STATION(payload) {
 		// station, bypassValidate = false
-		const stationModel = await this.db.runJob("GET_MODEL", { modelName: "station" });
-		const songModel = await this.db.runJob("GET_MODEL", { modelName: "song" });
+		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
 
 		return new Promise((resolve, reject) => {
 			const songList = [];
@@ -355,23 +376,22 @@ class StationsModule extends CoreClass {
 							if (songList.indexOf(songId) !== -1) playlist.push(songId);
 						});
 
-						this.utils
-							.runJob("SHUFFLE", { array: playlist })
+						UtilsModule.runJob("SHUFFLE", { array: playlist })
 							.then(result => {
 								next(null, result.array);
-							})
+							}, this)
 							.catch(next);
 					},
 
 					(playlist, next) => {
-						this.runJob(
+						StationsModule.runJob(
 							"CALCULATE_OFFICIAL_PLAYLIST_LIST",
 							{
 								stationId: payload.station._id,
 								songList: playlist,
 								bypassQueue: payload.bypassQueue
 							},
-							{ bypassQueue: payload.bypassQueue }
+							this
 						)
 							.then(() => {
 								next(null, playlist);
@@ -385,13 +405,13 @@ class StationsModule extends CoreClass {
 							{ $set: { playlist } },
 							{ runValidators: true },
 							() => {
-								this.runJob(
+								StationsModule.runJob(
 									"UPDATE_STATION",
 									{
 										stationId: payload.station._id,
 										bypassQueue: payload.bypassQueue
 									},
-									{ bypassQueue: payload.bypassQueue }
+									this
 								)
 									.then(() => {
 										next(null, playlist);
@@ -421,11 +441,14 @@ class StationsModule extends CoreClass {
 			async.waterfall(
 				[
 					next => {
-						this.cache
-							.runJob("HGET", {
+						CacheModule.runJob(
+							"HGET",
+							{
 								table: "stations",
 								key: payload.stationId
-							})
+							},
+							this
+						)
 							.then(station => {
 								next(null, station);
 							})
@@ -440,7 +463,7 @@ class StationsModule extends CoreClass {
 					(station, next) => {
 						if (station) {
 							if (station.type === "official") {
-								this.runJob("CALCULATE_OFFICIAL_PLAYLIST_LIST", {
+								StationsModule.runJob("CALCULATE_OFFICIAL_PLAYLIST_LIST", {
 									stationId: station._id,
 									songList: station.playlist
 								})
@@ -448,12 +471,11 @@ class StationsModule extends CoreClass {
 									.catch();
 							}
 							station = this.stationSchema(station);
-							this.cache
-								.runJob("HSET", {
-									table: "stations",
-									key: payload.stationId,
-									value: station
-								})
+							CacheModule.runJob("HSET", {
+								table: "stations",
+								key: payload.stationId,
+								value: station
+							})
 								.then()
 								.catch();
 							next(true, station);
@@ -462,9 +484,13 @@ class StationsModule extends CoreClass {
 				],
 				async (err, station) => {
 					if (err && err !== true) {
-						err = await this.utils.runJob("GET_ERROR", {
-							error: err
-						});
+						err = await UtilsModule.runJob(
+							"GET_ERROR",
+							{
+								error: err
+							},
+							this
+						);
 						reject(new Error(err));
 					} else resolve(station);
 				}
@@ -480,7 +506,7 @@ class StationsModule extends CoreClass {
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
 	async GET_STATION_BY_NAME(payload) {
-		const stationModel = await this.db.runJob("GET_MODEL", { modelName: "station" });
+		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
 
 		return new Promise((resolve, reject) =>
 			async.waterfall(
@@ -492,14 +518,14 @@ class StationsModule extends CoreClass {
 					(station, next) => {
 						if (station) {
 							if (station.type === "official") {
-								this.runJob("CALCULATE_OFFICIAL_PLAYLIST_LIST", {
+								StationsModule.runJob("CALCULATE_OFFICIAL_PLAYLIST_LIST", {
 									stationId: station._id,
 									songList: station.playlist
 								});
 							}
-							this.cache.runJob("GET_SCHEMA", { schemaName: "station" }).then(stationSchema => {
+							CacheModule.runJob("GET_SCHEMA", { schemaName: "station" }, this).then(stationSchema => {
 								station = stationSchema(station);
-								this.cache.runJob("HSET", {
+								CacheModule.runJob("HSET", {
 									table: "stations",
 									key: station._id,
 									value: station
@@ -534,22 +560,24 @@ class StationsModule extends CoreClass {
 
 					(station, next) => {
 						if (!station) {
-							this.cache
-								.runJob("HDEL", {
-									table: "stations",
-									key: payload.stationId
-								})
+							CacheModule.runJob("HDEL", {
+								table: "stations",
+								key: payload.stationId
+							})
 								.then()
 								.catch();
 							return next("Station not found");
 						}
 
-						return this.cache
-							.runJob("HSET", {
+						return CacheModule.runJob(
+							"HSET",
+							{
 								table: "stations",
 								key: payload.stationId,
 								value: station
-							})
+							},
+							this
+						)
 							.then(station => {
 								next(null, station);
 							})
@@ -558,9 +586,13 @@ class StationsModule extends CoreClass {
 				],
 				async (err, station) => {
 					if (err && err !== true) {
-						err = await this.utils.runJob("GET_ERROR", {
-							error: err
-						});
+						err = await UtilsModule.runJob(
+							"GET_ERROR",
+							{
+								error: err
+							},
+							this
+						);
 						reject(new Error(err));
 					} else resolve(station);
 				}
@@ -577,7 +609,7 @@ class StationsModule extends CoreClass {
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
 	async CALCULATE_OFFICIAL_PLAYLIST_LIST(payload) {
-		const officialPlaylistSchema = await this.cache.runJob("GET_SCHEMA", { schemaName: "officialPlaylist" });
+		const officialPlaylistSchema = await CacheModule.runJob("GET_SCHEMA", { schemaName: "officialPlaylist" }, this);
 
 		console.log(typeof payload.songList, payload.songList);
 
@@ -587,8 +619,7 @@ class StationsModule extends CoreClass {
 			return async.each(
 				payload.songList,
 				(song, next) => {
-					this.songs
-						.runJob("GET_SONG", { id: song })
+					SongsModule.runJob("GET_SONG", { id: song }, this)
 						.then(response => {
 							const { song } = response;
 							if (song) {
@@ -606,19 +637,21 @@ class StationsModule extends CoreClass {
 						});
 				},
 				() => {
-					this.cache
-						.runJob("HSET", {
+					CacheModule.runJob(
+						"HSET",
+						{
 							table: "officialPlaylists",
 							key: payload.stationId,
 							value: officialPlaylistSchema(payload.stationId, lessInfoPlaylist)
-						})
-						.finally(() => {
-							this.cache.runJob("PUB", {
-								channel: "station.newOfficialPlaylist",
-								value: payload.stationId
-							});
-							resolve();
+						},
+						this
+					).finally(() => {
+						CacheModule.runJob("PUB", {
+							channel: "station.newOfficialPlaylist",
+							value: payload.stationId
 						});
+						resolve();
+					});
 				}
 			);
 		});
@@ -634,20 +667,20 @@ class StationsModule extends CoreClass {
 	 */
 	SKIP_STATION(payload) {
 		return new Promise((resolve, reject) => {
-			this.log("INFO", `Skipping station ${payload.stationId}.`);
+			StationsModule.log("INFO", `Skipping station ${payload.stationId}.`);
 
-			this.log("STATION_ISSUE", `SKIP_STATION_CB - Station ID: ${payload.stationId}.`);
+			StationsModule.log("STATION_ISSUE", `SKIP_STATION_CB - Station ID: ${payload.stationId}.`);
 
 			async.waterfall(
 				[
 					next => {
-						this.runJob(
+						StationsModule.runJob(
 							"GET_STATION",
 							{
 								stationId: payload.stationId,
 								bypassQueue: payload.bypassQueue
 							},
-							{ bypassQueue: payload.bypassQueue }
+							this
 						)
 							.then(station => {
 								next(null, station);
@@ -682,7 +715,7 @@ class StationsModule extends CoreClass {
 						}
 
 						if (station.type === "community" && !station.partyMode) {
-							return this.db.runJob("GET_MODEL", { modelName: "playlist" }).then(playlistModel =>
+							return DBModule.runJob("GET_MODEL", { modelName: "playlist" }, this).then(playlistModel =>
 								playlistModel.findOne({ _id: station.privatePlaylist }, (err, playlist) => {
 									if (err) return next(err);
 
@@ -713,16 +746,22 @@ class StationsModule extends CoreClass {
 										};
 
 										if (playlist[currentSongIndex]._id)
-											return this.songs
-												.runJob("GET_SONG", {
+											return SongsModule.runJob(
+												"GET_SONG",
+												{
 													id: playlist[currentSongIndex]._id
-												})
+												},
+												this
+											)
 												.then(response => callback(null, response.song))
 												.catch(callback);
-										return this.songs
-											.runJob("GET_SONG_FROM_ID", {
+										return SongsModule.runJob(
+											"GET_SONG_FROM_ID",
+											{
 												songId: playlist[currentSongIndex].songId
-											})
+											},
+											this
+										)
 											.then(response => callback(null, response.song))
 											.catch(callback);
 									}
@@ -733,18 +772,21 @@ class StationsModule extends CoreClass {
 						}
 
 						if (station.type === "official" && station.playlist.length === 0) {
-							return this.runJob(
+							return StationsModule.runJob(
 								"CALCULATE_SONG_FOR_STATION",
 								{ station, bypassQueue: payload.bypassQueue },
-								{ bypassQueue: payload.bypassQueue }
+								this
 							)
 								.then(playlist => {
 									if (playlist.length === 0) return next(null, this.defaultSong, 0, station);
 
-									return this.songs
-										.runJob("GET_SONG", {
+									return SongsModule.runJob(
+										"GET_SONG",
+										{
 											id: playlist[0]
-										})
+										},
+										this
+									)
 										.then(response => {
 											next(null, response.song, 0, station);
 										})
@@ -757,27 +799,29 @@ class StationsModule extends CoreClass {
 							return async.doUntil(
 								next => {
 									if (station.currentSongIndex < station.playlist.length - 1) {
-										this.songs
-											.runJob("GET_SONG", {
+										SongsModule.runJob(
+											"GET_SONG",
+											{
 												id: station.playlist[station.currentSongIndex + 1]
-											})
+											},
+											this
+										)
 											.then(response => next(null, response.song, station.currentSongIndex + 1))
 											.catch(() => {
 												station.currentSongIndex += 1;
 												next(null, null, null);
 											});
 									} else {
-										this.runJob(
+										StationsModule.runJob(
 											"CALCULATE_SONG_FOR_STATION",
 											{
 												station,
 												bypassQueue: payload.bypassQueue
 											},
-											{ bypassQueue: payload.bypassQueue }
+											this
 										)
 											.then(newPlaylist => {
-												this.songs
-													.runJob("GET_SONG", { id: newPlaylist[0] })
+												SongsModule.runJob("GET_SONG", { id: newPlaylist[0] }, this)
 													.then(response => {
 														station.playlist = newPlaylist;
 														next(null, response.song, 0);
@@ -830,22 +874,20 @@ class StationsModule extends CoreClass {
 
 					($set, station, next) => {
 						this.stationModel.updateOne({ _id: station._id }, { $set }, () => {
-							this.runJob(
+							StationsModule.runJob(
 								"UPDATE_STATION",
 								{
 									stationId: station._id,
 									bypassQueue: payload.bypassQueue
 								},
-
-								{ bypassQueue: payload.bypassQueue }
+								this
 							)
 								.then(station => {
 									if (station.type === "community" && station.partyMode === true)
-										this.cache
-											.runJob("PUB", {
-												channel: "station.queueUpdate",
-												value: payload.stationId
-											})
+										CacheModule.runJob("PUB", {
+											channel: "station.queueUpdate",
+											value: payload.stationId
+										})
 											.then()
 											.catch();
 									next(null, station);
@@ -856,10 +898,14 @@ class StationsModule extends CoreClass {
 				],
 				async (err, station) => {
 					if (err) {
-						err = await this.utils.runJob("GET_ERROR", {
-							error: err
-						});
-						this.log("ERROR", `Skipping station "${payload.stationId}" failed. "${err}"`);
+						err = await UtilsModule.runJob(
+							"GET_ERROR",
+							{
+								error: err
+							},
+							this
+						);
+						StationsModule.log("ERROR", `Skipping station "${payload.stationId}" failed. "${err}"`);
 						reject(new Error(err));
 					} else {
 						if (station.currentSong !== null && station.currentSong.songId !== undefined) {
@@ -867,32 +913,30 @@ class StationsModule extends CoreClass {
 						}
 						// TODO Pub/Sub this
 
-						this.utils
-							.runJob("EMIT_TO_ROOM", {
-								room: `station.${station._id}`,
-								args: [
-									"event:songs.next",
-									{
-										currentSong: station.currentSong,
-										startedAt: station.startedAt,
-										paused: station.paused,
-										timePaused: 0
-									}
-								]
-							})
+						UtilsModule.runJob("EMIT_TO_ROOM", {
+							room: `station.${station._id}`,
+							args: [
+								"event:songs.next",
+								{
+									currentSong: station.currentSong,
+									startedAt: station.startedAt,
+									paused: station.paused,
+									timePaused: 0
+								}
+							]
+						})
 							.then()
 							.catch();
 
 						if (station.privacy === "public") {
-							this.utils
-								.runJob("EMIT_TO_ROOM", {
-									room: "home",
-									args: ["event:station.nextSong", station._id, station.currentSong]
-								})
+							UtilsModule.runJob("EMIT_TO_ROOM", {
+								room: "home",
+								args: ["event:station.nextSong", station._id, station.currentSong]
+							})
 								.then()
 								.catch();
 						} else {
-							const sockets = await this.utils.runJob("GET_ROOM_SOCKETS", { room: "home" });
+							const sockets = await UtilsModule.runJob("GET_ROOM_SOCKETS", { room: "home" }, this);
 
 							for (
 								let socketId = 0, socketKeys = Object.keys(sockets);
@@ -902,14 +946,17 @@ class StationsModule extends CoreClass {
 								const socket = sockets[socketId];
 								const { session } = sockets[socketId];
 								if (session.sessionId) {
-									this.cache
-										.runJob("HGET", {
+									CacheModule.runJob(
+										"HGET",
+										{
 											table: "sessions",
 											key: session.sessionId
-										})
-										.then(session => {
-											if (session) {
-												this.db.runJob("GET_MODEL", { modelName: "user" }).then(userModel => {
+										},
+										this
+									).then(session => {
+										if (session) {
+											DBModule.runJob("GET_MODEL", { modelName: "user" }, this).then(
+												userModel => {
 													userModel.findOne(
 														{
 															_id: session.userId
@@ -934,34 +981,42 @@ class StationsModule extends CoreClass {
 															}
 														}
 													);
-												});
-											}
-										});
+												}
+											);
+										}
+									});
 								}
 							}
 						}
 
 						if (station.currentSong !== null && station.currentSong.songId !== undefined) {
-							this.utils.runJob("SOCKETS_JOIN_SONG_ROOM", {
-								sockets: await this.utils.runJob("GET_ROOM_SOCKETS", {
-									room: `station.${station._id}`
-								}),
+							UtilsModule.runJob("SOCKETS_JOIN_SONG_ROOM", {
+								sockets: await UtilsModule.runJob(
+									"GET_ROOM_SOCKETS",
+									{
+										room: `station.${station._id}`
+									},
+									this
+								),
 								room: `song.${station.currentSong.songId}`
 							});
 							if (!station.paused) {
-								this.notifications.runJob("SCHEDULE", {
+								NotificationsModule.runJob("SCHEDULE", {
 									name: `stations.nextSong?id=${station._id}`,
 									time: station.currentSong.duration * 1000,
 									station
 								});
 							}
 						} else {
-							this.utils
-								.runJob("SOCKETS_LEAVE_SONG_ROOMS", {
-									sockets: await this.utils.runJob("GET_ROOM_SOCKETS", {
+							UtilsModule.runJob("SOCKETS_LEAVE_SONG_ROOMS", {
+								sockets: await UtilsModule.runJob(
+									"GET_ROOM_SOCKETS",
+									{
 										room: `station.${station._id}`
-									})
-								})
+									},
+									this
+								)
+							})
 								.then()
 								.catch();
 						}
@@ -997,13 +1052,15 @@ class StationsModule extends CoreClass {
 					},
 
 					next => {
-						this.db
-							.runJob("GET_MODEL", {
+						DBModule.runJob(
+							"GET_MODEL",
+							{
 								modelName: "user"
-							})
-							.then(userModel => {
-								userModel.findOne({ _id: payload.userId }, next);
-							});
+							},
+							this
+						).then(userModel => {
+							userModel.findOne({ _id: payload.userId }, next);
+						});
 					},
 
 					(user, next) => {
@@ -1017,9 +1074,13 @@ class StationsModule extends CoreClass {
 				],
 				async errOrResult => {
 					if (errOrResult !== true && errOrResult !== "Not allowed") {
-						errOrResult = await this.utils.runJob("GET_ERROR", {
-							error: errOrResult
-						});
+						errOrResult = await UtilsModule.runJob(
+							"GET_ERROR",
+							{
+								error: errOrResult
+							},
+							this
+						);
 						reject(new Error(errOrResult));
 					} else {
 						resolve(errOrResult === true);
@@ -1030,4 +1091,4 @@ class StationsModule extends CoreClass {
 	}
 }
 
-export default new StationsModule();
+export default new _StationsModule();

+ 96 - 79
backend/logic/tasks.js

@@ -8,12 +8,19 @@ import Timer from "../classes/Timer.class";
 
 const __dirname = path.dirname(fileURLToPath(import.meta.url));
 
-class TasksModule extends CoreClass {
+let TasksModule;
+let CacheModule;
+let StationsModule;
+let UtilsModule;
+
+class _TasksModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("tasks");
 
 		this.tasks = {};
+
+		TasksModule = this;
 	}
 
 	/**
@@ -25,28 +32,27 @@ class TasksModule extends CoreClass {
 		return new Promise(resolve => {
 			// return reject(new Error("Not fully migrated yet."));
 
-			this.cache = this.moduleManager.modules.cache;
-			this.stations = this.moduleManager.modules.stations;
-			this.notifications = this.moduleManager.modules.notifications;
-			this.utils = this.moduleManager.modules.utils;
+			CacheModule = this.moduleManager.modules.cache;
+			StationsModule = this.moduleManager.modules.stations;
+			UtilsModule = this.moduleManager.modules.utils;
 
 			// this.createTask("testTask", testTask, 5000, true);
 
-			this.runJob("CREATE_TASK", {
+			TasksModule.runJob("CREATE_TASK", {
 				name: "stationSkipTask",
-				fn: this.checkStationSkipTask,
+				fn: TasksModule.checkStationSkipTask,
 				timeout: 1000 * 60 * 30
 			});
 
-			this.runJob("CREATE_TASK", {
+			TasksModule.runJob("CREATE_TASK", {
 				name: "sessionClearTask",
-				fn: this.sessionClearingTask,
+				fn: TasksModule.sessionClearingTask,
 				timeout: 1000 * 60 * 60 * 6
 			});
 
-			this.runJob("CREATE_TASK", {
+			TasksModule.runJob("CREATE_TASK", {
 				name: "logFileSizeCheckTask",
-				fn: this.logFileSizeCheckTask,
+				fn: TasksModule.logFileSizeCheckTask,
 				timeout: 1000 * 60 * 60
 			});
 
@@ -66,7 +72,7 @@ class TasksModule extends CoreClass {
 	 */
 	CREATE_TASK(payload) {
 		return new Promise((resolve, reject) => {
-			this.tasks[payload.name] = {
+			TasksModule.tasks[payload.name] = {
 				name: payload.name,
 				fn: payload.fn,
 				timeout: payload.timeout,
@@ -75,7 +81,7 @@ class TasksModule extends CoreClass {
 			};
 
 			if (!payload.paused) {
-				this.runJob("RUN_TASK", { name: payload.name })
+				TasksModule.runJob("RUN_TASK", { name: payload.name }, this)
 					.then(() => resolve())
 					.catch(err => reject(err));
 			} else resolve();
@@ -93,7 +99,7 @@ class TasksModule extends CoreClass {
 		const taskName = { payload };
 
 		return new Promise(resolve => {
-			if (this.tasks[taskName].timer) this.tasks[taskName].timer.pause();
+			if (TasksModule.tasks[taskName].timer) TasksModule.tasks[taskName].timer.pause();
 			resolve();
 		});
 	}
@@ -107,7 +113,7 @@ class TasksModule extends CoreClass {
 	 */
 	RESUME_TASK(payload) {
 		return new Promise(resolve => {
-			this.tasks[payload.name].timer.resume();
+			TasksModule.tasks[payload.name].timer.resume();
 			resolve();
 		});
 	}
@@ -121,12 +127,17 @@ class TasksModule extends CoreClass {
 	 */
 	RUN_TASK(payload) {
 		return new Promise(resolve => {
-			const task = this.tasks[payload.name];
+			const task = TasksModule.tasks[payload.name];
 			if (task.timer) task.timer.pause();
 
+			TasksModule.log("ERROR", "CHECK THIS?!?!?!??!?!?!?!?!??!?!");
 			task.fn.apply(this).then(() => {
 				task.lastRan = Date.now();
-				task.timer = new Timer(() => this.runJob("RUN_TASK", { name: payload.name }), task.timeout, false);
+				task.timer = new Timer(
+					() => TasksModule.runJob("RUN_TASK", { name: payload.name }),
+					task.timeout,
+					false
+				);
 				resolve();
 			});
 		});
@@ -139,12 +150,11 @@ class TasksModule extends CoreClass {
 	 */
 	checkStationSkipTask() {
 		return new Promise(resolve => {
-			this.log("INFO", "TASK_STATIONS_SKIP_CHECK", `Checking for stations to be skipped.`, false);
+			TasksModule.log("INFO", "TASK_STATIONS_SKIP_CHECK", `Checking for stations to be skipped.`, false);
 			async.waterfall(
 				[
 					next => {
-						this.cache
-							.runJob("HGETALL", { table: "stations" })
+						CacheModule.runJob("HGETALL", { table: "stations" }, this)
 							.then(response => next(null, response))
 							.catch(next);
 					},
@@ -157,16 +167,18 @@ class TasksModule extends CoreClass {
 								const timeElapsed = Date.now() - station.startedAt - station.timePaused;
 								if (timeElapsed <= station.currentSong.duration) return next2();
 
-								this.log(
+								TasksModule.log(
 									"ERROR",
 									"TASK_STATIONS_SKIP_CHECK",
 									`Skipping ${station._id} as it should have skipped already.`
 								);
-								return this.stations
-									.runJob("INITIALIZE_STATION", {
+								return StationsModule.runJob(
+									"INITIALIZE_STATION",
+									{
 										stationId: station._id
-									})
-									.then(() => next2());
+									},
+									this
+								).then(() => next2());
 							},
 							() => next()
 						);
@@ -184,13 +196,12 @@ class TasksModule extends CoreClass {
 	 */
 	sessionClearingTask() {
 		return new Promise(resolve => {
-			this.log("INFO", "TASK_SESSION_CLEAR", `Checking for sessions to be cleared.`);
+			TasksModule.log("INFO", "TASK_SESSION_CLEAR", `Checking for sessions to be cleared.`);
 
 			async.waterfall(
 				[
 					next => {
-						this.cache
-							.runJob("HGETALL", { table: "sessions" })
+						CacheModule.runJob("HGETALL", { table: "sessions" }, this)
 							.then(sessions => next(null, sessions))
 							.catch(next);
 					},
@@ -212,59 +223,65 @@ class TasksModule extends CoreClass {
 									return next2();
 
 								if (!session) {
-									this.log("INFO", "TASK_SESSION_CLEAR", "Removing an empty session.");
-									return this.cache
-										.runJob("HDEL", {
+									TasksModule.log("INFO", "TASK_SESSION_CLEAR", "Removing an empty session.");
+									return CacheModule.runJob(
+										"HDEL",
+										{
 											table: "sessions",
 											key: sessionId
-										})
-										.finally(() => {
-											next2();
-										});
+										},
+										this
+									).finally(() => {
+										next2();
+									});
 								}
 								if (!session.refreshDate) {
 									session.refreshDate = Date.now();
-									return this.cache
-										.runJob("HSET", {
-											table: "sessions",
-											key: sessionId,
-											value: session
-										})
-										.finally(() => next2());
+									return CacheModule.runJob("HSET", {
+										table: "sessions",
+										key: sessionId,
+										value: session
+									}).finally(() => next2());
 								}
 								if (Date.now() - session.refreshDate > 60 * 60 * 24 * 30 * 1000) {
-									return this.utils
-										.runJob("SOCKETS_FROM_SESSION_ID", {
+									return UtilsModule.runJob(
+										"SOCKETS_FROM_SESSION_ID",
+										{
 											sessionId: session.sessionId
-										})
-										.then(response => {
-											if (response.sockets.length > 0) {
-												session.refreshDate = Date.now();
-												this.cache
-													.runJob("HSET", {
-														table: "sessions",
-														key: sessionId,
-														value: session
-													})
-													.finally(() => {
-														next2();
-													});
-											} else {
-												this.log(
-													"INFO",
-													"TASK_SESSION_CLEAR",
-													`Removing session ${sessionId} for user ${session.userId} since inactive for 30 days and not currently in use.`
-												);
-												this.cache
-													.runJob("HDEL", {
-														table: "sessions",
-														key: session.sessionId
-													})
-													.finally(() => next2());
-											}
-										});
+										},
+										this
+									).then(response => {
+										if (response.sockets.length > 0) {
+											session.refreshDate = Date.now();
+											CacheModule.runJob(
+												"HSET",
+												{
+													table: "sessions",
+													key: sessionId,
+													value: session
+												},
+												this
+											).finally(() => {
+												next2();
+											});
+										} else {
+											TasksModule.log(
+												"INFO",
+												"TASK_SESSION_CLEAR",
+												`Removing session ${sessionId} for user ${session.userId} since inactive for 30 days and not currently in use.`
+											);
+											CacheModule.runJob(
+												"HDEL",
+												{
+													table: "sessions",
+													key: session.sessionId
+												},
+												this
+											).finally(() => next2());
+										}
+									});
 								}
-								this.log("ERROR", "TASK_SESSION_CLEAR", "This should never log.");
+								TasksModule.log("ERROR", "TASK_SESSION_CLEAR", "This should never log.");
 								return next2();
 							},
 							() => next()
@@ -283,7 +300,7 @@ class TasksModule extends CoreClass {
 	 */
 	logFileSizeCheckTask() {
 		return new Promise((resolve, reject) => {
-			this.log("INFO", "TASK_LOG_FILE_SIZE_CHECK", `Checking the size for the log files.`);
+			TasksModule.log("INFO", "TASK_LOG_FILE_SIZE_CHECK", `Checking the size for the log files.`);
 			async.each(
 				["all.log", "debugStation.log", "error.log", "info.log", "success.log"],
 				(fileName, next) => {
@@ -299,26 +316,26 @@ class TasksModule extends CoreClass {
 				},
 				async err => {
 					if (err && err !== true) {
-						err = await this.utils.runJob("GET_ERROR", { error: err });
+						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 						return reject(new Error(err));
 					}
 					if (err === true) {
-						this.log(
+						TasksModule.log(
 							"ERROR",
 							"LOGGER_FILE_SIZE_WARNING",
 							"************************************WARNING*************************************"
 						);
-						this.log(
+						TasksModule.log(
 							"ERROR",
 							"LOGGER_FILE_SIZE_WARNING",
 							"***************ONE OR MORE LOG FILES APPEAR TO BE MORE THAN 25MB****************"
 						);
-						this.log(
+						TasksModule.log(
 							"ERROR",
 							"LOGGER_FILE_SIZE_WARNING",
 							"****MAKE SURE TO REGULARLY CLEAR UP THE LOG FILES, MANUALLY OR AUTOMATICALLY****"
 						);
-						this.log(
+						TasksModule.log(
 							"ERROR",
 							"LOGGER_FILE_SIZE_WARNING",
 							"********************************************************************************"
@@ -332,4 +349,4 @@ class TasksModule extends CoreClass {
 	}
 }
 
-export default new TasksModule();
+export default new _TasksModule();

+ 81 - 41
backend/logic/utils.js

@@ -5,7 +5,12 @@ import crypto from "crypto";
 import request from "request";
 import CoreClass from "../core";
 
-class UtilsModule extends CoreClass {
+let UtilsModule;
+let IOModule;
+let SpotifyModule;
+let CacheModule;
+
+class _UtilsModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("utils");
@@ -13,6 +18,8 @@ class UtilsModule extends CoreClass {
 		this.youtubeRequestCallbacks = [];
 		this.youtubeRequestsPending = 0;
 		this.youtubeRequestsActive = false;
+
+		UtilsModule = this;
 	}
 
 	/**
@@ -22,10 +29,9 @@ class UtilsModule extends CoreClass {
 	 */
 	initialize() {
 		return new Promise(resolve => {
-			this.io = this.moduleManager.modules.io;
-			this.db = this.moduleManager.modules.db;
-			this.spotify = this.moduleManager.modules.spotify;
-			this.cache = this.moduleManager.modules.cache;
+			IOModule = this.moduleManager.modules.io;
+			SpotifyModule = this.moduleManager.modules.spotify;
+			CacheModule = this.moduleManager.modules.cache;
 
 			resolve();
 		});
@@ -79,9 +85,13 @@ class UtilsModule extends CoreClass {
 			let cookies;
 
 			try {
-				cookies = this.runJob("PARSE_COOKIES", {
-					cookieString: payload.cookieString
-				});
+				cookies = UtilsModule.runJob(
+					"PARSE_COOKIES",
+					{
+						cookieString: payload.cookieString
+					},
+					this
+				);
 			} catch (err) {
 				return reject(err);
 			}
@@ -123,11 +133,19 @@ class UtilsModule extends CoreClass {
 
 		const promises = [];
 		for (let i = 0; i < payload.length; i += 1) {
+			this.log(
+				"ERROR",
+				"CHECK THIS!?!?!?!??!?!?!?!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
+			);
 			promises.push(
-				this.runJob("GET_RANDOM_NUMBER", {
-					min: 0,
-					max: chars.length - 1
-				})
+				UtilsModule.runJob(
+					"GET_RANDOM_NUMBER",
+					{
+						min: 0,
+						max: chars.length - 1
+					},
+					this
+				)
 			);
 		}
 
@@ -150,7 +168,7 @@ class UtilsModule extends CoreClass {
 	 */
 	async GET_SOCKET_FROM_ID(payload) {
 		// socketId
-		const io = await this.io.runJob("IO", {});
+		const io = await IOModule.runJob("IO", {}, this);
 
 		return new Promise(resolve => resolve(io.sockets.sockets[payload.socketId]));
 	}
@@ -248,7 +266,7 @@ class UtilsModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	async SOCKET_FROM_SESSION(payload) {
 		// socketId
-		const io = await this.io.runJob("IO", {});
+		const io = await IOModule.runJob("IO", {}, this);
 
 		return new Promise((resolve, reject) => {
 			const ns = io.of("/");
@@ -268,7 +286,7 @@ class UtilsModule extends CoreClass {
 	 * @returns {Promise} - returns promise (reject, resolve)
 	 */
 	async SOCKETS_FROM_SESSION_ID(payload) {
-		const io = await this.io.runJob("IO", {});
+		const io = await IOModule.runJob("IO", {}, this);
 
 		return new Promise(resolve => {
 			const ns = io.of("/");
@@ -300,7 +318,7 @@ class UtilsModule extends CoreClass {
 	 * @returns {Promise} - returns promise (reject, resolve)
 	 */
 	async SOCKETS_FROM_USER(payload) {
-		const io = await this.io.runJob("IO", {});
+		const io = await IOModule.runJob("IO", {}, this);
 
 		return new Promise((resolve, reject) => {
 			const ns = io.of("/");
@@ -311,11 +329,14 @@ class UtilsModule extends CoreClass {
 					Object.keys(ns.connected),
 					(id, next) => {
 						const { session } = ns.connected[id];
-						this.cache
-							.runJob("HGET", {
+						CacheModule.runJob(
+							"HGET",
+							{
 								table: "sessions",
 								key: session.sessionId
-							})
+							},
+							this
+						)
 							.then(session => {
 								if (session && session.userId === payload.userId) sockets.push(ns.connected[id]);
 								next();
@@ -343,7 +364,7 @@ class UtilsModule extends CoreClass {
 	 * @returns {Promise} - returns promise (reject, resolve)
 	 */
 	async SOCKETS_FROM_IP(payload) {
-		const io = await this.io.runJob("IO", {});
+		const io = await IOModule.runJob("IO", {}, this);
 
 		return new Promise(resolve => {
 			const ns = io.of("/");
@@ -353,11 +374,14 @@ class UtilsModule extends CoreClass {
 					Object.keys(ns.connected),
 					(id, next) => {
 						const { session } = ns.connected[id];
-						this.cache
-							.runJob("HGET", {
+						CacheModule.runJob(
+							"HGET",
+							{
 								table: "sessions",
 								key: session.sessionId
-							})
+							},
+							this
+						)
 							.then(session => {
 								if (session && ns.connected[id].ip === payload.ip) sockets.push(ns.connected[id]);
 								next();
@@ -382,7 +406,7 @@ class UtilsModule extends CoreClass {
 	 * @returns {Promise} - returns promise (reject, resolve)
 	 */
 	async SOCKETS_FROM_USER_WITHOUT_CACHE(payload) {
-		const io = await this.io.runJob("IO", {});
+		const io = await IOModule.runJob("IO", {}, this);
 
 		return new Promise(resolve => {
 			const ns = io.of("/");
@@ -414,9 +438,13 @@ class UtilsModule extends CoreClass {
 	 * @returns {Promise} - returns promise (reject, resolve)
 	 */
 	async SOCKET_LEAVE_ROOMS(payload) {
-		const socket = await this.runJob("SOCKET_FROM_SESSION", {
-			socketId: payload.socketId
-		});
+		const socket = await UtilsModule.runJob(
+			"SOCKET_FROM_SESSION",
+			{
+				socketId: payload.socketId
+			},
+			this
+		);
 
 		return new Promise(resolve => {
 			const { rooms } = socket;
@@ -437,9 +465,13 @@ class UtilsModule extends CoreClass {
 	 * @returns {Promise} - returns promise (reject, resolve)
 	 */
 	async SOCKET_JOIN_ROOM(payload) {
-		const socket = await this.runJob("SOCKET_FROM_SESSION", {
-			socketId: payload.socketId
-		});
+		const socket = await UtilsModule.runJob(
+			"SOCKET_FROM_SESSION",
+			{
+				socketId: payload.socketId
+			},
+			this
+		);
 
 		return new Promise(resolve => {
 			const { rooms } = socket;
@@ -457,9 +489,13 @@ class UtilsModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	async SOCKET_JOIN_SONG_ROOM(payload) {
 		// socketId, room
-		const socket = await this.runJob("SOCKET_FROM_SESSION", {
-			socketId: payload.socketId
-		});
+		const socket = await UtilsModule.runJob(
+			"SOCKET_FROM_SESSION",
+			{
+				socketId: payload.socketId
+			},
+			this
+		);
 
 		return new Promise(resolve => {
 			const { rooms } = socket;
@@ -518,7 +554,7 @@ class UtilsModule extends CoreClass {
 	 * @returns {Promise} - returns promise (reject, resolve)
 	 */
 	async EMIT_TO_ROOM(payload) {
-		const io = await this.io.runJob("IO", {});
+		const io = await IOModule.runJob("IO", {}, this);
 
 		return new Promise(resolve => {
 			const { sockets } = io.sockets;
@@ -541,7 +577,7 @@ class UtilsModule extends CoreClass {
 	 * @returns {Promise} - returns promise (reject, resolve)
 	 */
 	async GET_ROOM_SOCKETS(payload) {
-		const io = await this.io.runJob("IO", {});
+		const io = await IOModule.runJob("IO", {}, this);
 
 		return new Promise(resolve => {
 			const { sockets } = io.sockets;
@@ -763,9 +799,13 @@ class UtilsModule extends CoreClass {
 
 						if (!payload.musicOnly) return resolve({ songs });
 						return local
-							.runJob("FILTER_MUSIC_VIDEOS_YOUTUBE", {
-								videoIds: songs.slice()
-							})
+							.runJob(
+								"FILTER_MUSIC_VIDEOS_YOUTUBE",
+								{
+									videoIds: songs.slice()
+								},
+								this
+							)
 							.then(filteredSongs => {
 								resolve({ filteredSongs, songs });
 							});
@@ -786,7 +826,7 @@ class UtilsModule extends CoreClass {
 	 */
 	async GET_SONG_FROM_SPOTIFY(payload) {
 		// song
-		const token = await this.spotify.runJob("GET_TOKEN", {});
+		const token = await SpotifyModule.runJob("GET_TOKEN", {}, this);
 
 		return new Promise((resolve, reject) => {
 			if (!config.get("apis.spotify.enabled")) return reject(new Error("Spotify is not enabled."));
@@ -840,7 +880,7 @@ class UtilsModule extends CoreClass {
 	 */
 	async GET_SONGS_FROM_SPOTIFY(payload) {
 		// title, artist
-		const token = await this.spotify.runJob("GET_TOKEN", {});
+		const token = await SpotifyModule.runJob("GET_TOKEN", {}, this);
 
 		return new Promise((resolve, reject) => {
 			if (!config.get("apis.spotify.enabled")) return reject(new Error("Spotify is not enabled."));
@@ -966,4 +1006,4 @@ class UtilsModule extends CoreClass {
 	}
 }
 
-export default new UtilsModule();
+export default new _UtilsModule();