Browse Source

Merge branch 'polishing' of github.com:Musare/MusareNode into polishing

Jonathan 4 years ago
parent
commit
acc190ad2e
34 changed files with 1448 additions and 1362 deletions
  1. 9 1
      backend/index.js
  2. 8 7
      backend/logic/actions/activities.js
  3. 11 13
      backend/logic/actions/apis.js
  4. 11 10
      backend/logic/actions/hooks/adminRequired.js
  5. 9 9
      backend/logic/actions/hooks/loginRequired.js
  6. 13 13
      backend/logic/actions/hooks/ownerRequired.js
  7. 24 23
      backend/logic/actions/news.js
  8. 89 109
      backend/logic/actions/playlists.js
  9. 20 22
      backend/logic/actions/punishments.js
  10. 35 36
      backend/logic/actions/queueSongs.js
  11. 24 24
      backend/logic/actions/reports.js
  12. 72 76
      backend/logic/actions/songs.js
  13. 170 202
      backend/logic/actions/stations.js
  14. 137 147
      backend/logic/actions/users.js
  15. 7 5
      backend/logic/actions/utils.js
  16. 3 1
      backend/logic/activities.js
  17. 323 1
      backend/logic/io.js
  18. 1 1
      backend/logic/punishments.js
  19. 11 9
      backend/logic/stations.js
  20. 3 1
      backend/logic/tasks.js
  21. 0 571
      backend/logic/utils.js
  22. 292 0
      backend/logic/youtube.js
  23. 0 5
      frontend/dist/index.css
  24. 2 2
      frontend/src/App.vue
  25. 1 2
      frontend/src/components/ui/PlaylistItem.vue
  26. 31 11
      frontend/src/pages/Home/index.vue
  27. 2 1
      frontend/src/pages/Profile.vue
  28. 45 16
      frontend/src/pages/Station/components/CurrentlyPlaying.vue
  29. 5 1
      frontend/src/pages/Station/components/Sidebar/MyPlaylists.vue
  30. 21 19
      frontend/src/pages/Station/components/Sidebar/Queue/index.vue
  31. 1 0
      frontend/src/pages/Station/components/Sidebar/Users.vue
  32. 2 2
      frontend/src/pages/Station/components/Sidebar/index.vue
  33. 65 22
      frontend/src/pages/Station/index.vue
  34. 1 0
      frontend/src/styles/colors.scss

+ 9 - 1
backend/index.js

@@ -395,6 +395,7 @@ moduleManager.addModule("songs");
 moduleManager.addModule("stations");
 moduleManager.addModule("tasks");
 moduleManager.addModule("utils");
+moduleManager.addModule("youtube");
 
 moduleManager.initialize();
 
@@ -441,7 +442,14 @@ process.stdin.on("data", data => {
 		console.log(moduleManager.modules[parts[1]].jobStatistics);
 	}
 	if (command.startsWith("debug")) {
-		moduleManager.modules.utils.runJob("DEBUG");
+		moduleManager.modules.youtube
+			.runJob("GET_PLAYLIST", { url: "https://www.youtube.com/playlist?list=PLN-cFDG8y28Pz4dkAFwDNH0as0-prFfvR" })
+			.then(response => {
+				console.log(1111, response);
+			})
+			.catch(err => {
+				console.log(1112, err);
+			});
 	}
 
 	if (command.startsWith("eval")) {

+ 8 - 7
backend/logic/actions/activities.js

@@ -1,10 +1,11 @@
 import async from "async";
 
 import { isLoginRequired } from "./hooks";
-import db from "../db";
-import utils from "../utils";
 
-// const logger = moduleManager.modules["logger"];
+import moduleManager from "../../index";
+
+const DBModule = moduleManager.modules.db;
+const UtilsModule = moduleManager.modules.utils;
 
 export default {
 	/**
@@ -16,7 +17,7 @@ export default {
 	 * @param {Function} cb - callback
 	 */
 	getSet: async (session, userId, set, cb) => {
-		const activityModel = await db.runJob("GET_MODEL", {
+		const activityModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "activity"
 		});
 		async.waterfall(
@@ -32,7 +33,7 @@ export default {
 			],
 			async (err, activities) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "ACTIVITIES_GET_SET", `Failed to get set ${set} from activities. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
@@ -51,7 +52,7 @@ export default {
 	 * @param cb
 	 */
 	hideActivity: isLoginRequired(async (session, activityId, cb) => {
-		const activityModel = await db.runJob("GET_MODEL", {
+		const activityModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "activity"
 		});
 		async.waterfall(
@@ -62,7 +63,7 @@ export default {
 			],
 			async err => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "ACTIVITIES_HIDE_ACTIVITY", `Failed to hide activity ${activityId}. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}

+ 11 - 13
backend/logic/actions/apis.js

@@ -1,14 +1,13 @@
 import config from "config";
-
 import async from "async";
-
 import request from "request";
+
 import { isAdminRequired } from "./hooks";
-// const moduleManager = require("../../index");
 
-import utils from "../utils";
+import moduleManager from "../../index";
 
-// const logger = moduleManager.modules["logger"];
+const UtilsModule = moduleManager.modules.utils;
+const IOModule = moduleManager.modules.io;
 
 export default {
 	/**
@@ -42,7 +41,7 @@ export default {
 				console.log(data.error);
 				if (err || data.error) {
 					if (!err) err = data.error.message;
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"APIS_SEARCH_YOUTUBE",
@@ -89,7 +88,7 @@ export default {
 			],
 			async (err, body) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"APIS_SEARCH_DISCOGS",
@@ -120,11 +119,10 @@ export default {
 	 */
 	joinRoom: (session, page, cb) => {
 		if (page === "home") {
-			utils
-				.runJob("SOCKET_JOIN_ROOM", {
-					socketId: session.socketId,
-					room: page
-				})
+			IOModule.runJob("SOCKET_JOIN_ROOM", {
+				socketId: session.socketId,
+				room: page
+			})
 				.then()
 				.catch(err => {
 					console.log("ERROR", `Joining room failed: ${err.message}`);
@@ -151,7 +149,7 @@ export default {
 			page === "statistics" ||
 			page === "punishments"
 		) {
-			utils.runJob("SOCKET_JOIN_ROOM", {
+			IOModule.runJob("SOCKET_JOIN_ROOM", {
 				socketId: session.socketId,
 				room: `admin.${page}`
 			});

+ 11 - 10
backend/logic/actions/hooks/adminRequired.js

@@ -1,22 +1,23 @@
 import async from "async";
 
-import db from "../../db";
-import cache from "../../cache";
-import utils from "../../utils";
+import moduleManager from "../../../index";
+
+const DBModule = moduleManager.modules.db;
+const CacheModule = moduleManager.modules.cache;
+const UtilsModule = moduleManager.modules.utils;
 
 export default destination => async (session, ...args) => {
-	const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+	const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 
 	const cb = args[args.length - 1];
 
 	async.waterfall(
 		[
 			next => {
-				cache
-					.runJob("HGET", {
-						table: "sessions",
-						key: session.sessionId
-					})
+				CacheModule.runJob("HGET", {
+					table: "sessions",
+					key: session.sessionId
+				})
 					.then(session => {
 						next(null, session);
 					})
@@ -34,7 +35,7 @@ export default destination => async (session, ...args) => {
 		],
 		async err => {
 			if (err) {
-				err = await utils.runJob("GET_ERROR", { error: err });
+				err = await UtilsModule.runJob("GET_ERROR", { error: err });
 				console.log("INFO", "ADMIN_REQUIRED", `User failed to pass admin required check. "${err}"`);
 				return cb({ status: "failure", message: err });
 			}

+ 9 - 9
backend/logic/actions/hooks/loginRequired.js

@@ -1,8 +1,9 @@
 import async from "async";
 
-import cache from "../../cache";
-import utils from "../../utils";
-// const logger = moduleManager.modules["logger"];
+import moduleManager from "../../../index";
+
+const CacheModule = moduleManager.modules.cache;
+const UtilsModule = moduleManager.modules.utils;
 
 export default destination => (session, ...args) => {
 	const cb = args[args.length - 1];
@@ -10,11 +11,10 @@ export default destination => (session, ...args) => {
 	async.waterfall(
 		[
 			next => {
-				cache
-					.runJob("HGET", {
-						table: "sessions",
-						key: session.sessionId
-					})
+				CacheModule.runJob("HGET", {
+					table: "sessions",
+					key: session.sessionId
+				})
 					.then(session => next(null, session))
 					.catch(next);
 			},
@@ -25,7 +25,7 @@ export default destination => (session, ...args) => {
 		],
 		async err => {
 			if (err) {
-				err = await utils.runJob("GET_ERROR", { error: err });
+				err = await UtilsModule.runJob("GET_ERROR", { error: err });
 				console.log("LOGIN_REQUIRED", `User failed to pass login required check.`);
 				return cb({ status: "failure", message: err });
 			}

+ 13 - 13
backend/logic/actions/hooks/ownerRequired.js

@@ -1,23 +1,24 @@
 import async from "async";
 
-import db from "../../db";
-import cache from "../../cache";
-import utils from "../../utils";
-import stations from "../../stations";
+import moduleManager from "../../../index";
+
+const DBModule = moduleManager.modules.db;
+const CacheModule = moduleManager.modules.cache;
+const UtilsModule = moduleManager.modules.utils;
+const StationsModule = moduleManager.modules.stations;
 
 export default destination => async (session, stationId, ...args) => {
-	const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+	const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 
 	const cb = args[args.length - 1];
 
 	async.waterfall(
 		[
 			next => {
-				cache
-					.runJob("HGET", {
-						table: "sessions",
-						key: session.sessionId
-					})
+				CacheModule.runJob("HGET", {
+					table: "sessions",
+					key: session.sessionId
+				})
 					.then(session => {
 						next(null, session);
 					})
@@ -30,8 +31,7 @@ export default destination => async (session, stationId, ...args) => {
 			(user, next) => {
 				if (!user) return next("Login required.");
 				if (user.role === "admin") return next(true);
-				return stations
-					.runJob("GET_STATION", { stationId })
+				return StationsModule.runJob("GET_STATION", { stationId })
 					.then(station => {
 						next(null, station);
 					})
@@ -45,7 +45,7 @@ export default destination => async (session, stationId, ...args) => {
 		],
 		async err => {
 			if (err !== true) {
-				err = await utils.runJob("GET_ERROR", { error: err });
+				err = await UtilsModule.runJob("GET_ERROR", { error: err });
 				console.log(
 					"INFO",
 					"OWNER_REQUIRED",

+ 24 - 23
backend/logic/actions/news.js

@@ -2,36 +2,37 @@ import async from "async";
 
 import { isAdminRequired } from "./hooks";
 
-import db from "../db";
-import utils from "../utils";
+import moduleManager from "../../index";
 
-import cache from "../cache";
-// const logger = require("logger");
+const DBModule = moduleManager.modules.db;
+const UtilsModule = moduleManager.modules.utils;
+const IOModule = moduleManager.modules.io;
+const CacheModule = moduleManager.modules.cache;
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "news.create",
 	cb: news => {
-		utils.runJob("EMIT_TO_ROOM", {
+		IOModule.runJob("EMIT_TO_ROOM", {
 			room: "admin.news",
 			args: ["event:admin.news.created", news]
 		});
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "news.remove",
 	cb: news => {
-		utils.runJob("EMIT_TO_ROOM", {
+		IOModule.runJob("EMIT_TO_ROOM", {
 			room: "admin.news",
 			args: ["event:admin.news.removed", news]
 		});
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "news.update",
 	cb: news => {
-		utils.runJob("EMIT_TO_ROOM", {
+		IOModule.runJob("EMIT_TO_ROOM", {
 			room: "admin.news",
 			args: ["event:admin.news.updated", news]
 		});
@@ -46,7 +47,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	index: async (session, cb) => {
-		const newsModel = await db.runJob("GET_MODEL", { modelName: "news" });
+		const newsModel = await DBModule.runJob("GET_MODEL", { modelName: "news" });
 		async.waterfall(
 			[
 				next => {
@@ -55,7 +56,7 @@ export default {
 			],
 			async (err, news) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "NEWS_INDEX", `Indexing news failed. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
@@ -73,7 +74,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	create: isAdminRequired(async (session, data, cb) => {
-		const newsModel = await db.runJob("GET_MODEL", { modelName: "news" });
+		const newsModel = await DBModule.runJob("GET_MODEL", { modelName: "news" });
 		async.waterfall(
 			[
 				next => {
@@ -84,11 +85,11 @@ export default {
 			],
 			async (err, news) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "NEWS_CREATE", `Creating news failed. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
-				cache.runJob("PUB", { channel: "news.create", value: news });
+				CacheModule.runJob("PUB", { channel: "news.create", value: news });
 				console.log("SUCCESS", "NEWS_CREATE", `Creating news successful.`);
 				return cb({
 					status: "success",
@@ -105,7 +106,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	newest: async (session, cb) => {
-		const newsModel = await db.runJob("GET_MODEL", { modelName: "news" });
+		const newsModel = await DBModule.runJob("GET_MODEL", { modelName: "news" });
 		async.waterfall(
 			[
 				next => {
@@ -114,7 +115,7 @@ export default {
 			],
 			async (err, news) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "NEWS_NEWEST", `Getting the latest news failed. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
@@ -134,10 +135,10 @@ export default {
 	// TODO Pass in an id, not an object
 	// TODO Fix this
 	remove: isAdminRequired(async (session, news, cb) => {
-		const newsModel = await db.runJob("GET_MODEL", { modelName: "news" });
+		const newsModel = await DBModule.runJob("GET_MODEL", { modelName: "news" });
 		newsModel.deleteOne({ _id: news._id }, async err => {
 			if (err) {
-				err = await utils.runJob("GET_ERROR", { error: err });
+				err = await UtilsModule.runJob("GET_ERROR", { error: err });
 				console.log(
 					"ERROR",
 					"NEWS_REMOVE",
@@ -145,7 +146,7 @@ export default {
 				);
 				return cb({ status: "failure", message: err });
 			}
-			cache.runJob("PUB", { channel: "news.remove", value: news });
+			CacheModule.runJob("PUB", { channel: "news.remove", value: news });
 			console.log(
 				"SUCCESS",
 				"NEWS_REMOVE",
@@ -168,10 +169,10 @@ export default {
 	 */
 	// TODO Fix this
 	update: isAdminRequired(async (session, _id, news, cb) => {
-		const newsModel = await db.runJob("GET_MODEL", { modelName: "news" });
+		const newsModel = await DBModule.runJob("GET_MODEL", { modelName: "news" });
 		newsModel.updateOne({ _id }, news, { upsert: true }, async err => {
 			if (err) {
-				err = await utils.runJob("GET_ERROR", { error: err });
+				err = await UtilsModule.runJob("GET_ERROR", { error: err });
 				console.log(
 					"ERROR",
 					"NEWS_UPDATE",
@@ -179,7 +180,7 @@ export default {
 				);
 				return cb({ status: "failure", message: err });
 			}
-			cache.runJob("PUB", { channel: "news.update", value: news });
+			CacheModule.runJob("PUB", { channel: "news.update", value: news });
 			console.log("SUCCESS", "NEWS_UPDATE", `Updating news "${_id}" successful for user "${session.userId}".`);
 			return cb({
 				status: "success",

+ 89 - 109
backend/logic/actions/playlists.js

@@ -2,21 +2,21 @@ import async from "async";
 
 import { isLoginRequired } from "./hooks";
 
-import db from "../db";
-import utils from "../utils";
-import songs from "../songs";
-
-import cache from "../cache";
-
 import moduleManager from "../../index";
 
-import playlists from "../playlists";
-import activities from "../activities";
+const DBModule = moduleManager.modules.db;
+const UtilsModule = moduleManager.modules.utils;
+const IOModule = moduleManager.modules.io;
+const SongsModule = moduleManager.modules.songs;
+const CacheModule = moduleManager.modules.cache;
+const PlaylistsModule = moduleManager.modules.playlists;
+const YouTubeModule = moduleManager.modules.youtube;
+const ActivitiesModule = moduleManager.modules.activities;
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "playlist.create",
 	cb: playlist => {
-		utils.runJob("SOCKETS_FROM_USER", { userId: playlist.createdBy }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: playlist.createdBy }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:playlist.create", playlist);
 			});
@@ -24,10 +24,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "playlist.delete",
 	cb: res => {
-		utils.runJob("SOCKETS_FROM_USER", { userId: res.userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: res.userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:playlist.delete", res.playlistId);
 			});
@@ -35,10 +35,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "playlist.moveSongToTop",
 	cb: res => {
-		utils.runJob("SOCKETS_FROM_USER", { userId: res.userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: res.userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:playlist.moveSongToTop", {
 					playlistId: res.playlistId,
@@ -49,10 +49,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "playlist.moveSongToBottom",
 	cb: res => {
-		utils.runJob("SOCKETS_FROM_USER", { userId: res.userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: res.userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:playlist.moveSongToBottom", {
 					playlistId: res.playlistId,
@@ -63,10 +63,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "playlist.addSong",
 	cb: res => {
-		utils.runJob("SOCKETS_FROM_USER", { userId: res.userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: res.userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:playlist.addSong", {
 					playlistId: res.playlistId,
@@ -77,10 +77,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "playlist.removeSong",
 	cb: res => {
-		utils.runJob("SOCKETS_FROM_USER", { userId: res.userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: res.userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:playlist.removeSong", {
 					playlistId: res.playlistId,
@@ -91,10 +91,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "playlist.updateDisplayName",
 	cb: res => {
-		utils.runJob("SOCKETS_FROM_USER", { userId: res.userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: res.userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:playlist.updateDisplayName", {
 					playlistId: res.playlistId,
@@ -117,8 +117,7 @@ const lib = {
 		async.waterfall(
 			[
 				next => {
-					playlists
-						.runJob("GET_PLAYLIST", { playlistId })
+					PlaylistsModule.runJob("GET_PLAYLIST", { playlistId })
 						.then(playlist => {
 							next(null, playlist);
 						})
@@ -132,7 +131,7 @@ const lib = {
 			],
 			async (err, song) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"PLAYLIST_GET_FIRST_SONG",
@@ -160,7 +159,7 @@ const lib = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	indexForUser: isLoginRequired(async (session, cb) => {
-		const playlistModel = await db.runJob("GET_MODEL", {
+		const playlistModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "playlist"
 		});
 		async.waterfall(
@@ -171,7 +170,7 @@ const lib = {
 			],
 			async (err, playlists) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"PLAYLIST_INDEX_FOR_USER",
@@ -200,7 +199,7 @@ const lib = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	create: isLoginRequired(async (session, data, cb) => {
-		const playlistModel = await db.runJob("GET_MODEL", {
+		const playlistModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "playlist"
 		});
 		async.waterfall(
@@ -222,7 +221,7 @@ const lib = {
 			],
 			async (err, playlist) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"PLAYLIST_CREATE",
@@ -231,12 +230,12 @@ const lib = {
 					return cb({ status: "failure", message: err });
 				}
 
-				cache.runJob("PUB", {
+				CacheModule.runJob("PUB", {
 					channel: "playlist.create",
 					value: playlist
 				});
 
-				activities.runJob("ADD_ACTIVITY", {
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
 					userId: session.userId,
 					activityType: "created_playlist",
 					payload: [playlist._id]
@@ -270,8 +269,7 @@ const lib = {
 		async.waterfall(
 			[
 				next => {
-					playlists
-						.runJob("GET_PLAYLIST", { playlistId })
+					PlaylistsModule.runJob("GET_PLAYLIST", { playlistId })
 						.then(playlist => {
 							next(null, playlist);
 						})
@@ -285,7 +283,7 @@ const lib = {
 			],
 			async (err, playlist) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"PLAYLIST_GET",
@@ -317,8 +315,7 @@ const lib = {
 		async.waterfall(
 			[
 				next => {
-					playlists
-						.runJob("GET_PLAYLIST", { playlistId })
+					PlaylistsModule.runJob("GET_PLAYLIST", { playlistId })
 						.then(playlist => {
 							next(null, playlist);
 						})
@@ -327,7 +324,7 @@ const lib = {
 			],
 			async (err, playlist) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"PLAYLISTS_GET_PLAYLIST_FOR_ACTIVITY",
@@ -360,7 +357,7 @@ const lib = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	update: isLoginRequired(async (session, playlistId, playlist, cb) => {
-		const playlistModel = await db.runJob("GET_MODEL", {
+		const playlistModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "playlist"
 		});
 		async.waterfall(
@@ -375,8 +372,7 @@ const lib = {
 				},
 
 				(res, next) => {
-					playlists
-						.runJob("UPDATE_PLAYLIST", { playlistId })
+					PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId })
 						.then(playlist => {
 							next(null, playlist);
 						})
@@ -385,7 +381,7 @@ const lib = {
 			],
 			async (err, playlist) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"PLAYLIST_UPDATE",
@@ -414,7 +410,7 @@ const lib = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	shuffle: isLoginRequired(async (session, playlistId, cb) => {
-		const playlistModel = await db.runJob("GET_MODEL", {
+		const playlistModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "playlist"
 		});
 		async.waterfall(
@@ -425,8 +421,7 @@ const lib = {
 				},
 
 				(playlist, next) => {
-					utils
-						.runJob("SHUFFLE", { array: playlist.songs })
+					UtilsModule.runJob("SHUFFLE", { array: playlist.songs })
 						.then(result => {
 							next(null, result.array);
 						})
@@ -438,8 +433,7 @@ const lib = {
 				},
 
 				(res, next) => {
-					playlists
-						.runJob("UPDATE_PLAYLIST", { playlistId })
+					PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId })
 						.then(playlist => {
 							next(null, playlist);
 						})
@@ -448,7 +442,7 @@ const lib = {
 			],
 			async (err, playlist) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"PLAYLIST_SHUFFLE",
@@ -480,15 +474,14 @@ const lib = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	addSongToPlaylist: isLoginRequired(async (session, isSet, songId, playlistId, cb) => {
-		const playlistModel = await db.runJob("GET_MODEL", {
+		const playlistModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "playlist"
 		});
 
 		async.waterfall(
 			[
 				next => {
-					playlists
-						.runJob("GET_PLAYLIST", { playlistId })
+					PlaylistsModule.runJob("GET_PLAYLIST", { playlistId })
 						.then(playlist => {
 							if (!playlist || playlist.createdBy !== session.userId)
 								return next("Something went wrong when trying to get the playlist");
@@ -505,8 +498,7 @@ const lib = {
 						.catch(next);
 				},
 				next => {
-					songs
-						.runJob("GET_SONG", { id: songId })
+					SongsModule.runJob("GET_SONG", { id: songId })
 						.then(response => {
 							const { song } = response;
 							next(null, {
@@ -517,8 +509,7 @@ const lib = {
 							});
 						})
 						.catch(() => {
-							utils
-								.runJob("GET_SONG_FROM_YOUTUBE", { songId })
+							YouTubeModule.runJob("GET_SONG", { songId })
 								.then(response => next(null, response.song))
 								.catch(next);
 						});
@@ -530,8 +521,7 @@ const lib = {
 						{ runValidators: true },
 						err => {
 							if (err) return next(err);
-							return playlists
-								.runJob("UPDATE_PLAYLIST", { playlistId })
+							return PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId })
 								.then(playlist => next(null, playlist, newSong))
 								.catch(next);
 						}
@@ -540,7 +530,7 @@ const lib = {
 			],
 			async (err, playlist, newSong) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"PLAYLIST_ADD_SONG",
@@ -554,13 +544,13 @@ const lib = {
 					`Successfully added song "${songId}" to private playlist "${playlistId}" for user "${session.userId}".`
 				);
 				if (!isSet)
-					activities.runJob("ADD_ACTIVITY", {
+					ActivitiesModule.runJob("ADD_ACTIVITY", {
 						userId: session.userId,
 						activityType: "added_song_to_playlist",
 						payload: [{ songId, playlistId }]
 					});
 
-				cache.runJob("PUB", {
+				CacheModule.runJob("PUB", {
 					channel: "playlist.addSong",
 					value: {
 						playlistId: playlist._id,
@@ -597,20 +587,18 @@ const lib = {
 		async.waterfall(
 			[
 				next => {
-					utils
-						.runJob("GET_PLAYLIST_FROM_YOUTUBE", {
-							url,
-							musicOnly
-						})
-						.then(response => {
-							if (response.filteredSongs) {
-								videosInPlaylistTotal = response.songs.length;
-								songsInPlaylistTotal = response.filteredSongs.length;
-							} else {
-								songsInPlaylistTotal = videosInPlaylistTotal = response.songs.length;
-							}
-							next(null, response.songs);
-						});
+					YouTubeModule.runJob("GET_PLAYLIST", {
+						url,
+						musicOnly
+					}).then(response => {
+						if (response.filteredSongs) {
+							videosInPlaylistTotal = response.songs.length;
+							songsInPlaylistTotal = response.filteredSongs.length;
+						} else {
+							songsInPlaylistTotal = videosInPlaylistTotal = response.songs.length;
+						}
+						next(null, response.songs);
+					});
 				},
 				(songIds, next) => {
 					let processed = 0;
@@ -635,8 +623,7 @@ const lib = {
 				},
 
 				next => {
-					playlists
-						.runJob("GET_PLAYLIST", { playlistId })
+					PlaylistsModule.runJob("GET_PLAYLIST", { playlistId })
 						.then(playlist => {
 							next(null, playlist);
 						})
@@ -650,7 +637,7 @@ const lib = {
 			],
 			async (err, playlist) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"PLAYLIST_IMPORT",
@@ -658,7 +645,7 @@ const lib = {
 					);
 					return cb({ status: "failure", message: err });
 				}
-				activities.runJob("ADD_ACTIVITY", {
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
 					userId: session.userId,
 					activityType: "added_songs_to_playlist",
 					payload: addedSongs
@@ -692,7 +679,7 @@ const lib = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	removeSongFromPlaylist: isLoginRequired(async (session, songId, playlistId, cb) => {
-		const playlistModel = await db.runJob("GET_MODEL", {
+		const playlistModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "playlist"
 		});
 		async.waterfall(
@@ -704,8 +691,7 @@ const lib = {
 				},
 
 				next => {
-					playlists
-						.runJob("GET_PLAYLIST", { playlistId })
+					PlaylistsModule.runJob("GET_PLAYLIST", { playlistId })
 						.then(playlist => {
 							next(null, playlist);
 						})
@@ -718,8 +704,7 @@ const lib = {
 				},
 
 				(res, next) => {
-					playlists
-						.runJob("UPDATE_PLAYLIST", { playlistId })
+					PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId })
 						.then(playlist => {
 							next(null, playlist);
 						})
@@ -728,7 +713,7 @@ const lib = {
 			],
 			async (err, playlist) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"PLAYLIST_REMOVE_SONG",
@@ -741,7 +726,7 @@ const lib = {
 					"PLAYLIST_REMOVE_SONG",
 					`Successfully removed song "${songId}" from private playlist "${playlistId}" for user "${session.userId}".`
 				);
-				cache.runJob("PUB", {
+				CacheModule.runJob("PUB", {
 					channel: "playlist.removeSong",
 					value: {
 						playlistId: playlist._id,
@@ -766,7 +751,7 @@ const lib = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	updateDisplayName: isLoginRequired(async (session, playlistId, displayName, cb) => {
-		const playlistModel = await db.runJob("GET_MODEL", {
+		const playlistModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "playlist"
 		});
 		async.waterfall(
@@ -781,8 +766,7 @@ const lib = {
 				},
 
 				(res, next) => {
-					playlists
-						.runJob("UPDATE_PLAYLIST", { playlistId })
+					PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId })
 						.then(playlist => {
 							next(null, playlist);
 						})
@@ -791,7 +775,7 @@ const lib = {
 			],
 			async err => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"PLAYLIST_UPDATE_DISPLAY_NAME",
@@ -804,7 +788,7 @@ const lib = {
 					"PLAYLIST_UPDATE_DISPLAY_NAME",
 					`Successfully updated display name to "${displayName}" for private playlist "${playlistId}" for user "${session.userId}".`
 				);
-				cache.runJob("PUB", {
+				CacheModule.runJob("PUB", {
 					channel: "playlist.updateDisplayName",
 					value: {
 						playlistId,
@@ -829,14 +813,13 @@ const lib = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	moveSongToTop: isLoginRequired(async (session, playlistId, songId, cb) => {
-		const playlistModel = await db.runJob("GET_MODEL", {
+		const playlistModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "playlist"
 		});
 		async.waterfall(
 			[
 				next => {
-					playlists
-						.runJob("GET_PLAYLIST", { playlistId })
+					PlaylistsModule.runJob("GET_PLAYLIST", { playlistId })
 						.then(playlist => {
 							next(null, playlist);
 						})
@@ -881,8 +864,7 @@ const lib = {
 				},
 
 				(res, next) => {
-					playlists
-						.runJob("UPDATE_PLAYLIST", { playlistId })
+					PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId })
 						.then(playlist => {
 							next(null, playlist);
 						})
@@ -891,7 +873,7 @@ const lib = {
 			],
 			async err => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"PLAYLIST_MOVE_SONG_TO_TOP",
@@ -906,7 +888,7 @@ const lib = {
 					`Successfully moved song "${songId}" to the top for private playlist "${playlistId}" for user "${session.userId}".`
 				);
 
-				cache.runJob("PUB", {
+				CacheModule.runJob("PUB", {
 					channel: "playlist.moveSongToTop",
 					value: {
 						playlistId,
@@ -932,14 +914,13 @@ const lib = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	moveSongToBottom: isLoginRequired(async (session, playlistId, songId, cb) => {
-		const playlistModel = await db.runJob("GET_MODEL", {
+		const playlistModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "playlist"
 		});
 		async.waterfall(
 			[
 				next => {
-					playlists
-						.runJob("GET_PLAYLIST", { playlistId })
+					PlaylistsModule.runJob("GET_PLAYLIST", { playlistId })
 						.then(playlist => {
 							next(null, playlist);
 						})
@@ -981,8 +962,7 @@ const lib = {
 				},
 
 				(res, next) => {
-					playlists
-						.runJob("UPDATE_PLAYLIST", { playlistId })
+					PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId })
 						.then(playlist => {
 							next(null, playlist);
 						})
@@ -991,7 +971,7 @@ const lib = {
 			],
 			async err => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"PLAYLIST_MOVE_SONG_TO_BOTTOM",
@@ -1006,7 +986,7 @@ const lib = {
 					`Successfully moved song "${songId}" to the bottom for private playlist "${playlistId}" for user "${session.userId}".`
 				);
 
-				cache.runJob("PUB", {
+				CacheModule.runJob("PUB", {
 					channel: "playlist.moveSongToBottom",
 					value: {
 						playlistId,
@@ -1031,14 +1011,14 @@ const lib = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	remove: isLoginRequired(async (session, playlistId, cb) => {
-		const stationModel = await db.runJob("GET_MODEL", {
+		const stationModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "station"
 		});
 
 		async.waterfall(
 			[
 				next => {
-					playlists.runJob("DELETE_PLAYLIST", { playlistId }).then(next).catch(next);
+					PlaylistsModule.runJob("DELETE_PLAYLIST", { playlistId }).then(next).catch(next);
 				},
 
 				next => {
@@ -1070,7 +1050,7 @@ const lib = {
 												})
 												.then(station => next(null, station))
 												.catch(next);
-											cache.runJob("PUB", {
+											CacheModule.runJob("PUB", {
 												channel: "privatePlaylist.selected",
 												value: {
 													playlistId: null,
@@ -1094,7 +1074,7 @@ const lib = {
 			],
 			async err => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"PLAYLIST_REMOVE",
@@ -1109,7 +1089,7 @@ const lib = {
 					`Successfully removed private playlist "${playlistId}" for user "${session.userId}".`
 				);
 
-				cache.runJob("PUB", {
+				CacheModule.runJob("PUB", {
 					channel: "playlist.delete",
 					value: {
 						userId: session.userId,
@@ -1117,7 +1097,7 @@ const lib = {
 					}
 				});
 
-				activities.runJob("ADD_ACTIVITY", {
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
 					userId: session.userId,
 					activityType: "deleted_playlist",
 					payload: [playlistId]

+ 20 - 22
backend/logic/actions/punishments.js

@@ -1,24 +1,23 @@
 import async from "async";
 
 import { isAdminRequired } from "./hooks";
-import db from "../db";
-// const moduleManager = require("../../index");
 
-// const logger = require("logger");
-import utils from "../utils";
+import moduleManager from "../../index";
 
-import cache from "../cache";
+const DBModule = moduleManager.modules.db;
+const UtilsModule = moduleManager.modules.utils;
+const IOModule = moduleManager.modules.io;
+const CacheModule = moduleManager.modules.cache;
+const PunishmentsModule = moduleManager.modules.punishments;
 
-import punishments from "../punishments";
-
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "ip.ban",
 	cb: data => {
-		utils.runJob("EMIT_TO_ROOM", {
+		IOModule.runJob("EMIT_TO_ROOM", {
 			room: "admin.punishments",
 			args: ["event:admin.punishment.added", data.punishment]
 		});
-		utils.runJob("SOCKETS_FROM_IP", { ip: data.ip }).then(sockets => {
+		IOModule.runJob("SOCKETS_FROM_IP", { ip: data.ip }).then(sockets => {
 			sockets.forEach(socket => {
 				socket.disconnect(true);
 			});
@@ -34,7 +33,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	index: isAdminRequired(async (session, cb) => {
-		const punishmentModel = await db.runJob("GET_MODEL", {
+		const punishmentModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "punishment"
 		});
 		async.waterfall(
@@ -45,7 +44,7 @@ export default {
 			],
 			async (err, punishments) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "PUNISHMENTS_INDEX", `Indexing punishments failed. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
@@ -112,14 +111,13 @@ export default {
 				},
 
 				next => {
-					punishments
-						.runJob("ADD_PUNISHMENT", {
-							type: "banUserIp",
-							value,
-							reason,
-							expiresAt,
-							punishedBy: session.userId
-						})
+					PunishmentsModule.runJob("ADD_PUNISHMENT", {
+						type: "banUserIp",
+						value,
+						reason,
+						expiresAt,
+						punishedBy: session.userId
+					})
 						.then(punishment => {
 							next(null, punishment);
 						})
@@ -128,7 +126,7 @@ export default {
 			],
 			async (err, punishment) => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"BAN_IP",
@@ -141,7 +139,7 @@ export default {
 					"BAN_IP",
 					`User ${session.userId} has successfully banned IP address ${value} with the reason ${reason}.`
 				);
-				cache.runJob("PUB", {
+				CacheModule.runJob("PUB", {
 					channel: "ip.ban",
 					value: { ip: value, punishment }
 				});

+ 35 - 36
backend/logic/actions/queueSongs.js

@@ -4,21 +4,22 @@ import async from "async";
 
 import { isAdminRequired, isLoginRequired } from "./hooks";
 
-import db from "../db";
+import moduleManager from "../../index";
 
-import utils from "../utils";
+const DBModule = moduleManager.modules.db;
+const UtilsModule = moduleManager.modules.utils;
+const IOModule = moduleManager.modules.io;
+const YouTubeModule = moduleManager.modules.youtube;
+const CacheModule = moduleManager.modules.cache;
 
-import cache from "../cache";
-// const logger = moduleManager.modules["logger"];
-
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "queue.newSong",
 	cb: async songId => {
-		const queueSongModel = await db.runJob("GET_MODEL", {
+		const queueSongModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "queueSong"
 		});
 		queueSongModel.findOne({ _id: songId }, (err, song) => {
-			utils.runJob("EMIT_TO_ROOM", {
+			IOModule.runJob("EMIT_TO_ROOM", {
 				room: "admin.queue",
 				args: ["event:admin.queueSong.added", song]
 			});
@@ -26,24 +27,24 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "queue.removedSong",
 	cb: songId => {
-		utils.runJob("EMIT_TO_ROOM", {
+		IOModule.runJob("EMIT_TO_ROOM", {
 			room: "admin.queue",
 			args: ["event:admin.queueSong.removed", songId]
 		});
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "queue.update",
 	cb: async songId => {
-		const queueSongModel = await db.runJob("GET_MODEL", {
+		const queueSongModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "queueSong"
 		});
 		queueSongModel.findOne({ _id: songId }, (err, song) => {
-			utils.runJob("EMIT_TO_ROOM", {
+			IOModule.runJob("EMIT_TO_ROOM", {
 				room: "admin.queue",
 				args: ["event:admin.queueSong.updated", song]
 			});
@@ -59,7 +60,7 @@ const lib = {
 	 * @param cb
 	 */
 	length: isAdminRequired(async (session, cb) => {
-		const queueSongModel = await db.runJob("GET_MODEL", {
+		const queueSongModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "queueSong"
 		});
 		async.waterfall(
@@ -70,7 +71,7 @@ const lib = {
 			],
 			async (err, count) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "QUEUE_SONGS_LENGTH", `Failed to get length from queue songs. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
@@ -88,7 +89,7 @@ const lib = {
 	 * @param cb
 	 */
 	getSet: isAdminRequired(async (session, set, cb) => {
-		const queueSongModel = await db.runJob("GET_MODEL", {
+		const queueSongModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "queueSong"
 		});
 		async.waterfall(
@@ -103,7 +104,7 @@ const lib = {
 			],
 			async (err, songs) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "QUEUE_SONGS_GET_SET", `Failed to get set from queue songs. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
@@ -122,7 +123,7 @@ const lib = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	update: isAdminRequired(async (session, songId, updatedSong, cb) => {
-		const queueSongModel = await db.runJob("GET_MODEL", {
+		const queueSongModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "queueSong"
 		});
 		async.waterfall(
@@ -149,7 +150,7 @@ const lib = {
 			],
 			async err => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"QUEUE_UPDATE",
@@ -157,7 +158,7 @@ const lib = {
 					);
 					return cb({ status: "failure", message: err });
 				}
-				cache.runJob("PUB", { channel: "queue.update", value: songId });
+				CacheModule.runJob("PUB", { channel: "queue.update", value: songId });
 				console.log(
 					"SUCCESS",
 					"QUEUE_UPDATE",
@@ -179,7 +180,7 @@ const lib = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	remove: isAdminRequired(async (session, songId, cb) => {
-		const queueSongModel = await db.runJob("GET_MODEL", {
+		const queueSongModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "queueSong"
 		});
 		async.waterfall(
@@ -190,7 +191,7 @@ const lib = {
 			],
 			async err => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"QUEUE_REMOVE",
@@ -198,7 +199,7 @@ const lib = {
 					);
 					return cb({ status: "failure", message: err });
 				}
-				cache.runJob("PUB", {
+				CacheModule.runJob("PUB", {
 					channel: "queue.removedSong",
 					value: songId
 				});
@@ -224,9 +225,9 @@ const lib = {
 	 */
 	add: isLoginRequired(async (session, songId, cb) => {
 		const requestedAt = Date.now();
-		const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
-		const QueueSongModel = await db.runJob("GET_MODEL", {
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
+		const QueueSongModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "queueSong"
 		});
 
@@ -245,8 +246,7 @@ const lib = {
 				(song, next) => {
 					if (song) return next("This song has already been added.");
 					// TODO Add err object as first param of callback
-					return utils
-						.runJob("GET_SONG_FROM_YOUTUBE", { songId })
+					return YouTubeModule.runJob("GET_SONG", { songId })
 						.then(response => {
 							const { song } = response;
 							song.duration = -1;
@@ -283,7 +283,7 @@ const lib = {
 			],
 			async (err, newSong) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"QUEUE_ADD",
@@ -291,7 +291,7 @@ const lib = {
 					);
 					return cb({ status: "failure", message: err });
 				}
-				cache.runJob("PUB", {
+				CacheModule.runJob("PUB", {
 					channel: "queue.newSong",
 					value: newSong._id
 				});
@@ -320,11 +320,10 @@ const lib = {
 		async.waterfall(
 			[
 				next => {
-					utils
-						.runJob("GET_PLAYLIST_FROM_YOUTUBE", {
-							url,
-							musicOnly
-						})
+					YouTubeModule.runJob("GET_PLAYLIST", {
+						url,
+						musicOnly
+					})
 						.then(res => {
 							next(null, res.songs);
 						})
@@ -348,7 +347,7 @@ const lib = {
 			],
 			async err => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"QUEUE_IMPORT",

+ 24 - 24
backend/logic/actions/reports.js

@@ -2,12 +2,13 @@ import async from "async";
 
 import { isAdminRequired, isLoginRequired } from "./hooks";
 
-import db from "../db";
-import utils from "../utils";
-// const logger = require("../logger");
-import songs from "../songs";
+import moduleManager from "../../index";
 
-import cache from "../cache";
+const DBModule = moduleManager.modules.db;
+const UtilsModule = moduleManager.modules.utils;
+const IOModule = moduleManager.modules.io;
+const SongsModule = moduleManager.modules.songs;
+const CacheModule = moduleManager.modules.cache;
 
 const reportableIssues = [
 	{
@@ -32,20 +33,20 @@ const reportableIssues = [
 	}
 ];
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "report.resolve",
 	cb: reportId => {
-		utils.runJob("EMIT_TO_ROOM", {
+		IOModule.runJob("EMIT_TO_ROOM", {
 			room: "admin.reports",
 			args: ["event:admin.report.resolved", reportId]
 		});
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "report.create",
 	cb: report => {
-		utils.runJob("EMIT_TO_ROOM", {
+		IOModule.runJob("EMIT_TO_ROOM", {
 			room: "admin.reports",
 			args: ["event:admin.report.created", report]
 		});
@@ -60,7 +61,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	index: isAdminRequired(async (session, cb) => {
-		const reportModel = await db.runJob("GET_MODEL", {
+		const reportModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "report"
 		});
 		async.waterfall(
@@ -71,7 +72,7 @@ export default {
 			],
 			async (err, reports) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "REPORTS_INDEX", `Indexing reports failed. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
@@ -89,7 +90,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	findOne: isAdminRequired(async (session, reportId, cb) => {
-		const reportModel = await db.runJob("GET_MODEL", {
+		const reportModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "report"
 		});
 		async.waterfall(
@@ -100,7 +101,7 @@ export default {
 			],
 			async (err, report) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "REPORTS_FIND_ONE", `Finding report "${reportId}" failed. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
@@ -118,7 +119,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	getReportsForSong: isAdminRequired(async (session, songId, cb) => {
-		const reportModel = await db.runJob("GET_MODEL", {
+		const reportModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "report"
 		});
 		async.waterfall(
@@ -140,7 +141,7 @@ export default {
 			],
 			async (err, data) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"GET_REPORTS_FOR_SONG",
@@ -162,7 +163,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	resolve: isAdminRequired(async (session, reportId, cb) => {
-		const reportModel = await db.runJob("GET_MODEL", {
+		const reportModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "report"
 		});
 		async.waterfall(
@@ -182,7 +183,7 @@ export default {
 			],
 			async err => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"REPORTS_RESOLVE",
@@ -190,7 +191,7 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
-				cache.runJob("PUB", {
+				CacheModule.runJob("PUB", {
 					channel: "report.resolve",
 					value: reportId
 				});
@@ -211,10 +212,10 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	create: isLoginRequired(async (session, data, cb) => {
-		const reportModel = await db.runJob("GET_MODEL", {
+		const reportModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "report"
 		});
-		const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
 		async.waterfall(
 			[
 				next => {
@@ -223,8 +224,7 @@ export default {
 
 				(song, next) => {
 					if (!song) return next("Song not found.");
-					return songs
-						.runJob("GET_SONG", { id: song._id })
+					return SongsModule.runJob("GET_SONG", { id: song._id })
 						.then(response => {
 							next(null, response.song);
 						})
@@ -284,7 +284,7 @@ export default {
 			],
 			async (err, report) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"REPORTS_CREATE",
@@ -292,7 +292,7 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
-				cache.runJob("PUB", {
+				CacheModule.runJob("PUB", {
 					channel: "report.create",
 					value: report
 				});

+ 72 - 76
backend/logic/actions/songs.js

@@ -2,33 +2,33 @@ import async from "async";
 
 import { isAdminRequired, isLoginRequired } from "./hooks";
 
-// const moduleManager = require("../../index");
+import moduleManager from "../../index";
 
-import db from "../db";
-import utils from "../utils";
-import cache from "../cache";
-
-import songs from "../songs";
 import queueSongs from "./queueSongs";
-import activities from "../activities";
-// const logger = moduleManager.modules["logger"];
 
-cache.runJob("SUB", {
+const DBModule = moduleManager.modules.db;
+const UtilsModule = moduleManager.modules.utils;
+const IOModule = moduleManager.modules.io;
+const CacheModule = moduleManager.modules.cache;
+const SongsModule = moduleManager.modules.songs;
+const ActivitiesModule = moduleManager.modules.activities;
+
+CacheModule.runJob("SUB", {
 	channel: "song.removed",
 	cb: songId => {
-		utils.runJob("EMIT_TO_ROOM", {
+		IOModule.runJob("EMIT_TO_ROOM", {
 			room: "admin.songs",
 			args: ["event:admin.song.removed", songId]
 		});
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "song.added",
 	cb: async songId => {
-		const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
 		songModel.findOne({ _id: songId }, (err, song) => {
-			utils.runJob("EMIT_TO_ROOM", {
+			IOModule.runJob("EMIT_TO_ROOM", {
 				room: "admin.songs",
 				args: ["event:admin.song.added", song]
 			});
@@ -36,12 +36,12 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "song.updated",
 	cb: async songId => {
-		const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
 		songModel.findOne({ _id: songId }, (err, song) => {
-			utils.runJob("EMIT_TO_ROOM", {
+			IOModule.runJob("EMIT_TO_ROOM", {
 				room: "admin.songs",
 				args: ["event:admin.song.updated", song]
 			});
@@ -49,10 +49,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "song.like",
 	cb: data => {
-		utils.runJob("EMIT_TO_ROOM", {
+		IOModule.runJob("EMIT_TO_ROOM", {
 			room: `song.${data.songId}`,
 			args: [
 				"event:song.like",
@@ -63,7 +63,7 @@ cache.runJob("SUB", {
 				}
 			]
 		});
-		utils.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:song.newRatings", {
 					songId: data.songId,
@@ -75,10 +75,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "song.dislike",
 	cb: data => {
-		utils.runJob("EMIT_TO_ROOM", {
+		IOModule.runJob("EMIT_TO_ROOM", {
 			room: `song.${data.songId}`,
 			args: [
 				"event:song.dislike",
@@ -89,7 +89,7 @@ cache.runJob("SUB", {
 				}
 			]
 		});
-		utils.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:song.newRatings", {
 					songId: data.songId,
@@ -101,10 +101,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "song.unlike",
 	cb: data => {
-		utils.runJob("EMIT_TO_ROOM", {
+		IOModule.runJob("EMIT_TO_ROOM", {
 			room: `song.${data.songId}`,
 			args: [
 				"event:song.unlike",
@@ -115,7 +115,7 @@ cache.runJob("SUB", {
 				}
 			]
 		});
-		utils.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:song.newRatings", {
 					songId: data.songId,
@@ -127,10 +127,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "song.undislike",
 	cb: data => {
-		utils.runJob("EMIT_TO_ROOM", {
+		IOModule.runJob("EMIT_TO_ROOM", {
 			room: `song.${data.songId}`,
 			args: [
 				"event:song.undislike",
@@ -141,7 +141,7 @@ cache.runJob("SUB", {
 				}
 			]
 		});
-		utils.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:song.newRatings", {
 					songId: data.songId,
@@ -161,7 +161,7 @@ export default {
 	 * @param cb
 	 */
 	length: isAdminRequired(async (session, cb) => {
-		const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
 		async.waterfall(
 			[
 				next => {
@@ -170,7 +170,7 @@ export default {
 			],
 			async (err, count) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "SONGS_LENGTH", `Failed to get length from songs. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
@@ -188,7 +188,7 @@ export default {
 	 * @param cb
 	 */
 	getSet: isAdminRequired(async (session, set, cb) => {
-		const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
 		async.waterfall(
 			[
 				next => {
@@ -201,7 +201,7 @@ export default {
 			],
 			async (err, songs) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "SONGS_GET_SET", `Failed to get set from songs. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
@@ -222,8 +222,7 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					songs
-						.runJob("GET_SONG_FROM_ID", { songId })
+					SongsModule.runJob("GET_SONG_FROM_ID", { songId })
 						.then(song => {
 							next(null, song);
 						})
@@ -234,7 +233,7 @@ export default {
 			],
 			async (err, song) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "SONGS_GET_SONG", `Failed to get song ${songId}. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
@@ -255,15 +254,14 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					songs
-						.runJob("GET_SONG_FROM_ID", { songId })
+					SongsModule.runJob("GET_SONG_FROM_ID", { songId })
 						.then(response => next(null, response.song))
 						.catch(next);
 				}
 			],
 			async (err, song) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 
 					console.log(
 						"ERROR",
@@ -310,7 +308,7 @@ export default {
 	 * @param {Function} cb
 	 */
 	update: isAdminRequired(async (session, songId, song, cb) => {
-		const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
 		async.waterfall(
 			[
 				next => {
@@ -318,8 +316,7 @@ export default {
 				},
 
 				(res, next) => {
-					songs
-						.runJob("UPDATE_SONG", { songId })
+					SongsModule.runJob("UPDATE_SONG", { songId })
 						.then(song => {
 							next(null, song);
 						})
@@ -328,7 +325,7 @@ export default {
 			],
 			async (err, song) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 
 					console.log("ERROR", "SONGS_UPDATE", `Failed to update song "${songId}". "${err}"`);
 
@@ -337,7 +334,7 @@ export default {
 
 				console.log("SUCCESS", "SONGS_UPDATE", `Successfully updated song "${songId}".`);
 
-				cache.runJob("PUB", {
+				CacheModule.runJob("PUB", {
 					channel: "song.updated",
 					value: song.songId
 				});
@@ -359,7 +356,7 @@ export default {
 	 * @param cb
 	 */
 	remove: isAdminRequired(async (session, songId, cb) => {
-		const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
 		async.waterfall(
 			[
 				next => {
@@ -368,8 +365,7 @@ export default {
 
 				(res, next) => {
 					// TODO Check if res gets returned from above
-					cache
-						.runJob("HDEL", { table: "songs", key: songId })
+					CacheModule.runJob("HDEL", { table: "songs", key: songId })
 						.then(() => {
 							next();
 						})
@@ -378,7 +374,7 @@ export default {
 			],
 			async err => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 
 					console.log("ERROR", "SONGS_UPDATE", `Failed to remove song "${songId}". "${err}"`);
 
@@ -387,7 +383,7 @@ export default {
 
 				console.log("SUCCESS", "SONGS_UPDATE", `Successfully remove song "${songId}".`);
 
-				cache.runJob("PUB", { channel: "song.removed", value: songId });
+				CacheModule.runJob("PUB", { channel: "song.removed", value: songId });
 
 				return cb({
 					status: "success",
@@ -405,7 +401,7 @@ export default {
 	 * @param cb
 	 */
 	add: isAdminRequired(async (session, song, cb) => {
-		const SongModel = await db.runJob("GET_MODEL", { modelName: "song" });
+		const SongModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
 		async.waterfall(
 			[
 				next => {
@@ -432,7 +428,7 @@ export default {
 			],
 			async err => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 
 					console.log("ERROR", "SONGS_ADD", `User "${session.userId}" failed to add song. "${err}"`);
 
@@ -445,7 +441,7 @@ export default {
 					`User "${session.userId}" successfully added song "${song.songId}".`
 				);
 
-				cache.runJob("PUB", {
+				CacheModule.runJob("PUB", {
 					channel: "song.added",
 					value: song.songId
 				});
@@ -467,8 +463,8 @@ export default {
 	 * @param cb
 	 */
 	like: isLoginRequired(async (session, songId, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
-		const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
 		async.waterfall(
 			[
 				next => {
@@ -482,7 +478,7 @@ export default {
 			],
 			async (err, song) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"SONGS_LIKE",
@@ -538,9 +534,9 @@ export default {
 														message: "Something went wrong while liking this song."
 													});
 
-												songs.runJob("UPDATE_SONG", { songId });
+												SongsModule.runJob("UPDATE_SONG", { songId });
 
-												cache.runJob("PUB", {
+												CacheModule.runJob("PUB", {
 													channel: "song.like",
 													value: JSON.stringify({
 														songId: oldSongId,
@@ -550,7 +546,7 @@ export default {
 													})
 												});
 
-												activities.runJob("ADD_ACTIVITY", {
+												ActivitiesModule.runJob("ADD_ACTIVITY", {
 													userId: session.userId,
 													activityType: "liked_song",
 													payload: [songId]
@@ -584,8 +580,8 @@ export default {
 	 * @param cb
 	 */
 	dislike: isLoginRequired(async (session, songId, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
-		const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
 		async.waterfall(
 			[
 				next => {
@@ -599,7 +595,7 @@ export default {
 			],
 			async (err, song) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"SONGS_DISLIKE",
@@ -653,8 +649,8 @@ export default {
 														message: "Something went wrong while disliking this song."
 													});
 
-												songs.runJob("UPDATE_SONG", { songId });
-												cache.runJob("PUB", {
+												SongsModule.runJob("UPDATE_SONG", { songId });
+												CacheModule.runJob("PUB", {
 													channel: "song.dislike",
 													value: JSON.stringify({
 														songId: oldSongId,
@@ -692,8 +688,8 @@ export default {
 	 * @param cb
 	 */
 	undislike: isLoginRequired(async (session, songId, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
-		const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
 		async.waterfall(
 			[
 				next => {
@@ -707,7 +703,7 @@ export default {
 			],
 			async (err, song) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"SONGS_UNDISLIKE",
@@ -757,8 +753,8 @@ export default {
 														status: "failure",
 														message: "Something went wrong while undisliking this song."
 													});
-												songs.runJob("UPDATE_SONG", { songId });
-												cache.runJob("PUB", {
+												SongsModule.runJob("UPDATE_SONG", { songId });
+												CacheModule.runJob("PUB", {
 													channel: "song.undislike",
 													value: JSON.stringify({
 														songId: oldSongId,
@@ -795,8 +791,8 @@ export default {
 	 * @param cb
 	 */
 	unlike: isLoginRequired(async (session, songId, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
-		const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
 		async.waterfall(
 			[
 				next => {
@@ -810,7 +806,7 @@ export default {
 			],
 			async (err, song) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"SONGS_UNLIKE",
@@ -859,8 +855,8 @@ export default {
 														status: "failure",
 														message: "Something went wrong while unliking this song."
 													});
-												songs.runJob("UPDATE_SONG", { songId });
-												cache.runJob("PUB", {
+												SongsModule.runJob("UPDATE_SONG", { songId });
+												CacheModule.runJob("PUB", {
 													channel: "song.unlike",
 													value: JSON.stringify({
 														songId: oldSongId,
@@ -897,8 +893,8 @@ export default {
 	 * @param cb
 	 */
 	getOwnSongRatings: isLoginRequired(async (session, songId, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
-		const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
 		async.waterfall(
 			[
 				next => {
@@ -912,7 +908,7 @@ export default {
 			],
 			async (err, song) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"SONGS_GET_OWN_RATINGS",
@@ -932,7 +928,7 @@ export default {
 					}
 					return cb({
 						status: "failure",
-						message: await utils.runJob("GET_ERROR", {
+						message: await UtilsModule.runJob("GET_ERROR", {
 							error: err
 						})
 					});

File diff suppressed because it is too large
+ 170 - 202
backend/logic/actions/stations.js


+ 137 - 147
backend/logic/actions/users.js

@@ -7,21 +7,20 @@ import bcrypt from "bcrypt";
 import sha256 from "sha256";
 import { isAdminRequired, isLoginRequired } from "./hooks";
 
-// const moduleManager = require("../../index");
+import moduleManager from "../../index";
 
-import db from "../db";
-import utils from "../utils";
-import cache from "../cache";
+const DBModule = moduleManager.modules.db;
+const UtilsModule = moduleManager.modules.utils;
+const IOModule = moduleManager.modules.io;
+const CacheModule = moduleManager.modules.cache;
+const MailModule = moduleManager.modules.mail;
+const PunishmentsModule = moduleManager.modules.punishments;
+const ActivitiesModule = moduleManager.modules.activities;
 
-import mail from "../mail";
-import punishments from "../punishments";
-// const logger = require("../logger");
-import activities from "../activities";
-
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "user.updateUsername",
 	cb: user => {
-		utils.runJob("SOCKETS_FROM_USER", { userId: user._id }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: user._id }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:user.username.changed", user.username);
 			});
@@ -29,10 +28,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "user.removeSessions",
 	cb: userId => {
-		utils.runJob("SOCKETS_FROM_USER_WITHOUT_CACHE", { userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER_WITHOUT_CACHE", { userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("keep.event:user.session.removed");
 			});
@@ -40,10 +39,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "user.linkPassword",
 	cb: userId => {
-		utils.runJob("SOCKETS_FROM_USER", { userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:user.linkPassword");
 			});
@@ -51,10 +50,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "user.unlinkPassword",
 	cb: userId => {
-		utils.runJob("SOCKETS_FROM_USER", { userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:user.unlinkPassword");
 			});
@@ -62,10 +61,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "user.linkGithub",
 	cb: userId => {
-		utils.runJob("SOCKETS_FROM_USER", { userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:user.linkGithub");
 			});
@@ -73,10 +72,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "user.unlinkGithub",
 	cb: userId => {
-		utils.runJob("SOCKETS_FROM_USER", { userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:user.unlinkGithub");
 			});
@@ -84,10 +83,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "user.ban",
 	cb: data => {
-		utils.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("keep.event:banned", data.punishment);
 				socket.disconnect(true);
@@ -96,10 +95,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "user.favoritedStation",
 	cb: data => {
-		utils.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:user.favoritedStation", data.stationId);
 			});
@@ -107,10 +106,10 @@ cache.runJob("SUB", {
 	}
 });
 
-cache.runJob("SUB", {
+CacheModule.runJob("SUB", {
 	channel: "user.unfavoritedStation",
 	cb: data => {
-		utils.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
 			response.sockets.forEach(socket => {
 				socket.emit("event:user.unfavoritedStation", data.stationId);
 			});
@@ -126,7 +125,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	index: isAdminRequired(async (session, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 
 		async.waterfall(
 			[
@@ -136,7 +135,7 @@ const thisExport = {
 			],
 			async (err, users) => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "USER_INDEX", `Indexing users failed. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
@@ -173,8 +172,8 @@ const thisExport = {
 	 */
 	login: async (session, identifier, password, cb) => {
 		identifier = identifier.toLowerCase();
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
-		const sessionSchema = await cache.runJob("GET_SCHEMA", {
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
+		const sessionSchema = await CacheModule.runJob("GET_SCHEMA", {
 			schemaName: "session"
 		});
 
@@ -205,18 +204,17 @@ const thisExport = {
 				},
 
 				(user, next) => {
-					utils.runJob("GUID", {}).then(sessionId => {
+					UtilsModule.runJob("GUID", {}).then(sessionId => {
 						next(null, user, sessionId);
 					});
 				},
 
 				(user, sessionId, next) => {
-					cache
-						.runJob("HSET", {
-							table: "sessions",
-							key: sessionId,
-							value: sessionSchema(sessionId, user._id)
-						})
+					CacheModule.runJob("HSET", {
+						table: "sessions",
+						key: sessionId,
+						value: sessionSchema(sessionId, user._id)
+					})
 						.then(() => {
 							next(null, sessionId);
 						})
@@ -225,7 +223,7 @@ const thisExport = {
 			],
 			async (err, sessionId) => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"USER_PASSWORD_LOGIN",
@@ -262,11 +260,11 @@ const thisExport = {
 	 */
 	async register(session, username, email, password, recaptcha, cb) {
 		email = email.toLowerCase();
-		const verificationToken = await utils.runJob("GENERATE_RANDOM_STRING", {
+		const verificationToken = await UtilsModule.runJob("GENERATE_RANDOM_STRING", {
 			length: 64
 		});
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
-		const verifyEmailSchema = await mail.runJob("GET_SCHEMA", {
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
+		const verifyEmailSchema = await MailModule.runJob("GET_SCHEMA", {
 			schemaName: "verifyEmail"
 		});
 
@@ -279,7 +277,7 @@ const thisExport = {
 				},
 
 				next => {
-					if (!db.passwordValid(password))
+					if (!DBModule.passwordValid(password))
 						return next("Invalid password. Check if it meets all the requirements.");
 					return next();
 				},
@@ -332,7 +330,7 @@ const thisExport = {
 				},
 
 				(hash, next) => {
-					utils.runJob("GENERATE_RANDOM_STRING", { length: 12 }).then(_id => {
+					UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 12 }).then(_id => {
 						next(null, hash, _id);
 					});
 				},
@@ -356,17 +354,15 @@ const thisExport = {
 
 				// generate the url for gravatar avatar
 				(user, next) => {
-					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
@@ -383,7 +379,7 @@ const thisExport = {
 			],
 			async (err, user) => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"USER_PASSWORD_REGISTER",
@@ -400,7 +396,7 @@ const thisExport = {
 					if (result.status === "success") {
 						obj.SID = result.SID;
 					}
-					activities.runJob("ADD_ACTIVITY", {
+					ActivitiesModule.runJob("ADD_ACTIVITY", {
 						userId: user._id,
 						activityType: "created_account"
 					});
@@ -425,11 +421,10 @@ const thisExport = {
 		async.waterfall(
 			[
 				next => {
-					cache
-						.runJob("HGET", {
-							table: "sessions",
-							key: session.sessionId
-						})
+					CacheModule.runJob("HGET", {
+						table: "sessions",
+						key: session.sessionId
+					})
 						.then(session => {
 							next(null, session);
 						})
@@ -442,11 +437,10 @@ const thisExport = {
 				},
 
 				(session, next) => {
-					cache
-						.runJob("HDEL", {
-							table: "sessions",
-							key: session.sessionId
-						})
+					CacheModule.runJob("HDEL", {
+						table: "sessions",
+						key: session.sessionId
+					})
 						.then(() => {
 							next();
 						})
@@ -455,7 +449,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "USER_LOGOUT", `Logout failed. "${err}" `);
 					cb({ status: "failure", message: err });
 				} else {
@@ -477,7 +471,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	removeSessions: isLoginRequired(async (session, userId, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 		async.waterfall(
 			[
 				next => {
@@ -490,8 +484,7 @@ const thisExport = {
 				},
 
 				next => {
-					cache
-						.runJob("HGETALL", { table: "sessions" })
+					CacheModule.runJob("HGETALL", { table: "sessions" })
 						.then(sessions => {
 							next(null, sessions);
 						})
@@ -507,7 +500,7 @@ const thisExport = {
 				},
 
 				(keys, sessions, next) => {
-					cache.runJob("PUB", {
+					CacheModule.runJob("PUB", {
 						channel: "user.removeSessions",
 						value: userId
 					});
@@ -516,11 +509,10 @@ const thisExport = {
 						(sessionId, callback) => {
 							const session = sessions[sessionId];
 							if (session.userId === userId) {
-								cache
-									.runJob("HDEL", {
-										channel: "sessions",
-										key: sessionId
-									})
+								CacheModule.runJob("HDEL", {
+									channel: "sessions",
+									key: sessionId
+								})
 									.then(() => {
 										callback(null);
 									})
@@ -535,7 +527,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"REMOVE_SESSIONS_FOR_USER",
@@ -560,7 +552,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	findByUsername: async (session, username, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 
 		async.waterfall(
 			[
@@ -575,7 +567,7 @@ const thisExport = {
 			],
 			async (err, account) => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 
 					console.log("ERROR", "FIND_BY_USERNAME", `User not found for username "${username}". "${err}"`);
 
@@ -609,7 +601,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	getUsernameFromId: async (session, userId, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 		userModel
 			.findById(userId)
 			.then(user => {
@@ -635,7 +627,7 @@ const thisExport = {
 			})
 			.catch(async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"GET_USERNAME_FROM_ID",
@@ -654,16 +646,15 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	findBySession: async (session, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 
 		async.waterfall(
 			[
 				next => {
-					cache
-						.runJob("HGET", {
-							table: "sessions",
-							key: session.sessionId
-						})
+					CacheModule.runJob("HGET", {
+						table: "sessions",
+						key: session.sessionId
+					})
 						.then(session => {
 							next(null, session);
 						})
@@ -686,7 +677,7 @@ const thisExport = {
 			],
 			async (err, user) => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "FIND_BY_SESSION", `User not found. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
@@ -723,7 +714,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	updateUsername: isLoginRequired(async (session, updatingUserId, newUsername, cb) => {
-		const userModel = await db.runJob("GET_MODEL", {
+		const userModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "user"
 		});
 		async.waterfall(
@@ -766,7 +757,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 
 					console.log(
 						"ERROR",
@@ -777,7 +768,7 @@ const thisExport = {
 					return cb({ status: "failure", message: err });
 				}
 
-				cache.runJob("PUB", {
+				CacheModule.runJob("PUB", {
 					channel: "user.updateUsername",
 					value: {
 						username: newUsername,
@@ -809,12 +800,12 @@ const thisExport = {
 	 */
 	updateEmail: isLoginRequired(async (session, updatingUserId, newEmail, cb) => {
 		newEmail = newEmail.toLowerCase();
-		const verificationToken = await utils.runJob("GENERATE_RANDOM_STRING", { length: 64 });
+		const verificationToken = await UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 64 });
 
-		const userModel = await db.runJob("GET_MODEL", {
+		const userModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "user"
 		});
-		const verifyEmailSchema = await mail.runJob("GET_SCHEMA", {
+		const verifyEmailSchema = await MailModule.runJob("GET_SCHEMA", {
 			schemaName: "verifyEmail"
 		});
 
@@ -849,7 +840,7 @@ const thisExport = {
 
 				// regenerate the url for gravatar avatar
 				next => {
-					utils.runJob("CREATE_GRAVATAR", { email: newEmail }).then(url => {
+					UtilsModule.runJob("CREATE_GRAVATAR", { email: newEmail }).then(url => {
 						next(null, url);
 					});
 				},
@@ -882,7 +873,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 
 					console.log(
 						"ERROR",
@@ -916,7 +907,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	updateName: isLoginRequired(async (session, updatingUserId, newName, cb) => {
-		const userModel = await db.runJob("GET_MODEL", {
+		const userModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "user"
 		});
 
@@ -944,7 +935,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"UPDATE_NAME",
@@ -975,7 +966,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	updateLocation: isLoginRequired(async (session, updatingUserId, newLocation, cb) => {
-		const userModel = await db.runJob("GET_MODEL", {
+		const userModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "user"
 		});
 
@@ -1003,7 +994,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 
 					console.log(
 						"ERROR",
@@ -1037,7 +1028,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	updateBio: isLoginRequired(async (session, updatingUserId, newBio, cb) => {
-		const userModel = await db.runJob("GET_MODEL", {
+		const userModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "user"
 		});
 
@@ -1065,7 +1056,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"UPDATE_BIO",
@@ -1096,7 +1087,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	updateAvatarType: isLoginRequired(async (session, updatingUserId, newType, cb) => {
-		const userModel = await db.runJob("GET_MODEL", {
+		const userModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "user"
 		});
 
@@ -1124,7 +1115,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"UPDATE_AVATAR_TYPE",
@@ -1157,7 +1148,7 @@ const thisExport = {
 	 */
 	updateRole: isAdminRequired(async (session, updatingUserId, newRole, cb) => {
 		newRole = newRole.toLowerCase();
-		const userModel = await db.runJob("GET_MODEL", {
+		const userModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "user"
 		});
 		async.waterfall(
@@ -1182,7 +1173,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 
 					console.log(
 						"ERROR",
@@ -1216,7 +1207,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	updatePassword: isLoginRequired(async (session, previousPassword, newPassword, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 
 		async.waterfall(
 			[
@@ -1237,7 +1228,7 @@ const thisExport = {
 				},
 
 				next => {
-					if (!db.passwordValid(newPassword))
+					if (!DBModule.passwordValid(newPassword))
 						return next("Invalid new password. Check if it meets all the requirements.");
 					return next();
 				},
@@ -1265,7 +1256,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"UPDATE_PASSWORD",
@@ -1291,11 +1282,11 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	requestPassword: isLoginRequired(async (session, cb) => {
-		const code = await utils.runJob("GENERATE_RANDOM_STRING", { length: 8 });
-		const passwordRequestSchema = await mail.runJob("GET_SCHEMA", {
+		const code = await UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 8 });
+		const passwordRequestSchema = await MailModule.runJob("GET_SCHEMA", {
 			schemaName: "passwordRequest"
 		});
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 		async.waterfall(
 			[
 				next => {
@@ -1332,7 +1323,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 
 					console.log(
 						"ERROR",
@@ -1365,7 +1356,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	verifyPasswordCode: isLoginRequired(async (session, code, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 		async.waterfall(
 			[
 				next => {
@@ -1387,7 +1378,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "VERIFY_PASSWORD_CODE", `Code '${code}' failed to verify. '${err}'`);
 					cb({ status: "failure", message: err });
 				} else {
@@ -1410,7 +1401,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	changePasswordWithCode: isLoginRequired(async (session, code, newPassword, cb) => {
-		const userModel = await db.runJob("GET_MODEL", {
+		const userModel = await DBModule.runJob("GET_MODEL", {
 			modelName: "user"
 		});
 		async.waterfall(
@@ -1427,7 +1418,7 @@ const thisExport = {
 				},
 
 				next => {
-					if (!db.passwordValid(newPassword))
+					if (!DBModule.passwordValid(newPassword))
 						return next("Invalid password. Check if it meets all the requirements.");
 					return next();
 				},
@@ -1457,12 +1448,12 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "ADD_PASSWORD_WITH_CODE", `Code '${code}' failed to add password. '${err}'`);
 					cb({ status: "failure", message: err });
 				} else {
 					console.log("SUCCESS", "ADD_PASSWORD_WITH_CODE", `Code '${code}' successfully added password.`);
-					cache.runJob("PUB", {
+					CacheModule.runJob("PUB", {
 						channel: "user.linkPassword",
 						value: session.userId
 					});
@@ -1482,7 +1473,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	unlinkPassword: isLoginRequired(async (session, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 		async.waterfall(
 			[
 				next => {
@@ -1498,7 +1489,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"UNLINK_PASSWORD",
@@ -1511,7 +1502,7 @@ const thisExport = {
 						"UNLINK_PASSWORD",
 						`Unlinking password successful for userId '${session.userId}'.`
 					);
-					cache.runJob("PUB", {
+					CacheModule.runJob("PUB", {
 						channel: "user.unlinkPassword",
 						value: session.userId
 					});
@@ -1531,7 +1522,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	unlinkGitHub: isLoginRequired(async (session, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 		async.waterfall(
 			[
 				next => {
@@ -1547,7 +1538,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"UNLINK_GITHUB",
@@ -1560,7 +1551,7 @@ const thisExport = {
 						"UNLINK_GITHUB",
 						`Unlinking GitHub successful for userId '${session.userId}'.`
 					);
-					cache.runJob("PUB", {
+					CacheModule.runJob("PUB", {
 						channel: "user.unlinkGithub",
 						value: session.userId
 					});
@@ -1581,10 +1572,10 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	requestPasswordReset: async (session, email, cb) => {
-		const code = await utils.runJob("GENERATE_RANDOM_STRING", { length: 8 });
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+		const code = await UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 8 });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 
-		const resetPasswordRequestSchema = await mail.runJob("GET_SCHEMA", {
+		const resetPasswordRequestSchema = await MailModule.runJob("GET_SCHEMA", {
 			schemaName: "resetPasswordRequest"
 		});
 
@@ -1627,7 +1618,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"REQUEST_PASSWORD_RESET",
@@ -1657,7 +1648,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	verifyPasswordResetCode: async (session, code, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 		async.waterfall(
 			[
 				next => {
@@ -1673,7 +1664,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "VERIFY_PASSWORD_RESET_CODE", `Code '${code}' failed to verify. '${err}'`);
 					cb({ status: "failure", message: err });
 				} else {
@@ -1696,7 +1687,7 @@ const thisExport = {
 	 * @param {Function} cb - gets called with the result
 	 */
 	changePasswordWithResetCode: async (session, code, newPassword, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 		async.waterfall(
 			[
 				next => {
@@ -1711,7 +1702,7 @@ const thisExport = {
 				},
 
 				next => {
-					if (!db.passwordValid(newPassword))
+					if (!DBModule.passwordValid(newPassword))
 						return next("Invalid password. Check if it meets all the requirements.");
 					return next();
 				},
@@ -1741,7 +1732,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"CHANGE_PASSWORD_WITH_RESET_CODE",
@@ -1820,20 +1811,19 @@ const thisExport = {
 				},
 
 				next => {
-					punishments
-						.runJob("ADD_PUNISHMENT", {
-							type: "banUserId",
-							value: userId,
-							reason,
-							expiresAt,
-							punishedBy: "" // needs changed
-						})
+					PunishmentsModule.runJob("ADD_PUNISHMENT", {
+						type: "banUserId",
+						value: userId,
+						reason,
+						expiresAt,
+						punishedBy: "" // needs changed
+					})
 						.then(punishment => next(null, punishment))
 						.catch(next);
 				},
 
 				(punishment, next) => {
-					cache.runJob("PUB", {
+					CacheModule.runJob("PUB", {
 						channel: "user.ban",
 						value: { userId, punishment }
 					});
@@ -1842,7 +1832,7 @@ const thisExport = {
 			],
 			async err => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"BAN_USER_BY_ID",
@@ -1865,7 +1855,7 @@ const thisExport = {
 	}),
 
 	getFavoriteStations: isLoginRequired(async (session, cb) => {
-		const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 		async.waterfall(
 			[
 				next => {
@@ -1879,7 +1869,7 @@ const thisExport = {
 			],
 			async (err, user) => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log(
 						"ERROR",
 						"GET_FAVORITE_STATIONS",

+ 7 - 5
backend/logic/actions/utils.js

@@ -2,14 +2,16 @@ import async from "async";
 
 import { isAdminRequired } from "./hooks";
 
-import utils from "../utils";
+import moduleManager from "../../index";
+
+const UtilsModule = moduleManager.modules.utils;
 
 export default {
 	getModules: isAdminRequired((session, cb) => {
 		async.waterfall(
 			[
 				next => {
-					next(null, utils.moduleManager.modules);
+					next(null, UtilsModule.moduleManager.modules);
 				},
 
 				(modules, next) => {
@@ -33,7 +35,7 @@ export default {
 			],
 			async (err, modules) => {
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "GET_MODULES", `User ${session.userId} failed to get modules. '${err}'`);
 					cb({ status: "failure", message: err });
 				} else {
@@ -56,13 +58,13 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					next(null, utils.moduleManager.modules[moduleName]);
+					next(null, UtilsModule.moduleManager.modules[moduleName]);
 				}
 			],
 			async (err, module) => {
 				// console.log(module.runningJobs);
 				if (err && err !== true) {
-					err = await utils.runJob("GET_ERROR", { error: err });
+					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					console.log("ERROR", "GET_MODULE", `User ${session.userId} failed to get module. '${err}'`);
 					cb({ status: "failure", message: err });
 				} else {

+ 3 - 1
backend/logic/activities.js

@@ -5,6 +5,7 @@ import CoreClass from "../core";
 // let ActivitiesModule;
 let DBModule;
 let UtilsModule;
+let IOModule;
 
 class _ActivitiesModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
@@ -23,6 +24,7 @@ class _ActivitiesModule extends CoreClass {
 		return new Promise(resolve => {
 			DBModule = this.moduleManager.modules.db;
 			UtilsModule = this.moduleManager.modules.utils;
+			IOModule = this.moduleManager.modules.io;
 
 			resolve();
 		});
@@ -61,7 +63,7 @@ class _ActivitiesModule extends CoreClass {
 					},
 
 					(activity, next) => {
-						UtilsModule.runJob(
+						IOModule.runJob(
 							"SOCKETS_FROM_USER",
 							{
 								userId: activity.userId

+ 323 - 1
backend/logic/io.js

@@ -6,7 +6,6 @@ import config from "config";
 import async from "async";
 import socketio from "socket.io";
 
-import actions from "./actions";
 import CoreClass from "../core";
 
 let IOModule;
@@ -38,6 +37,8 @@ class _IOModule extends CoreClass {
 		DBModule = this.moduleManager.modules.db;
 		PunishmentsModule = this.moduleManager.modules.punishments;
 
+		const actions = (await import("./actions")).default;
+
 		this.userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 
 		this.setStage(2);
@@ -334,6 +335,327 @@ class _IOModule extends CoreClass {
 			resolve(IOModule._io);
 		});
 	}
+
+	// UNKNOWN
+	// eslint-disable-next-line require-jsdoc
+	async SOCKET_FROM_SESSION(payload) {
+		// socketId
+		return new Promise((resolve, reject) => {
+			const ns = IOModule._io.of("/");
+			if (ns) {
+				return resolve(ns.connected[payload.socketId]);
+			}
+
+			return reject();
+		});
+	}
+
+	/**
+	 * Gets all sockets for a specified session id
+	 *
+	 * @param {object} payload - object containing the payload
+	 * @param {string} payload.sessionId - user session id
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	async SOCKETS_FROM_SESSION_ID(payload) {
+		return new Promise(resolve => {
+			const ns = IOModule._io.of("/");
+			const sockets = [];
+
+			if (ns) {
+				return async.each(
+					Object.keys(ns.connected),
+					(id, next) => {
+						const { session } = ns.connected[id];
+						if (session.sessionId === payload.sessionId) sockets.push(session.sessionId);
+						next();
+					},
+					() => {
+						resolve({ sockets });
+					}
+				);
+			}
+
+			return resolve();
+		});
+	}
+
+	/**
+	 * Returns any sockets for a specific user
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {string} payload.userId - the user id
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	async SOCKETS_FROM_USER(payload) {
+		return new Promise((resolve, reject) => {
+			const ns = IOModule._io.of("/");
+			const sockets = [];
+
+			if (ns) {
+				return async.each(
+					Object.keys(ns.connected),
+					(id, next) => {
+						const { session } = ns.connected[id];
+						CacheModule.runJob(
+							"HGET",
+							{
+								table: "sessions",
+								key: session.sessionId
+							},
+							this
+						)
+							.then(session => {
+								if (session && session.userId === payload.userId) sockets.push(ns.connected[id]);
+								next();
+							})
+							.catch(err => {
+								next(err);
+							});
+					},
+					err => {
+						if (err) return reject(err);
+						return resolve({ sockets });
+					}
+				);
+			}
+
+			return resolve();
+		});
+	}
+
+	/**
+	 * Returns any sockets from a specific ip address
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {string} payload.ip - the ip address in question
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	async SOCKETS_FROM_IP(payload) {
+		return new Promise(resolve => {
+			const ns = IOModule._io.of("/");
+			const sockets = [];
+			if (ns) {
+				return async.each(
+					Object.keys(ns.connected),
+					(id, next) => {
+						const { session } = ns.connected[id];
+						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();
+							})
+							.catch(() => next());
+					},
+					() => {
+						resolve({ sockets });
+					}
+				);
+			}
+
+			return resolve();
+		});
+	}
+
+	/**
+	 * Returns any sockets from a specific user without using redis/cache
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {string} payload.userId - the id of the user in question
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	async SOCKETS_FROM_USER_WITHOUT_CACHE(payload) {
+		return new Promise(resolve => {
+			const ns = IOModule._io.of("/");
+			const sockets = [];
+
+			if (ns) {
+				return async.each(
+					Object.keys(ns.connected),
+					(id, next) => {
+						const { session } = ns.connected[id];
+						if (session.userId === payload.userId) sockets.push(ns.connected[id]);
+						next();
+					},
+					() => {
+						resolve({ sockets });
+					}
+				);
+			}
+
+			return resolve();
+		});
+	}
+
+	/**
+	 * Allows a socket to leave any rooms they are connected to
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {string} payload.socketId - the id of the socket which should leave all their rooms
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	async SOCKET_LEAVE_ROOMS(payload) {
+		const socket = await IOModule.runJob(
+			"SOCKET_FROM_SESSION",
+			{
+				socketId: payload.socketId
+			},
+			this
+		);
+
+		return new Promise(resolve => {
+			const { rooms } = socket;
+
+			Object.keys(rooms).forEach(roomKey => {
+				const room = rooms[roomKey];
+				socket.leave(room);
+			});
+
+			return resolve();
+		});
+	}
+
+	/**
+	 * Allows a socket to join a specified room
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {string} payload.socketId - the id of the socket which should join the room
+	 * @param {object} payload.room - the object representing the room the socket should join
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	async SOCKET_JOIN_ROOM(payload) {
+		const socket = await IOModule.runJob(
+			"SOCKET_FROM_SESSION",
+			{
+				socketId: payload.socketId
+			},
+			this
+		);
+
+		return new Promise(resolve => {
+			const { rooms } = socket;
+			Object.keys(rooms).forEach(roomKey => {
+				const room = rooms[roomKey];
+				socket.leave(room);
+			});
+
+			socket.join(payload.room);
+
+			return resolve();
+		});
+	}
+
+	// UNKNOWN
+	// eslint-disable-next-line require-jsdoc
+	async SOCKET_JOIN_SONG_ROOM(payload) {
+		// socketId, room
+		const socket = await IOModule.runJob(
+			"SOCKET_FROM_SESSION",
+			{
+				socketId: payload.socketId
+			},
+			this
+		);
+
+		return new Promise(resolve => {
+			const { rooms } = socket;
+			Object.keys(rooms).forEach(roomKey => {
+				const room = rooms[roomKey];
+				if (room.indexOf("song.") !== -1) socket.leave(room);
+			});
+
+			socket.join(payload.room);
+
+			return resolve();
+		});
+	}
+
+	// UNKNOWN
+	// eslint-disable-next-line require-jsdoc
+	SOCKETS_JOIN_SONG_ROOM(payload) {
+		// sockets, room
+		return new Promise(resolve => {
+			Object.keys(payload.sockets).forEach(socketKey => {
+				const socket = payload.sockets[socketKey];
+
+				const { rooms } = socket;
+				Object.keys(rooms).forEach(roomKey => {
+					const room = rooms[roomKey];
+					if (room.indexOf("song.") !== -1) socket.leave(room);
+				});
+
+				socket.join(payload.room);
+			});
+
+			return resolve();
+		});
+	}
+
+	// UNKNOWN
+	// eslint-disable-next-line require-jsdoc
+	SOCKETS_LEAVE_SONG_ROOMS(payload) {
+		// sockets
+		return new Promise(resolve => {
+			Object.keys(payload.sockets).forEach(socketKey => {
+				const socket = payload.sockets[socketKey];
+				const { rooms } = socket;
+				Object.keys(rooms).forEach(roomKey => {
+					const room = rooms[roomKey];
+					if (room.indexOf("song.") !== -1) socket.leave(room);
+				});
+			});
+			resolve();
+		});
+	}
+
+	/**
+	 * Emits arguments to any sockets that are in a specified a room
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {string} payload.room - the name of the room to emit arguments
+	 * @param {object} payload.args - any arguments to be emitted to the sockets in the specific room
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	async EMIT_TO_ROOM(payload) {
+		return new Promise(resolve => {
+			const { sockets } = IOModule._io.sockets;
+			Object.keys(sockets).forEach(socketKey => {
+				const socket = sockets[socketKey];
+				if (socket.rooms[payload.room]) {
+					socket.emit(...payload.args);
+				}
+			});
+
+			return resolve();
+		});
+	}
+
+	/**
+	 * Gets any sockets connected to a room
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {string} payload.room - the name of the room
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	async GET_ROOM_SOCKETS(payload) {
+		return new Promise(resolve => {
+			const { sockets } = IOModule._io.sockets;
+			const roomSockets = [];
+			Object.keys(sockets).forEach(socketKey => {
+				const socket = sockets[socketKey];
+				if (socket.rooms[payload.room]) roomSockets.push(socket);
+			});
+
+			return resolve(roomSockets);
+		});
+	}
 }
 
 export default new _IOModule();

+ 1 - 1
backend/logic/punishments.js

@@ -125,7 +125,7 @@ class _PunishmentsModule extends CoreClass {
 
 						Object.keys(punishments).forEach(punishmentKey => {
 							const punishment = punishments[punishmentKey];
-							punishment.punishmentId = id;
+							punishment.punishmentId = punishmentKey;
 							punishments.push(punishment);
 						});
 

+ 11 - 9
backend/logic/stations.js

@@ -6,6 +6,7 @@ let StationsModule;
 let CacheModule;
 let DBModule;
 let UtilsModule;
+let IOModule;
 let SongsModule;
 let NotificationsModule;
 
@@ -26,6 +27,7 @@ class _StationsModule extends CoreClass {
 		CacheModule = this.moduleManager.modules.cache;
 		DBModule = this.moduleManager.modules.db;
 		UtilsModule = this.moduleManager.modules.utils;
+		IOModule = this.moduleManager.modules.io;
 		SongsModule = this.moduleManager.modules.songs;
 		NotificationsModule = this.moduleManager.modules.notifications;
 
@@ -76,7 +78,7 @@ class _StationsModule extends CoreClass {
 					key: stationId
 				}).then(playlistObj => {
 					if (playlistObj) {
-						UtilsModule.runJob("EMIT_TO_ROOM", {
+						IOModule.runJob("EMIT_TO_ROOM", {
 							room: `station.${stationId}`,
 							args: ["event:newOfficialPlaylist", playlistObj.songs]
 						});
@@ -217,7 +219,7 @@ class _StationsModule extends CoreClass {
 					(station, next) => {
 						if (!station) return next("Station not found.");
 
-						NotificationsModule.runJob(
+						return NotificationsModule.runJob(
 							"UNSCHEDULE",
 							{
 								name: `stations.nextSong?id=${station._id}`
@@ -888,7 +890,7 @@ class _StationsModule extends CoreClass {
 						}
 						// TODO Pub/Sub this
 
-						UtilsModule.runJob("EMIT_TO_ROOM", {
+						IOModule.runJob("EMIT_TO_ROOM", {
 							room: `station.${station._id}`,
 							args: [
 								"event:songs.next",
@@ -904,14 +906,14 @@ class _StationsModule extends CoreClass {
 							.catch();
 
 						if (station.privacy === "public") {
-							UtilsModule.runJob("EMIT_TO_ROOM", {
+							IOModule.runJob("EMIT_TO_ROOM", {
 								room: "home",
 								args: ["event:station.nextSong", station._id, station.currentSong]
 							})
 								.then()
 								.catch();
 						} else {
-							const sockets = await UtilsModule.runJob("GET_ROOM_SOCKETS", { room: "home" }, this);
+							const sockets = await IOModule.runJob("GET_ROOM_SOCKETS", { room: "home" }, this);
 
 							Object.keys(sockets).forEach(socketKey => {
 								const socket = sockets[socketKey];
@@ -962,8 +964,8 @@ class _StationsModule extends CoreClass {
 						}
 
 						if (station.currentSong !== null && station.currentSong.songId !== undefined) {
-							UtilsModule.runJob("SOCKETS_JOIN_SONG_ROOM", {
-								sockets: await UtilsModule.runJob(
+							IOModule.runJob("SOCKETS_JOIN_SONG_ROOM", {
+								sockets: await IOModule.runJob(
 									"GET_ROOM_SOCKETS",
 									{
 										room: `station.${station._id}`
@@ -980,8 +982,8 @@ class _StationsModule extends CoreClass {
 								});
 							}
 						} else {
-							UtilsModule.runJob("SOCKETS_LEAVE_SONG_ROOMS", {
-								sockets: await UtilsModule.runJob(
+							IOModule.runJob("SOCKETS_LEAVE_SONG_ROOMS", {
+								sockets: await IOModule.runJob(
 									"GET_ROOM_SOCKETS",
 									{
 										room: `station.${station._id}`

+ 3 - 1
backend/logic/tasks.js

@@ -12,6 +12,7 @@ let TasksModule;
 let CacheModule;
 let StationsModule;
 let UtilsModule;
+let IOModule;
 
 class _TasksModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
@@ -35,6 +36,7 @@ class _TasksModule extends CoreClass {
 			CacheModule = this.moduleManager.modules.cache;
 			StationsModule = this.moduleManager.modules.stations;
 			UtilsModule = this.moduleManager.modules.utils;
+			IOModule = this.moduleManager.modules.io;
 
 			// this.createTask("testTask", testTask, 5000, true);
 
@@ -235,7 +237,7 @@ class _TasksModule extends CoreClass {
 									}).finally(() => next2());
 								}
 								if (Date.now() - session.refreshDate > 60 * 60 * 24 * 30 * 1000) {
-									return UtilsModule.runJob("SOCKETS_FROM_SESSION_ID", {
+									return IOModule.runJob("SOCKETS_FROM_SESSION_ID", {
 										sessionId: session.sessionId
 									}).then(response => {
 										if (response.sockets.length > 0) {

+ 0 - 571
backend/logic/utils.js

@@ -1,23 +1,14 @@
-import config from "config";
-
-import async from "async";
 import crypto from "crypto";
-import request from "request";
 import CoreClass from "../core";
 
 let UtilsModule;
 let IOModule;
-let CacheModule;
 
 class _UtilsModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		super("utils");
 
-		this.youtubeRequestCallbacks = [];
-		this.youtubeRequestsPending = 0;
-		this.youtubeRequestsActive = false;
-
 		UtilsModule = this;
 	}
 
@@ -29,7 +20,6 @@ class _UtilsModule extends CoreClass {
 	initialize() {
 		return new Promise(resolve => {
 			IOModule = this.moduleManager.modules.io;
-			CacheModule = this.moduleManager.modules.cache;
 
 			resolve();
 		});
@@ -256,567 +246,6 @@ class _UtilsModule extends CoreClass {
 		});
 	}
 
-	// UNKNOWN
-	// eslint-disable-next-line require-jsdoc
-	async SOCKET_FROM_SESSION(payload) {
-		// socketId
-		const io = await IOModule.runJob("IO", {}, this);
-
-		return new Promise((resolve, reject) => {
-			const ns = io.of("/");
-			if (ns) {
-				return resolve(ns.connected[payload.socketId]);
-			}
-
-			return reject();
-		});
-	}
-
-	/**
-	 * Gets all sockets for a specified session id
-	 *
-	 * @param {object} payload - object containing the payload
-	 * @param {string} payload.sessionId - user session id
-	 * @returns {Promise} - returns promise (reject, resolve)
-	 */
-	async SOCKETS_FROM_SESSION_ID(payload) {
-		const io = await IOModule.runJob("IO", {}, this);
-
-		return new Promise(resolve => {
-			const ns = io.of("/");
-			const sockets = [];
-
-			if (ns) {
-				return async.each(
-					Object.keys(ns.connected),
-					(id, next) => {
-						const { session } = ns.connected[id];
-						if (session.sessionId === payload.sessionId) sockets.push(session.sessionId);
-						next();
-					},
-					() => {
-						resolve({ sockets });
-					}
-				);
-			}
-
-			return resolve();
-		});
-	}
-
-	/**
-	 * Returns any sockets for a specific user
-	 *
-	 * @param {object} payload - object that contains the payload
-	 * @param {string} payload.userId - the user id
-	 * @returns {Promise} - returns promise (reject, resolve)
-	 */
-	async SOCKETS_FROM_USER(payload) {
-		const io = await IOModule.runJob("IO", {}, this);
-
-		return new Promise((resolve, reject) => {
-			const ns = io.of("/");
-			const sockets = [];
-
-			if (ns) {
-				return async.each(
-					Object.keys(ns.connected),
-					(id, next) => {
-						const { session } = ns.connected[id];
-						CacheModule.runJob(
-							"HGET",
-							{
-								table: "sessions",
-								key: session.sessionId
-							},
-							this
-						)
-							.then(session => {
-								if (session && session.userId === payload.userId) sockets.push(ns.connected[id]);
-								next();
-							})
-							.catch(err => {
-								next(err);
-							});
-					},
-					err => {
-						if (err) return reject(err);
-						return resolve({ sockets });
-					}
-				);
-			}
-
-			return resolve();
-		});
-	}
-
-	/**
-	 * Returns any sockets from a specific ip address
-	 *
-	 * @param {object} payload - object that contains the payload
-	 * @param {string} payload.ip - the ip address in question
-	 * @returns {Promise} - returns promise (reject, resolve)
-	 */
-	async SOCKETS_FROM_IP(payload) {
-		const io = await IOModule.runJob("IO", {}, this);
-
-		return new Promise(resolve => {
-			const ns = io.of("/");
-			const sockets = [];
-			if (ns) {
-				return async.each(
-					Object.keys(ns.connected),
-					(id, next) => {
-						const { session } = ns.connected[id];
-						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();
-							})
-							.catch(() => next());
-					},
-					() => {
-						resolve({ sockets });
-					}
-				);
-			}
-
-			return resolve();
-		});
-	}
-
-	/**
-	 * Returns any sockets from a specific user without using redis/cache
-	 *
-	 * @param {object} payload - object that contains the payload
-	 * @param {string} payload.userId - the id of the user in question
-	 * @returns {Promise} - returns promise (reject, resolve)
-	 */
-	async SOCKETS_FROM_USER_WITHOUT_CACHE(payload) {
-		const io = await IOModule.runJob("IO", {}, this);
-
-		return new Promise(resolve => {
-			const ns = io.of("/");
-			const sockets = [];
-
-			if (ns) {
-				return async.each(
-					Object.keys(ns.connected),
-					(id, next) => {
-						const { session } = ns.connected[id];
-						if (session.userId === payload.userId) sockets.push(ns.connected[id]);
-						next();
-					},
-					() => {
-						resolve({ sockets });
-					}
-				);
-			}
-
-			return resolve();
-		});
-	}
-
-	/**
-	 * Allows a socket to leave any rooms they are connected to
-	 *
-	 * @param {object} payload - object that contains the payload
-	 * @param {string} payload.socketId - the id of the socket which should leave all their rooms
-	 * @returns {Promise} - returns promise (reject, resolve)
-	 */
-	async SOCKET_LEAVE_ROOMS(payload) {
-		const socket = await UtilsModule.runJob(
-			"SOCKET_FROM_SESSION",
-			{
-				socketId: payload.socketId
-			},
-			this
-		);
-
-		return new Promise(resolve => {
-			const { rooms } = socket;
-
-			Object.keys(rooms).forEach(roomKey => {
-				const room = rooms[roomKey];
-				socket.leave(room);
-			});
-
-			return resolve();
-		});
-	}
-
-	/**
-	 * Allows a socket to join a specified room
-	 *
-	 * @param {object} payload - object that contains the payload
-	 * @param {string} payload.socketId - the id of the socket which should join the room
-	 * @param {object} payload.room - the object representing the room the socket should join
-	 * @returns {Promise} - returns promise (reject, resolve)
-	 */
-	async SOCKET_JOIN_ROOM(payload) {
-		const socket = await UtilsModule.runJob(
-			"SOCKET_FROM_SESSION",
-			{
-				socketId: payload.socketId
-			},
-			this
-		);
-
-		return new Promise(resolve => {
-			const { rooms } = socket;
-			Object.keys(rooms).forEach(roomKey => {
-				const room = rooms[roomKey];
-				socket.leave(room);
-			});
-
-			socket.join(payload.room);
-
-			return resolve();
-		});
-	}
-
-	// UNKNOWN
-	// eslint-disable-next-line require-jsdoc
-	async SOCKET_JOIN_SONG_ROOM(payload) {
-		// socketId, room
-		const socket = await UtilsModule.runJob(
-			"SOCKET_FROM_SESSION",
-			{
-				socketId: payload.socketId
-			},
-			this
-		);
-
-		return new Promise(resolve => {
-			const { rooms } = socket;
-			Object.keys(rooms).forEach(roomKey => {
-				const room = rooms[roomKey];
-				if (room.indexOf("song.") !== -1) socket.leave(room);
-			});
-
-			socket.join(payload.room);
-
-			return resolve();
-		});
-	}
-
-	// UNKNOWN
-	// eslint-disable-next-line require-jsdoc
-	SOCKETS_JOIN_SONG_ROOM(payload) {
-		// sockets, room
-		return new Promise(resolve => {
-			Object.keys(payload.sockets).forEach(socketKey => {
-				const socket = payload.sockets[socketKey];
-
-				const { rooms } = socket;
-				Object.keys(rooms).forEach(roomKey => {
-					const room = rooms[roomKey];
-					if (room.indexOf("song.") !== -1) socket.leave(room);
-				});
-
-				socket.join(payload.room);
-			});
-
-			return resolve();
-		});
-	}
-
-	// UNKNOWN
-	// eslint-disable-next-line require-jsdoc
-	SOCKETS_LEAVE_SONG_ROOMS(payload) {
-		// sockets
-		return new Promise(resolve => {
-			Object.keys(payload.sockets).forEach(socketKey => {
-				const socket = payload.sockets[socketKey];
-				const { rooms } = socket;
-				Object.keys(rooms).forEach(roomKey => {
-					const room = rooms[roomKey];
-					if (room.indexOf("song.") !== -1) socket.leave(room);
-				});
-			});
-			resolve();
-		});
-	}
-
-	/**
-	 * Emits arguments to any sockets that are in a specified a room
-	 *
-	 * @param {object} payload - object that contains the payload
-	 * @param {string} payload.room - the name of the room to emit arguments
-	 * @param {object} payload.args - any arguments to be emitted to the sockets in the specific room
-	 * @returns {Promise} - returns promise (reject, resolve)
-	 */
-	async EMIT_TO_ROOM(payload) {
-		const io = await IOModule.runJob("IO", {}, this);
-
-		return new Promise(resolve => {
-			const { sockets } = io.sockets;
-			Object.keys(sockets).forEach(socketKey => {
-				const socket = sockets[socketKey];
-				if (socket.rooms[payload.room]) {
-					socket.emit(...payload.args);
-				}
-			});
-
-			return resolve();
-		});
-	}
-
-	/**
-	 * Gets any sockets connected to a room
-	 *
-	 * @param {object} payload - object that contains the payload
-	 * @param {string} payload.room - the name of the room
-	 * @returns {Promise} - returns promise (reject, resolve)
-	 */
-	async GET_ROOM_SOCKETS(payload) {
-		const io = await IOModule.runJob("IO", {}, this);
-
-		return new Promise(resolve => {
-			const { sockets } = io.sockets;
-			const roomSockets = [];
-			Object.keys(sockets).forEach(socketKey => {
-				const socket = sockets[socketKey];
-				if (socket.rooms[payload.room]) roomSockets.push(socket);
-			});
-
-			return resolve(roomSockets);
-		});
-	}
-
-	/**
-	 * Gets the details of a song using the YouTube API
-	 *
-	 * @param {object} payload - object that contains the payload
-	 * @param {string} payload.songId - the YouTube API id of the song
-	 * @returns {Promise} - returns promise (reject, resolve)
-	 */
-	GET_SONG_FROM_YOUTUBE(payload) {
-		// songId, cb
-		return new Promise((resolve, reject) => {
-			UtilsModule.youtubeRequestCallbacks.push({
-				cb: () => {
-					UtilsModule.youtubeRequestsActive = true;
-					const youtubeParams = [
-						"part=snippet,contentDetails,statistics,status",
-						`id=${encodeURIComponent(payload.songId)}`,
-						`key=${config.get("apis.youtube.key")}`
-					].join("&");
-
-					request(`https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`, (err, res, body) => {
-						UtilsModule.youtubeRequestCallbacks.splice(0, 1);
-						if (UtilsModule.youtubeRequestCallbacks.length > 0) {
-							UtilsModule.youtubeRequestCallbacks[0].cb(UtilsModule.youtubeRequestCallbacks[0].songId);
-						} else UtilsModule.youtubeRequestsActive = false;
-
-						if (err) {
-							console.error(err);
-							return null;
-						}
-
-						body = JSON.parse(body);
-
-						if (body.error) {
-							console.log("ERROR", "GET_SONG_FROM_YOUTUBE", `${body.error.message}`);
-							return reject(new Error("An error has occured. Please try again later."));
-						}
-
-						if (body.items[0] === undefined)
-							return reject(
-								new Error("The specified video does not exist or cannot be publicly accessed.")
-							);
-
-						// TODO Clean up duration converter
-						let dur = body.items[0].contentDetails.duration;
-
-						dur = dur.replace("PT", "");
-
-						let duration = 0;
-
-						dur = dur.replace(/([\d]*)H/, (v, v2) => {
-							v2 = Number(v2);
-							duration = v2 * 60 * 60;
-							return "";
-						});
-
-						dur = dur.replace(/([\d]*)M/, (v, v2) => {
-							v2 = Number(v2);
-							duration += v2 * 60;
-							return "";
-						});
-
-						// eslint-disable-next-line no-unused-vars
-						dur = dur.replace(/([\d]*)S/, (v, v2) => {
-							v2 = Number(v2);
-							duration += v2;
-							return "";
-						});
-
-						const song = {
-							songId: body.items[0].id,
-							title: body.items[0].snippet.title,
-							duration
-						};
-
-						return resolve({ song });
-					});
-				},
-				songId: payload.songId
-			});
-
-			if (!UtilsModule.youtubeRequestsActive) {
-				UtilsModule.youtubeRequestCallbacks[0].cb(UtilsModule.youtubeRequestCallbacks[0].songId);
-			}
-		});
-	}
-
-	/**
-	 * Filters a list of YouTube videos so that they only contains videos with music
-	 *
-	 * @param {object} payload - object that contains the payload
-	 * @param {Array} payload.videoIds - an array of YouTube videoIds to filter through
-	 * @returns {Promise} - returns promise (reject, resolve)
-	 */
-	FILTER_MUSIC_VIDEOS_YOUTUBE(payload) {
-		// videoIds, cb
-		return new Promise((resolve, reject) => {
-			/**
-			 * @param {Function} cb2 - callback
-			 */
-			function getNextPage(cb2) {
-				const localVideoIds = payload.videoIds.splice(0, 50);
-
-				const youtubeParams = [
-					"part=topicDetails",
-					`id=${encodeURIComponent(localVideoIds.join(","))}`,
-					`maxResults=50`,
-					`key=${config.get("apis.youtube.key")}`
-				].join("&");
-
-				request(`https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`, (err, res, body) => {
-					if (err) {
-						console.error(err);
-						return reject(new Error("Failed to find playlist from YouTube"));
-					}
-
-					body = JSON.parse(body);
-
-					if (body.error) {
-						console.log("ERROR", "FILTER_MUSIC_VIDEOS_YOUTUBE", `${body.error.message}`);
-						return reject(new Error("An error has occured. Please try again later."));
-					}
-
-					const songIds = [];
-					body.items.forEach(item => {
-						const songId = item.id;
-						if (!item.topicDetails) return;
-						if (item.topicDetails.relevantTopicIds.indexOf("/m/04rlf") !== -1) {
-							songIds.push(songId);
-						}
-					});
-
-					if (payload.videoIds.length > 0) {
-						return getNextPage(newSongIds => {
-							cb2(songIds.concat(newSongIds));
-						});
-					}
-
-					return cb2(songIds);
-				});
-			}
-
-			if (payload.videoIds.length === 0) resolve({ songIds: [] });
-			else getNextPage(songIds => resolve({ songIds }));
-		});
-	}
-
-	/**
-	 * Returns an array of songs taken from a YouTube playlist
-	 *
-	 * @param {object} payload - object that contains the payload
-	 * @param {boolean} payload.musicOnly - whether to return music videos or all videos in the playlist
-	 * @param {string} payload.url - the url of the YouTube playlist
-	 * @returns {Promise} - returns promise (reject, resolve)
-	 */
-	GET_PLAYLIST_FROM_YOUTUBE(payload) {
-		// payload includes: url, musicOnly
-		return new Promise((resolve, reject) => {
-			const local = this;
-
-			const name = "list".replace(/[\\[]/, "\\[").replace(/[\]]/, "\\]");
-
-			const regex = new RegExp(`[\\?&]${name}=([^&#]*)`);
-			const splitQuery = regex.exec(payload.url);
-
-			if (!splitQuery) {
-				console.log("ERROR", "GET_PLAYLIST_FROM_YOUTUBE", "Invalid YouTube playlist URL query.");
-				return reject(new Error("An error has occured. Please try again later."));
-			}
-
-			const playlistId = splitQuery[1];
-
-			/**
-			 * @param {string} pageToken - page token for YouTube API
-			 * @param {Array} songs - array of songs
-			 */
-			function getPage(pageToken, songs) {
-				const nextPageToken = pageToken ? `pageToken=${pageToken}` : "";
-				const youtubeParams = [
-					"part=contentDetails",
-					`playlistId=${encodeURIComponent(playlistId)}`,
-					`maxResults=50`,
-					`key=${config.get("apis.youtube.key")}`,
-					nextPageToken
-				].join("&");
-
-				request(
-					`https://www.googleapis.com/youtube/v3/playlistItems?${youtubeParams}`,
-					async (err, res, body) => {
-						if (err) {
-							console.error(err);
-							return reject(new Error("Failed to find playlist from YouTube"));
-						}
-
-						body = JSON.parse(body);
-
-						if (body.error) {
-							console.log("ERROR", "GET_PLAYLIST_FROM_YOUTUBE", `${body.error.message}`);
-							return reject(new Error("An error has occured. Please try again later."));
-						}
-
-						songs = songs.concat(body.items);
-
-						if (body.nextPageToken) return getPage(body.nextPageToken, songs);
-
-						songs = songs.map(song => song.contentDetails.videoId);
-
-						if (!payload.musicOnly) return resolve({ songs });
-						return local
-							.runJob(
-								"FILTER_MUSIC_VIDEOS_YOUTUBE",
-								{
-									videoIds: songs.slice()
-								},
-								this
-							)
-							.then(filteredSongs => {
-								resolve({ filteredSongs, songs });
-							});
-					}
-				);
-			}
-
-			return getPage(null, []);
-		});
-	}
-
 	/**
 	 * Shuffles an array of songs
 	 *

+ 292 - 0
backend/logic/youtube.js

@@ -0,0 +1,292 @@
+import async from "async";
+import config from "config";
+
+import request from "request";
+
+import CoreClass from "../core";
+
+let YouTubeModule;
+
+class _YouTubeModule extends CoreClass {
+	// eslint-disable-next-line require-jsdoc
+	constructor() {
+		super("youtube");
+
+		YouTubeModule = this;
+	}
+
+	/**
+	 * Initialises the activities module
+	 *
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	initialize() {
+		return new Promise(resolve => {
+			resolve();
+		});
+	}
+
+	/**
+	 * Gets the details of a song using the YouTube API
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {string} payload.songId - the YouTube API id of the song
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	GET_SONG(payload) {
+		// songId, cb
+		return new Promise((resolve, reject) => {
+			const youtubeParams = [
+				"part=snippet,contentDetails,statistics,status",
+				`id=${encodeURIComponent(payload.songId)}`,
+				`key=${config.get("apis.youtube.key")}`
+			].join("&");
+
+			request(`https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`, (err, res, body) => {
+				if (err) {
+					YouTubeModule.log("ERROR", "GET_SONG", `${err.message}`);
+					return reject(new Error("An error has occured. Please try again later."));
+				}
+
+				body = JSON.parse(body);
+
+				if (body.error) {
+					YouTubeModule.log("ERROR", "GET_SONG", `${body.error.message}`);
+					return reject(new Error("An error has occured. Please try again later."));
+				}
+
+				if (body.items[0] === undefined)
+					return reject(new Error("The specified video does not exist or cannot be publicly accessed."));
+
+				// TODO Clean up duration converter
+				let dur = body.items[0].contentDetails.duration;
+
+				dur = dur.replace("PT", "");
+
+				let duration = 0;
+
+				dur = dur.replace(/([\d]*)H/, (v, v2) => {
+					v2 = Number(v2);
+					duration = v2 * 60 * 60;
+					return "";
+				});
+
+				dur = dur.replace(/([\d]*)M/, (v, v2) => {
+					v2 = Number(v2);
+					duration += v2 * 60;
+					return "";
+				});
+
+				// eslint-disable-next-line no-unused-vars
+				dur = dur.replace(/([\d]*)S/, (v, v2) => {
+					v2 = Number(v2);
+					duration += v2;
+					return "";
+				});
+
+				const song = {
+					songId: body.items[0].id,
+					title: body.items[0].snippet.title,
+					duration
+				};
+
+				return resolve({ song });
+			});
+			// songId: payload.songId
+		});
+	}
+
+	/**
+	 * Returns an array of songs taken from a YouTube playlist
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {boolean} payload.musicOnly - whether to return music videos or all videos in the playlist
+	 * @param {string} payload.url - the url of the YouTube playlist
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	GET_PLAYLIST(payload) {
+		// payload includes: url, musicOnly
+		return new Promise((resolve, reject) => {
+			const name = "list".replace(/[\\[]/, "\\[").replace(/[\]]/, "\\]");
+
+			const regex = new RegExp(`[\\?&]${name}=([^&#]*)`);
+			const splitQuery = regex.exec(payload.url);
+
+			if (!splitQuery) {
+				YouTubeModule.log("ERROR", "GET_PLAYLIST", "Invalid YouTube playlist URL query.");
+				return reject(new Error("Invalid playlist URL."));
+			}
+			const playlistId = splitQuery[1];
+
+			return async.waterfall(
+				[
+					next => {
+						let songs = [];
+						let nextPageToken = "";
+
+						async.whilst(
+							next => {
+								YouTubeModule.log(
+									"INFO",
+									`Getting playlist progress for job (${this.toString()}): ${
+										songs.length
+									} songs gotten so far. Is there a next page: ${nextPageToken !== undefined}.`
+								);
+								next(null, nextPageToken !== undefined);
+							},
+							next => {
+								YouTubeModule.runJob("GET_PLAYLIST_PAGE", { playlistId, nextPageToken }, this)
+									// eslint-disable-next-line no-loop-func
+									.then(response => {
+										songs = songs.concat(response.songs);
+										nextPageToken = response.nextPageToken;
+										next();
+									})
+									// eslint-disable-next-line no-loop-func
+									.catch(err => {
+										next(err);
+									});
+							},
+							err => {
+								next(err, songs);
+							}
+						);
+					},
+
+					(songs, next) => {
+						next(
+							null,
+							songs.map(song => song.contentDetails.videoId)
+						);
+					},
+
+					(songs, next) => {
+						if (!payload.musicOnly) return next(true, { songs });
+						return YouTubeModule.runJob(
+							"FILTER_MUSIC_VIDEOS",
+							{
+								videoIds: songs.slice()
+							},
+							this
+						)
+							.then(filteredSongs => {
+								next(null, { filteredSongs, songs });
+							})
+							.catch(next);
+					}
+				],
+				(err, response) => {
+					if (err && err !== true) {
+						YouTubeModule.log("ERROR", "GET_PLAYLIST", "Some error has occurred.", err.message);
+						reject(new Error("Some error has occurred."));
+					} else {
+						resolve({ songs: response.filteredSongs ? response.filteredSongs.songIds : response.songs });
+					}
+				}
+			);
+		});
+	}
+
+	/**
+	 * Returns a a page from a YouTube playlist. Is used internally by GET_PLAYLIST.
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {boolean} payload.playlistId - the playlist id to get videos from
+	 * @param {boolean} payload.nextPageToken - the nextPageToken to use
+	 * @param {string} payload.url - the url of the YouTube playlist
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	GET_PLAYLIST_PAGE(payload) {
+		// payload includes: playlistId, nextPageToken
+		return new Promise((resolve, reject) => {
+			const nextPageToken = payload.nextPageToken ? `pageToken=${payload.nextPageToken}` : "";
+			const videosPerPage = 50;
+			const youtubeParams = [
+				"part=contentDetails",
+				`playlistId=${encodeURIComponent(payload.playlistId)}`,
+				`maxResults=${videosPerPage}`,
+				`key=${config.get("apis.youtube.key")}`,
+				nextPageToken
+			].join("&");
+
+			request(`https://www.googleapis.com/youtube/v3/playlistItems?${youtubeParams}`, async (err, res, body) => {
+				if (err) {
+					YouTubeModule.log("ERROR", "GET_PLAYLIST_PAGE", `${err.message}`);
+					return reject(new Error("An error has occured. Please try again later."));
+				}
+
+				body = JSON.parse(body);
+
+				if (body.error) {
+					YouTubeModule.log("ERROR", "GET_PLAYLIST_PAGE", `${body.error.message}`);
+					return reject(new Error("An error has occured. Please try again later."));
+				}
+
+				const songs = body.items;
+
+				if (body.nextPageToken) return resolve({ nextPageToken: body.nextPageToken, songs });
+				return resolve({ songs });
+			});
+		});
+	}
+
+	/**
+	 * Filters a list of YouTube videos so that they only contains videos with music. Is used internally by GET_PLAYLIST
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {Array} payload.videoIds - an array of YouTube videoIds to filter through
+	 * @param {Array} payload.page - the current page/set of video's to get, starting at 0. If left null, 0 is assumed. Will recurse.
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	FILTER_MUSIC_VIDEOS(payload) {
+		return new Promise((resolve, reject) => {
+			const page = payload.page ? payload.page : 0;
+			const videosPerPage = 50; // 50 is the max I believe
+			const localVideoIds = payload.videoIds.splice(page * 50, videosPerPage);
+
+			if (localVideoIds.length === 0) {
+				return resolve({ videoIds: [] });
+			}
+
+			const youtubeParams = [
+				"part=topicDetails",
+				`id=${encodeURIComponent(localVideoIds.join(","))}`,
+				`maxResults=${videosPerPage}`,
+				`key=${config.get("apis.youtube.key")}`
+			].join("&");
+
+			return request(`https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`, (err, res, body) => {
+				if (err) {
+					YouTubeModule.log("ERROR", "FILTER_MUSIC_VIDEOS", `${err.message}`);
+					return reject(new Error("Failed to find playlist from YouTube"));
+				}
+
+				body = JSON.parse(body);
+
+				if (body.error) {
+					YouTubeModule.log("ERROR", "FILTER_MUSIC_VIDEOS", `${body.error.message}`);
+					return reject(new Error("An error has occured. Please try again later."));
+				}
+
+				const songIds = [];
+				body.items.forEach(item => {
+					const songId = item.id;
+					if (!item.topicDetails) return;
+					if (item.topicDetails.relevantTopicIds.indexOf("/m/04rlf") !== -1) {
+						songIds.push(songId);
+					}
+				});
+
+				return YouTubeModule.runJob("FILTER_MUSIC_VIDEOS", { videoIds: payload.videoIds, page: page + 1 })
+					.then(result => {
+						resolve({ songIds: songIds.concat(result.songIds) });
+					})
+					.catch(err => {
+						reject(err);
+					});
+			});
+		});
+	}
+}
+
+export default new _YouTubeModule();

+ 0 - 5
frontend/dist/index.css

@@ -123,10 +123,6 @@ h6 {
 	float: right !important;
 }
 
-.light-blue {
-	background-color: #ff4545 !important;
-}
-
 .white {
 	background-color: #FFFFFF !important;
 }
@@ -141,7 +137,6 @@ a.nav-item.is-tab {
 }
 
 .button.is-info {
-	background-color: #ff4545;
 	border-width: 0;
 	color: #fff;
 }

+ 2 - 2
frontend/src/App.vue

@@ -446,11 +446,11 @@ button.delete:focus {
 	}
 
 	&.is-info {
-		background-color: $blue !important;
+		background-color: $musare-blue !important;
 
 		&:hover,
 		&:focus {
-			background-color: darken($blue, 5%) !important;
+			background-color: darken($musare-blue, 5%) !important;
 		}
 	}
 

+ 1 - 2
frontend/src/components/ui/PlaylistItem.vue

@@ -50,9 +50,8 @@ export default {
 .playlist {
 	width: 100%;
 	height: 72px;
-	border: 0.5px $light-grey-2 solid;
 	margin-bottom: 12px;
-	border-radius: 0 5px 5px 0;
+	border-radius: 5px;
 	display: flex;
 
 	.top-text {

+ 31 - 11
frontend/src/pages/Home/index.vue

@@ -23,7 +23,7 @@
 					</a>
 				</div>
 				<router-link
-					v-for="(station, index) in stations"
+					v-for="(station, index) in filteredStations()"
 					:key="index"
 					:to="{
 						name: 'station',
@@ -131,7 +131,13 @@
 					</div>
 					<div class="bottomBar">
 						<i
-							v-if="station.currentSong.title"
+							v-if="station.paused && station.currentSong.title"
+							class="material-icons"
+							title="Station Paused"
+							>pause</i
+						>
+						<i
+							v-else-if="station.currentSong.title"
 							class="material-icons"
 							>music_note</i
 						>
@@ -184,14 +190,6 @@ export default {
 		};
 	},
 	computed: {
-		filteredStations() {
-			return this.stations.filter(
-				station =>
-					JSON.stringify(Object.values(station)).indexOf(
-						this.searchQuery
-					) !== -1
-			);
-		},
 		...mapState({
 			loggedIn: state => state.user.auth.loggedIn,
 			userId: state => state.user.auth.userId,
@@ -280,12 +278,34 @@ export default {
 			});
 			this.socket.emit("apis.joinRoom", "home", () => {});
 		},
+		filteredStations() {
+			const privacyOrder = ["public", "unlisted", "private"];
+			return this.stations
+				.filter(
+					station =>
+						JSON.stringify(Object.values(station)).indexOf(
+							this.searchQuery
+						) !== -1
+				)
+				.sort(
+					(a, b) =>
+						this.isFavorite(b) - this.isFavorite(a) ||
+						this.isOwner(b) - this.isOwner(a) ||
+						this.isPlaying(b) - this.isPlaying(a) ||
+						a.paused - b.paused ||
+						privacyOrder.indexOf(a.privacy) -
+							privacyOrder.indexOf(b.privacy)
+				);
+		},
 		isOwner(station) {
 			return station.owner === this.userId;
 		},
 		isFavorite(station) {
 			return this.favoriteStations.indexOf(station._id) !== -1;
 		},
+		isPlaying(station) {
+			return typeof station.currentSong.title !== "undefined";
+		},
 		favoriteStation(event, station) {
 			event.preventDefault();
 			this.socket.emit("stations.favoriteStation", station._id, res => {
@@ -526,7 +546,7 @@ html {
 				width: 100%;
 				position: absolute;
 				top: 0;
-				filter: blur(3px);
+				filter: blur(1px);
 			}
 			img {
 				height: auto;

+ 2 - 1
frontend/src/pages/Profile.vue

@@ -670,8 +670,9 @@ export default {
 			height: 72px;
 			border: 0.5px $light-grey-2 solid;
 			margin-bottom: 12px;
-			border-radius: 0 5px 5px 0;
+			border-radius: 5px;
 			display: flex;
+			overflow: hidden;
 
 			.top-text {
 				color: $dark-grey-2;

+ 45 - 16
frontend/src/pages/Station/components/CurrentlyPlaying.vue

@@ -1,19 +1,24 @@
 <template>
 	<div id="currently-playing">
-		<div
-			v-if="currentSong.ytThumbnail"
-			class="thumbnail"
-			id="yt-thumbnail"
-			:style="{
-				'background-image': 'url(' + currentSong.ytThumbnail + ')'
-			}"
-		/>
-		<img
-			v-if="currentSong.thumbnail"
-			class="thumbnail"
-			:src="currentSong.thumbnail"
-			onerror="this.src='/assets/notes-transparent.png'"
-		/>
+		<figure class="thumbnail">
+			<div
+				v-if="currentSong.ytThumbnail"
+				class="ytThumbnailBg"
+				:style="{
+					'background-image': 'url(' + currentSong.ytThumbnail + ')'
+				}"
+			></div>
+			<img
+				v-if="currentSong.ytThumbnail"
+				:src="currentSong.ytThumbnail"
+				onerror="this.src='/assets/notes-transparent.png'"
+			/>
+			<img
+				v-else
+				:src="currentSong.thumbnail"
+				onerror="this.src='/assets/notes-transparent.png'"
+			/>
+		</figure>
 		<div id="song-info">
 			<h6>Currently playing...</h6>
 			<h4
@@ -84,12 +89,36 @@ export default {
 	align-items: center;
 	width: 100%;
 	height: 100%;
-	padding: 10px 20px;
+	padding: 10px;
 
 	.thumbnail {
 		min-width: 140px;
 		max-height: 140px;
 		height: 100%;
+		position: relative;
+
+		.ytThumbnailBg {
+			height: 100%;
+			width: 100%;
+			position: absolute;
+			top: 0;
+			filter: blur(1px);
+			background: url("/assets/notes-transparent.png") no-repeat center
+				center;
+		}
+
+		img {
+			height: auto;
+			width: 100%;
+			margin-top: auto;
+			margin-bottom: auto;
+			z-index: 1;
+			position: absolute;
+			top: 0;
+			bottom: 0;
+			left: 0;
+			right: 0;
+		}
 	}
 
 	#yt-thumbnail {
@@ -154,7 +183,7 @@ export default {
 			}
 
 			#report-icon {
-				background-color: #6b6a6a;
+				background-color: $grey;
 			}
 
 			#youtube-icon {

+ 5 - 1
frontend/src/pages/Station/components/Sidebar/MyPlaylists.vue

@@ -150,6 +150,7 @@ export default {
 	border: 1px solid $light-grey-2;
 	margin-bottom: 20px;
 	padding: 10px;
+	border-radius: 0 0 5px 5px;
 
 	.icons-group {
 		display: flex;
@@ -170,12 +171,15 @@ export default {
 
 .menu-list li {
 	align-items: center;
+	.playlist {
+		border: 0.5px $light-grey-2 solid;
+	}
 }
 
 .create-playlist {
 	width: 100%;
 	height: 40px;
-	border-radius: 0;
+	border-radius: 5px;
 	background-color: rgba(3, 169, 244, 1);
 	color: $white !important;
 	border: 0;

+ 21 - 19
frontend/src/pages/Station/components/Sidebar/Queue/index.vue

@@ -10,26 +10,19 @@
 			<p class="nothing-here" v-if="songsList.length < 1">
 				There are no songs currently queued
 			</p>
-		</div>
-		<div
-			id="queue-buttons"
-			v-if="
-				(loggedIn &&
-					(station.type === 'community' && station.partyMode)) ||
-					station.type === 'official'
-			"
-		>
 			<button
 				id="add-song-to-queue"
 				class="button is-primary"
 				v-if="
-					(station.type === 'community' &&
-						((station.locked && isOwnerOnly()) ||
-							!station.locked ||
-							(station.locked &&
-								isAdminOnly() &&
-								dismissedWarning))) ||
-						station.type === 'official'
+					loggedIn &&
+						((station.type === 'community' &&
+							station.partyMode &&
+							((station.locked && isOwnerOnly()) ||
+								!station.locked ||
+								(station.locked &&
+									isAdminOnly() &&
+									dismissedWarning))) ||
+							station.type === 'official')
 				"
 				@click="
 					openModal({
@@ -159,10 +152,19 @@ export default {
 
 	#add-song-to-queue {
 		width: 100%;
-		height: 45px;
+		height: 40px;
+		border-radius: 5px;
+		background-color: rgba(3, 169, 244, 1);
+		color: $white !important;
+		border: 0;
+		margin-top: 10px;
+		&:active,
+		&:focus {
+			border: 0;
+		}
 
-		@media (min-width: 1040px) {
-			border-radius: 5px;
+		&:focus {
+			background-color: $primary-color;
 		}
 	}
 }

+ 1 - 0
frontend/src/pages/Station/components/Sidebar/Users.vue

@@ -46,6 +46,7 @@ export default {
 	border: 1px solid $light-grey-2;
 	margin-bottom: 20px;
 	padding: 10px;
+	border-radius: 0 0 5px 5px;
 
 	.menu-list li a:hover {
 		color: #000 !important;

+ 2 - 2
frontend/src/pages/Station/components/Sidebar/index.vue

@@ -68,13 +68,13 @@ export default {
 	display: flex;
 
 	.button {
-		border-radius: 0;
+		border-radius: 5px 5px 0 0;
 		border: 0;
 		text-transform: uppercase;
 		font-size: 17px;
 		color: #222;
 		background-color: #ddd;
-		width: -webkit-fill-available;
+		flex-grow: 1;
 
 		&:not(:first-of-type) {
 			margin-left: 5px;

+ 65 - 22
frontend/src/pages/Station/index.vue

@@ -45,20 +45,6 @@
 								</span>
 							</button>
 
-							<!-- Debug Box -->
-							<button
-								class="button is-primary"
-								@click="togglePlayerDebugBox()"
-								@dblclick="resetPlayerDebugBox()"
-							>
-								<i class="material-icons icon-with-button">
-									bug_report
-								</i>
-								<span class="optional-desktop-only-text">
-									Debug player box
-								</span>
-							</button>
-
 							<!-- (Admin) Skip Button -->
 							<button
 								class="button is-danger"
@@ -131,7 +117,21 @@
 							<div id="seeker-bar" style="width: 0%" />
 						</div>
 						<div id="control-bar-container">
-							<div id="left-buttons" v-if="loggedIn">
+							<div id="left-buttons">
+								<!-- Debug Box -->
+								<button
+									class="button is-primary"
+									@click="togglePlayerDebugBox()"
+									@dblclick="resetPlayerDebugBox()"
+								>
+									<i class="material-icons icon-with-button">
+										bug_report
+									</i>
+									<span class="optional-desktop-only-text">
+										Debug
+									</span>
+								</button>
+
 								<!-- Local Pause/Resume Button -->
 								<button
 									class="button is-primary"
@@ -158,6 +158,7 @@
 
 								<!-- Vote to Skip Button -->
 								<button
+									v-if="loggedIn"
 									class="button is-primary"
 									@click="voteSkipStation()"
 								>
@@ -215,6 +216,10 @@
 										currentSong.likes !== -1 &&
 											currentSong.dislikes !== -1
 									"
+									:class="{
+										liked: liked,
+										disliked: disliked
+									}"
 								>
 									<!-- Like Song Button -->
 									<button
@@ -299,6 +304,8 @@
 			<report v-if="modals.report" />
 		</div>
 
+		<main-footer />
+
 		<floating-box id="playerDebugBox" ref="playerDebugBox">
 			<template #body>
 				<span><b>YouTube id</b>: {{ currentSong.songId }}</span>
@@ -344,6 +351,7 @@ import { mapState, mapActions } from "vuex";
 import Toast from "toasters";
 
 import MainHeader from "../../components/layout/MainHeader.vue";
+import MainFooter from "../../components/layout/MainFooter.vue";
 
 import Z404 from "../404.vue";
 
@@ -360,6 +368,7 @@ import StationSidebar from "./components/Sidebar/index.vue";
 export default {
 	components: {
 		MainHeader,
+		MainFooter,
 		SongQueue: () => import("./AddSongToQueue.vue"),
 		EditPlaylist: () => import("../../components/modals/EditPlaylist.vue"),
 		CreatePlaylist: () =>
@@ -474,6 +483,8 @@ export default {
 							if (this.currentSong.songId === song.songId) {
 								this.liked = song.liked;
 								this.disliked = song.disliked;
+								if (song.disliked === true)
+									this.voteSkipStation();
 							}
 						}
 					);
@@ -629,7 +640,8 @@ export default {
 			"station.lowerVolumeLarge",
 			"station.lowerVolumeSmall",
 			"station.increaseVolumeLarge",
-			"station.increaseVolumeSmall"
+			"station.increaseVolumeSmall",
+			"station.toggleDebug"
 		];
 
 		shortcutNames.forEach(shortcutName => {
@@ -1253,6 +1265,7 @@ export default {
 								keyCode: 32,
 								shift: false,
 								ctrl: true,
+								preventDefault: true,
 								handler: () => {
 									if (this.stationPaused)
 										this.resumeStation();
@@ -1267,6 +1280,7 @@ export default {
 								keyCode: 39,
 								shift: false,
 								ctrl: true,
+								preventDefault: true,
 								handler: () => {
 									this.skipStation();
 								}
@@ -1280,6 +1294,7 @@ export default {
 							keyCode: 40,
 							shift: false,
 							ctrl: true,
+							preventDefault: true,
 							handler: () => {
 								this.volumeSliderValue -= 1000;
 								this.changeVolume();
@@ -1293,6 +1308,7 @@ export default {
 							keyCode: 40,
 							shift: true,
 							ctrl: true,
+							preventDefault: true,
 							handler: () => {
 								this.volumeSliderValue -= 100;
 								this.changeVolume();
@@ -1306,6 +1322,7 @@ export default {
 							keyCode: 38,
 							shift: false,
 							ctrl: true,
+							preventDefault: true,
 							handler: () => {
 								this.volumeSliderValue += 1000;
 								this.changeVolume();
@@ -1319,6 +1336,7 @@ export default {
 							keyCode: 38,
 							shift: true,
 							ctrl: true,
+							preventDefault: true,
 							handler: () => {
 								this.volumeSliderValue += 100;
 								this.changeVolume();
@@ -1326,6 +1344,16 @@ export default {
 						}
 					);
 
+					keyboardShortcuts.registerShortcut("station.toggleDebug", {
+						keyCode: 68,
+						shift: false,
+						ctrl: true,
+						preventDefault: true,
+						handler: () => {
+							this.togglePlayerDebugBox();
+						}
+					});
+
 					// UNIX client time before ping
 					const beforePing = Date.now();
 					this.socket.emit("apis.ping", pong => {
@@ -1410,6 +1438,10 @@ export default {
 <style lang="scss" scoped>
 @import "../../styles/global.scss";
 
+.main-container {
+	min-height: calc(100vh + 180px);
+}
+
 .progress {
 	width: 50px;
 	animation: rotate 0.8s infinite linear;
@@ -1620,6 +1652,7 @@ export default {
 
 				p {
 					max-width: 700px;
+					flex-grow: 1;
 				}
 
 				#admin-buttons {
@@ -1652,17 +1685,19 @@ export default {
 				background-color: #fff;
 				display: flex;
 				flex-direction: column;
+				border: 1px solid $light-grey-2;
+				border-radius: 5px;
+				overflow: hidden;
 
 				#video-container {
 					width: 100%;
 					height: 100%;
-					border: 1px solid $light-grey-2;
-					border-bottom: 0;
 
 					.player-cannot-autoplay {
-						position: absolute;
+						position: relative;
 						width: 100%;
 						height: 100%;
+						bottom: calc(100% + 5px);
 						background: rgba(3, 169, 244, 0.95);
 						display: flex;
 						align-items: center;
@@ -1700,9 +1735,6 @@ export default {
 					width: 100%;
 					// height: 62px;
 					background: #fff;
-					border: 1px solid $light-grey-2;
-					border-radius: 0 0 5px 5px;
-					border-top: 0;
 
 					@media (max-width: 1450px) {
 						flex-direction: column;
@@ -1865,6 +1897,17 @@ export default {
 							#dislike-song.disliked {
 								background-color: darken($red, 5%) !important;
 							}
+
+							&.liked #dislike-song,
+							&.disliked #like-song {
+								background-color: $grey !important;
+								&:hover {
+									background-color: darken(
+										$grey,
+										5%
+									) !important;
+								}
+							}
 						}
 
 						#add-song-to-playlist {

+ 1 - 0
frontend/src/styles/colors.scss

@@ -18,6 +18,7 @@ $white: hsl(0, 0%, 100%);
 $black: hsl(0, 0%, 0%);
 $light-grey: hsl(0, 0%, 96%);
 $light-grey-2: hsl(300, 2%, 76%);
+$grey: hsl(0, 0%, 42%);
 $dark-grey: hsl(0, 0%, 30%);
 $dark-grey-2: hsl(0, 0%, 20%);
 $dark-grey-3: hsl(0, 0%, 10%);

Some files were not shown because too many files changed in this diff