Browse Source

Started working on song merging

Kristian Vos 4 years ago
parent
commit
bb3fa16e7b

+ 405 - 425
backend/logic/actions/queueSongs.js

@@ -12,434 +12,414 @@ const WSModule = moduleManager.modules.ws;
 const YouTubeModule = moduleManager.modules.youtube;
 const CacheModule = moduleManager.modules.cache;
 
-CacheModule.runJob("SUB", {
-	channel: "queue.newSong",
-	cb: async songId => {
-		const queueSongModel = await DBModule.runJob("GET_MODEL", {
-			modelName: "queueSong"
-		});
-		queueSongModel.findOne({ _id: songId }, (err, song) => {
-			WSModule.runJob("EMIT_TO_ROOM", {
-				room: "admin.queue",
-				args: ["event:admin.queueSong.added", song]
-			});
-		});
-	}
-});
+// CacheModule.runJob("SUB", {
+// 	channel: "queue.newSong",
+// 	cb: async songId => {
+// 		const queueSongModel = await DBModule.runJob("GET_MODEL", {
+// 			modelName: "queueSong"
+// 		});
+// 		queueSongModel.findOne({ _id: songId }, (err, song) => {
+// 			WSModule.runJob("EMIT_TO_ROOM", {
+// 				room: "admin.queue",
+// 				args: ["event:admin.queueSong.added", song]
+// 			});
+// 		});
+// 	}
+// });
 
-CacheModule.runJob("SUB", {
-	channel: "queue.removedSong",
-	cb: songId => {
-		WSModule.runJob("EMIT_TO_ROOM", {
-			room: "admin.queue",
-			args: ["event:admin.queueSong.removed", songId]
-		});
-	}
-});
+// CacheModule.runJob("SUB", {
+// 	channel: "queue.removedSong",
+// 	cb: songId => {
+// 		WSModule.runJob("EMIT_TO_ROOM", {
+// 			room: "admin.queue",
+// 			args: ["event:admin.queueSong.removed", songId]
+// 		});
+// 	}
+// });
 
-CacheModule.runJob("SUB", {
-	channel: "queue.update",
-	cb: async songId => {
-		const queueSongModel = await DBModule.runJob("GET_MODEL", {
-			modelName: "queueSong"
-		});
+// CacheModule.runJob("SUB", {
+// 	channel: "queue.update",
+// 	cb: async songId => {
+// 		const queueSongModel = await DBModule.runJob("GET_MODEL", {
+// 			modelName: "queueSong"
+// 		});
 
-		queueSongModel.findOne({ _id: songId }, (err, song) => {
-			WSModule.runJob("EMIT_TO_ROOM", {
-				room: "admin.queue",
-				args: ["event:admin.queueSong.updated", song]
-			});
-		});
-	}
-});
+// 		queueSongModel.findOne({ _id: songId }, (err, song) => {
+// 			WSModule.runJob("EMIT_TO_ROOM", {
+// 				room: "admin.queue",
+// 				args: ["event:admin.queueSong.updated", song]
+// 			});
+// 		});
+// 	}
+// });
 
 export default {
-	/**
-	 * Returns the length of the queue songs list
-	 *
-	 * @param session
-	 * @param cb
-	 */
-	length: isAdminRequired(async function length(session, cb) {
-		const queueSongModel = await DBModule.runJob("GET_MODEL", { modelName: "queueSong" }, this);
-
-		async.waterfall(
-			[
-				next => {
-					queueSongModel.countDocuments({}, next);
-				}
-			],
-			async (err, count) => {
-				if (err) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log("ERROR", "QUEUE_SONGS_LENGTH", `Failed to get length from queue songs. "${err}"`);
-					return cb({ status: "failure", message: err });
-				}
-				this.log("SUCCESS", "QUEUE_SONGS_LENGTH", `Got length from queue songs successfully.`);
-				return cb(count);
-			}
-		);
-	}),
-
-	/**
-	 * Gets a set of queue songs
-	 *
-	 * @param session
-	 * @param set - the set number to return
-	 * @param cb
-	 */
-	getSet: isAdminRequired(async function getSet(session, set, cb) {
-		const queueSongModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "queueSong"
-			},
-			this
-		);
-		async.waterfall(
-			[
-				next => {
-					queueSongModel
-						.find({})
-						.skip(15 * (set - 1))
-						.limit(15)
-						.exec(next);
-				}
-			],
-			async (err, songs) => {
-				if (err) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log("ERROR", "QUEUE_SONGS_GET_SET", `Failed to get set from queue songs. "${err}"`);
-					return cb({ status: "failure", message: err });
-				}
-				this.log("SUCCESS", "QUEUE_SONGS_GET_SET", `Got set from queue songs successfully.`);
-				return cb(songs);
-			}
-		);
-	}),
-
-	/**
-	 * Gets a song from the Musare song id
-	 *
-	 * @param {object} session - the session object automatically added by the websocket
-	 * @param {string} songId - the Musare song id
-	 * @param {Function} cb
-	 */
-	getSongFromMusareId: isAdminRequired(async function getSong(session, songId, cb) {
-		const queueSongModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "queueSong"
-			},
-			this
-		);
-
-		async.waterfall(
-			[
-				next => {
-					queueSongModel.findOne({ _id: songId }, next);
-				}
-			],
-			async (err, song) => {
-				if (err) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log("ERROR", "QUEUE_SONGS_GET_SONG_FROM_MUSARE_ID", `Failed to get song ${songId}. "${err}"`);
-					return cb({ status: "failure", message: err });
-				}
-				this.log("SUCCESS", "QUEUE_SONGS_GET_SONG_FROM_MUSARE_ID", `Got song ${songId} successfully.`);
-				return cb({ status: "success", data: { song } });
-			}
-		);
-	}),
-
-	/**
-	 * Updates a queuesong
-	 *
-	 * @param {object} session - the session object automatically added by the websocket
-	 * @param {string} songId - the id of the queuesong that gets updated
-	 * @param {object} updatedSong - the object of the updated queueSong
-	 * @param {Function} cb - gets called with the result
-	 */
-	update: isAdminRequired(async function update(session, songId, updatedSong, cb) {
-		const queueSongModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "queueSong"
-			},
-			this
-		);
-		async.waterfall(
-			[
-				next => {
-					queueSongModel.findOne({ _id: songId }, next);
-				},
-
-				(song, next) => {
-					if (!song) return next("Song not found");
-
-					let updated = false;
-
-					const $set = {};
-					Object.keys(updatedSong).forEach(prop => {
-						if (updatedSong[prop] !== song[prop]) $set[prop] = updatedSong[prop];
-					});
-
-					updated = true;
-					if (!updated) return next("No properties changed");
-
-					return queueSongModel.updateOne({ _id: songId }, { $set }, { runValidators: true }, next);
-				}
-			],
-			async err => {
-				if (err) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log(
-						"ERROR",
-						"QUEUE_UPDATE",
-						`Updating queuesong "${songId}" failed for user ${session.userId}. "${err}"`
-					);
-					return cb({ status: "failure", message: err });
-				}
-				CacheModule.runJob("PUB", { channel: "queue.update", value: songId });
-				this.log(
-					"SUCCESS",
-					"QUEUE_UPDATE",
-					`User "${session.userId}" successfully update queuesong "${songId}".`
-				);
-				return cb({
-					status: "success",
-					message: "Successfully updated song."
-				});
-			}
-		);
-	}),
-
-	/**
-	 * Removes a queuesong
-	 *
-	 * @param {object} session - the session object automatically added by the websocket
-	 * @param {string} songId - the id of the queuesong that gets removed
-	 * @param {Function} cb - gets called with the result
-	 */
-	remove: isAdminRequired(async function remove(session, songId, cb) {
-		const queueSongModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "queueSong"
-			},
-			this
-		);
-		async.waterfall(
-			[
-				next => {
-					queueSongModel.deleteOne({ _id: songId }, next);
-				}
-			],
-			async err => {
-				if (err) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log(
-						"ERROR",
-						"QUEUE_REMOVE",
-						`Removing queuesong "${songId}" failed for user ${session.userId}. "${err}"`
-					);
-					return cb({ status: "failure", message: err });
-				}
-				CacheModule.runJob("PUB", {
-					channel: "queue.removedSong",
-					value: songId
-				});
-				this.log(
-					"SUCCESS",
-					"QUEUE_REMOVE",
-					`User "${session.userId}" successfully removed queuesong "${songId}".`
-				);
-				return cb({
-					status: "success",
-					message: "Successfully updated song."
-				});
-			}
-		);
-	}),
-
-	/**
-	 * Creates a queuesong
-	 *
-	 * @param {object} session - the session object automatically added by the websocket
-	 * @param {string} songId - the id of the song that gets added
-	 * @param {Function} cb - gets called with the result
-	 */
-	add: isLoginRequired(async function add(session, songId, cb) {
-		const requestedAt = Date.now();
-		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
-		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
-		const QueueSongModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "queueSong"
-			},
-			this
-		);
-
-		async.waterfall(
-			[
-				next => {
-					QueueSongModel.findOne({ songId }, next);
-				},
-
-				(song, next) => {
-					if (song) return next("This song is already in the queue.");
-					return songModel.findOne({ songId }, next);
-				},
-
-				// Get YouTube data from id
-				(song, next) => {
-					if (song) return next("This song has already been added.");
-					// TODO Add err object as first param of callback
-					return YouTubeModule.runJob("GET_SONG", { songId }, this)
-						.then(response => {
-							const { song } = response;
-							song.duration = -1;
-							song.artists = [];
-							song.genres = [];
-							song.skipDuration = 0;
-							song.thumbnail = `${config.get("domain")}/assets/notes.png`;
-							song.explicit = false;
-							song.requestedBy = session.userId;
-							song.requestedAt = requestedAt;
-							next(null, song);
-						})
-						.catch(next);
-				},
-				(newSong, next) => {
-					const song = new QueueSongModel(newSong);
-					song.save({ validateBeforeSave: false }, (err, song) => {
-						if (err) return next(err);
-						return next(null, song);
-					});
-				},
-				(newSong, next) => {
-					userModel.findOne({ _id: session.userId }, (err, user) => {
-						if (err) return next(err, newSong);
-
-						user.statistics.songsRequested += 1;
-
-						return user.save(err => {
-							if (err) return next(err, newSong);
-							return next(null, newSong);
-						});
-					});
-				}
-			],
-			async (err, newSong) => {
-				if (err) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log(
-						"ERROR",
-						"QUEUE_ADD",
-						`Adding queuesong "${songId}" failed for user ${session.userId}. "${err}"`
-					);
-					return cb({ status: "failure", message: err });
-				}
-				CacheModule.runJob("PUB", {
-					channel: "queue.newSong",
-					value: newSong._id
-				});
-				this.log("SUCCESS", "QUEUE_ADD", `User "${session.userId}" successfully added queuesong "${songId}".`);
-				return cb({
-					status: "success",
-					message: "Successfully added that song to the queue"
-				});
-			}
-		);
-	}),
-
-	/**
-	 * Adds a set of songs to the queue
-	 *
-	 * @param {object} session - the session object automatically added by the websocket
-	 * @param {string} url - the url of the the YouTube playlist
-	 * @param {boolean} musicOnly - whether to only get music from the playlist
-	 * @param {Function} cb - gets called with the result
-	 */
-	addSetToQueue: isLoginRequired(function addSetToQueue(session, url, musicOnly, cb) {
-		async.waterfall(
-			[
-				next => {
-					YouTubeModule.runJob(
-						"GET_PLAYLIST",
-						{
-							url,
-							musicOnly
-						},
-						this
-					)
-						.then(res => {
-							next(null, res.songs);
-						})
-						.catch(next);
-				},
-				(songIds, next) => {
-					let successful = 0;
-					let failed = 0;
-					let alreadyInQueue = 0;
-					let alreadyAdded = 0;
-
-					if (songIds.length === 0) next();
-
-					async.eachLimit(
-						songIds,
-						1,
-						(songId, next) => {
-							WSModule.runJob(
-								"RUN_ACTION2",
-								{
-									session,
-									namespace: "queueSongs",
-									action: "add",
-									args: [songId]
-								},
-								this
-							)
-								.then(res => {
-									if (res.status === "success") successful += 1;
-									else failed += 1;
-									if (res.message === "This song is already in the queue.") alreadyInQueue += 1;
-									if (res.message === "This song has already been added.") alreadyAdded += 1;
-								})
-								.catch(() => {
-									failed += 1;
-								})
-								.finally(() => {
-									next();
-								});
-						},
-						() => {
-							next(null, { successful, failed, alreadyInQueue, alreadyAdded });
-						}
-					);
-				}
-			],
-			async (err, response) => {
-				if (err) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log(
-						"ERROR",
-						"QUEUE_IMPORT",
-						`Importing a YouTube playlist to the queue failed for user "${session.userId}". "${err}"`
-					);
-					return cb({ status: "failure", message: err });
-				}
-				this.log(
-					"SUCCESS",
-					"QUEUE_IMPORT",
-					`Successfully imported a YouTube playlist to the queue for user "${session.userId}".`
-				);
-				return cb({
-					status: "success",
-					message: `Playlist is done importing. ${response.successful} were added succesfully, ${response.failed} failed (${response.alreadyInQueue} were already in queue, ${response.alreadyAdded} were already added)`
-				});
-			}
-		);
-	})
+	// /**
+	//  * Returns the length of the queue songs list
+	//  *
+	//  * @param session
+	//  * @param cb
+	//  */
+	// length: isAdminRequired(async function length(session, cb) {
+	// 	const queueSongModel = await DBModule.runJob("GET_MODEL", { modelName: "queueSong" }, this);
+	// 	async.waterfall(
+	// 		[
+	// 			next => {
+	// 				queueSongModel.countDocuments({}, next);
+	// 			}
+	// 		],
+	// 		async (err, count) => {
+	// 			if (err) {
+	// 				err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+	// 				this.log("ERROR", "QUEUE_SONGS_LENGTH", `Failed to get length from queue songs. "${err}"`);
+	// 				return cb({ status: "failure", message: err });
+	// 			}
+	// 			this.log("SUCCESS", "QUEUE_SONGS_LENGTH", `Got length from queue songs successfully.`);
+	// 			return cb(count);
+	// 		}
+	// 	);
+	// }),
+	// /**
+	//  * Gets a set of queue songs
+	//  *
+	//  * @param session
+	//  * @param set - the set number to return
+	//  * @param cb
+	//  */
+	// getSet: isAdminRequired(async function getSet(session, set, cb) {
+	// 	const queueSongModel = await DBModule.runJob(
+	// 		"GET_MODEL",
+	// 		{
+	// 			modelName: "queueSong"
+	// 		},
+	// 		this
+	// 	);
+	// 	async.waterfall(
+	// 		[
+	// 			next => {
+	// 				queueSongModel
+	// 					.find({})
+	// 					.skip(15 * (set - 1))
+	// 					.limit(15)
+	// 					.exec(next);
+	// 			}
+	// 		],
+	// 		async (err, songs) => {
+	// 			if (err) {
+	// 				err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+	// 				this.log("ERROR", "QUEUE_SONGS_GET_SET", `Failed to get set from queue songs. "${err}"`);
+	// 				return cb({ status: "failure", message: err });
+	// 			}
+	// 			this.log("SUCCESS", "QUEUE_SONGS_GET_SET", `Got set from queue songs successfully.`);
+	// 			return cb(songs);
+	// 		}
+	// 	);
+	// }),
+	// /**
+	//  * Gets a song from the Musare song id
+	//  *
+	//  * @param {object} session - the session object automatically added by the websocket
+	//  * @param {string} songId - the Musare song id
+	//  * @param {Function} cb
+	//  */
+	// getSongFromMusareId: isAdminRequired(async function getSong(session, songId, cb) {
+	// 	const queueSongModel = await DBModule.runJob(
+	// 		"GET_MODEL",
+	// 		{
+	// 			modelName: "queueSong"
+	// 		},
+	// 		this
+	// 	);
+	// 	async.waterfall(
+	// 		[
+	// 			next => {
+	// 				queueSongModel.findOne({ _id: songId }, next);
+	// 			}
+	// 		],
+	// 		async (err, song) => {
+	// 			if (err) {
+	// 				err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+	// 				this.log("ERROR", "QUEUE_SONGS_GET_SONG_FROM_MUSARE_ID", `Failed to get song ${songId}. "${err}"`);
+	// 				return cb({ status: "failure", message: err });
+	// 			}
+	// 			this.log("SUCCESS", "QUEUE_SONGS_GET_SONG_FROM_MUSARE_ID", `Got song ${songId} successfully.`);
+	// 			return cb({ status: "success", data: { song } });
+	// 		}
+	// 	);
+	// }),
+	// /**
+	//  * Updates a queuesong
+	//  *
+	//  * @param {object} session - the session object automatically added by the websocket
+	//  * @param {string} songId - the id of the queuesong that gets updated
+	//  * @param {object} updatedSong - the object of the updated queueSong
+	//  * @param {Function} cb - gets called with the result
+	//  */
+	// update: isAdminRequired(async function update(session, songId, updatedSong, cb) {
+	// 	const queueSongModel = await DBModule.runJob(
+	// 		"GET_MODEL",
+	// 		{
+	// 			modelName: "queueSong"
+	// 		},
+	// 		this
+	// 	);
+	// 	async.waterfall(
+	// 		[
+	// 			next => {
+	// 				queueSongModel.findOne({ _id: songId }, next);
+	// 			},
+	// 			(song, next) => {
+	// 				if (!song) return next("Song not found");
+	// 				let updated = false;
+	// 				const $set = {};
+	// 				Object.keys(updatedSong).forEach(prop => {
+	// 					if (updatedSong[prop] !== song[prop]) $set[prop] = updatedSong[prop];
+	// 				});
+	// 				updated = true;
+	// 				if (!updated) return next("No properties changed");
+	// 				return queueSongModel.updateOne({ _id: songId }, { $set }, { runValidators: true }, next);
+	// 			}
+	// 		],
+	// 		async err => {
+	// 			if (err) {
+	// 				err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+	// 				this.log(
+	// 					"ERROR",
+	// 					"QUEUE_UPDATE",
+	// 					`Updating queuesong "${songId}" failed for user ${session.userId}. "${err}"`
+	// 				);
+	// 				return cb({ status: "failure", message: err });
+	// 			}
+	// 			CacheModule.runJob("PUB", { channel: "queue.update", value: songId });
+	// 			this.log(
+	// 				"SUCCESS",
+	// 				"QUEUE_UPDATE",
+	// 				`User "${session.userId}" successfully update queuesong "${songId}".`
+	// 			);
+	// 			return cb({
+	// 				status: "success",
+	// 				message: "Successfully updated song."
+	// 			});
+	// 		}
+	// 	);
+	// }),
+	// /**
+	//  * Removes a queuesong
+	//  *
+	//  * @param {object} session - the session object automatically added by the websocket
+	//  * @param {string} songId - the id of the queuesong that gets removed
+	//  * @param {Function} cb - gets called with the result
+	//  */
+	// remove: isAdminRequired(async function remove(session, songId, cb) {
+	// 	const queueSongModel = await DBModule.runJob(
+	// 		"GET_MODEL",
+	// 		{
+	// 			modelName: "queueSong"
+	// 		},
+	// 		this
+	// 	);
+	// 	async.waterfall(
+	// 		[
+	// 			next => {
+	// 				queueSongModel.deleteOne({ _id: songId }, next);
+	// 			}
+	// 		],
+	// 		async err => {
+	// 			if (err) {
+	// 				err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+	// 				this.log(
+	// 					"ERROR",
+	// 					"QUEUE_REMOVE",
+	// 					`Removing queuesong "${songId}" failed for user ${session.userId}. "${err}"`
+	// 				);
+	// 				return cb({ status: "failure", message: err });
+	// 			}
+	// 			CacheModule.runJob("PUB", {
+	// 				channel: "queue.removedSong",
+	// 				value: songId
+	// 			});
+	// 			this.log(
+	// 				"SUCCESS",
+	// 				"QUEUE_REMOVE",
+	// 				`User "${session.userId}" successfully removed queuesong "${songId}".`
+	// 			);
+	// 			return cb({
+	// 				status: "success",
+	// 				message: "Successfully updated song."
+	// 			});
+	// 		}
+	// 	);
+	// }),
+	// /**
+	//  * Creates a queuesong
+	//  *
+	//  * @param {object} session - the session object automatically added by the websocket
+	//  * @param {string} songId - the id of the song that gets added
+	//  * @param {Function} cb - gets called with the result
+	//  */
+	// add: isLoginRequired(async function add(session, songId, cb) {
+	// 	const requestedAt = Date.now();
+	// 	const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
+	// 	const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
+	// 	const QueueSongModel = await DBModule.runJob(
+	// 		"GET_MODEL",
+	// 		{
+	// 			modelName: "queueSong"
+	// 		},
+	// 		this
+	// 	);
+	// 	async.waterfall(
+	// 		[
+	// 			next => {
+	// 				QueueSongModel.findOne({ songId }, next);
+	// 			},
+	// 			(song, next) => {
+	// 				if (song) return next("This song is already in the queue.");
+	// 				return songModel.findOne({ songId }, next);
+	// 			},
+	// 			// Get YouTube data from id
+	// 			(song, next) => {
+	// 				if (song) return next("This song has already been added.");
+	// 				// TODO Add err object as first param of callback
+	// 				return YouTubeModule.runJob("GET_SONG", { songId }, this)
+	// 					.then(response => {
+	// 						const { song } = response;
+	// 						song.duration = -1;
+	// 						song.artists = [];
+	// 						song.genres = [];
+	// 						song.skipDuration = 0;
+	// 						song.thumbnail = `${config.get("domain")}/assets/notes.png`;
+	// 						song.explicit = false;
+	// 						song.requestedBy = session.userId;
+	// 						song.requestedAt = requestedAt;
+	// 						next(null, song);
+	// 					})
+	// 					.catch(next);
+	// 			},
+	// 			(newSong, next) => {
+	// 				const song = new QueueSongModel(newSong);
+	// 				song.save({ validateBeforeSave: false }, (err, song) => {
+	// 					if (err) return next(err);
+	// 					return next(null, song);
+	// 				});
+	// 			},
+	// 			(newSong, next) => {
+	// 				userModel.findOne({ _id: session.userId }, (err, user) => {
+	// 					if (err) return next(err, newSong);
+	// 					user.statistics.songsRequested += 1;
+	// 					return user.save(err => {
+	// 						if (err) return next(err, newSong);
+	// 						return next(null, newSong);
+	// 					});
+	// 				});
+	// 			}
+	// 		],
+	// 		async (err, newSong) => {
+	// 			if (err) {
+	// 				err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+	// 				this.log(
+	// 					"ERROR",
+	// 					"QUEUE_ADD",
+	// 					`Adding queuesong "${songId}" failed for user ${session.userId}. "${err}"`
+	// 				);
+	// 				return cb({ status: "failure", message: err });
+	// 			}
+	// 			CacheModule.runJob("PUB", {
+	// 				channel: "queue.newSong",
+	// 				value: newSong._id
+	// 			});
+	// 			this.log("SUCCESS", "QUEUE_ADD", `User "${session.userId}" successfully added queuesong "${songId}".`);
+	// 			return cb({
+	// 				status: "success",
+	// 				message: "Successfully added that song to the queue"
+	// 			});
+	// 		}
+	// 	);
+	// }),
+	// /**
+	//  * Adds a set of songs to the queue
+	//  *
+	//  * @param {object} session - the session object automatically added by the websocket
+	//  * @param {string} url - the url of the the YouTube playlist
+	//  * @param {boolean} musicOnly - whether to only get music from the playlist
+	//  * @param {Function} cb - gets called with the result
+	//  */
+	// addSetToQueue: isLoginRequired(function addSetToQueue(session, url, musicOnly, cb) {
+	// 	async.waterfall(
+	// 		[
+	// 			next => {
+	// 				YouTubeModule.runJob(
+	// 					"GET_PLAYLIST",
+	// 					{
+	// 						url,
+	// 						musicOnly
+	// 					},
+	// 					this
+	// 				)
+	// 					.then(res => {
+	// 						next(null, res.songs);
+	// 					})
+	// 					.catch(next);
+	// 			},
+	// 			(songIds, next) => {
+	// 				let successful = 0;
+	// 				let failed = 0;
+	// 				let alreadyInQueue = 0;
+	// 				let alreadyAdded = 0;
+	// 				if (songIds.length === 0) next();
+	// 				async.eachLimit(
+	// 					songIds,
+	// 					1,
+	// 					(songId, next) => {
+	// 						WSModule.runJob(
+	// 							"RUN_ACTION2",
+	// 							{
+	// 								session,
+	// 								namespace: "queueSongs",
+	// 								action: "add",
+	// 								args: [songId]
+	// 							},
+	// 							this
+	// 						)
+	// 							.then(res => {
+	// 								if (res.status === "success") successful += 1;
+	// 								else failed += 1;
+	// 								if (res.message === "This song is already in the queue.") alreadyInQueue += 1;
+	// 								if (res.message === "This song has already been added.") alreadyAdded += 1;
+	// 							})
+	// 							.catch(() => {
+	// 								failed += 1;
+	// 							})
+	// 							.finally(() => {
+	// 								next();
+	// 							});
+	// 					},
+	// 					() => {
+	// 						next(null, { successful, failed, alreadyInQueue, alreadyAdded });
+	// 					}
+	// 				);
+	// 			}
+	// 		],
+	// 		async (err, response) => {
+	// 			if (err) {
+	// 				err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+	// 				this.log(
+	// 					"ERROR",
+	// 					"QUEUE_IMPORT",
+	// 					`Importing a YouTube playlist to the queue failed for user "${session.userId}". "${err}"`
+	// 				);
+	// 				return cb({ status: "failure", message: err });
+	// 			}
+	// 			this.log(
+	// 				"SUCCESS",
+	// 				"QUEUE_IMPORT",
+	// 				`Successfully imported a YouTube playlist to the queue for user "${session.userId}".`
+	// 			);
+	// 			return cb({
+	// 				status: "success",
+	// 				message: `Playlist is done importing. ${response.successful} were added succesfully, ${response.failed} failed (${response.alreadyInQueue} were already in queue, ${response.alreadyAdded} were already added)`
+	// 			});
+	// 		}
+	// 	);
+	// })
 };

+ 369 - 57
backend/logic/actions/songs.js

@@ -1,4 +1,5 @@
 import async from "async";
+import config from "config";
 
 import { isAdminRequired, isLoginRequired } from "./hooks";
 
@@ -10,39 +11,81 @@ const WSModule = moduleManager.modules.ws;
 const CacheModule = moduleManager.modules.cache;
 const SongsModule = moduleManager.modules.songs;
 const ActivitiesModule = moduleManager.modules.activities;
+const YouTubeModule = moduleManager.modules.youtube;
 const PlaylistsModule = moduleManager.modules.playlists;
 
 CacheModule.runJob("SUB", {
-	channel: "song.removed",
+	channel: "song.newUnverifiedSong",
+	cb: async songId => {
+		const songModel = await DBModule.runJob("GET_MODEL", {
+			modelName: "song"
+		});
+		songModel.findOne({ _id: songId }, (err, song) => {
+			WSModule.runJob("EMIT_TO_ROOM", {
+				room: "admin.unverifiedSongs",
+				args: ["event:admin.unverifiedSong.added", song]
+			});
+		});
+	}
+});
+
+CacheModule.runJob("SUB", {
+	channel: "song.removedUnverifiedSong",
 	cb: songId => {
 		WSModule.runJob("EMIT_TO_ROOM", {
-			room: "admin.songs",
-			args: ["event:admin.song.removed", songId]
+			room: "admin.unverifiedSongs",
+			args: ["event:admin.unverifiedSong.removed", songId]
+		});
+	}
+});
+
+CacheModule.runJob("SUB", {
+	channel: "song.updateUnverifiedSong",
+	cb: async songId => {
+		const songModel = await DBModule.runJob("GET_MODEL", {
+			modelName: "song"
+		});
+
+		songModel.findOne({ _id: songId }, (err, song) => {
+			WSModule.runJob("EMIT_TO_ROOM", {
+				room: "admin.unverifiedSongs",
+				args: ["event:admin.unverifiedSong.updated", song]
+			});
 		});
 	}
 });
 
 CacheModule.runJob("SUB", {
-	channel: "song.added",
+	channel: "song.newVerifiedSong",
 	cb: async songId => {
 		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
 		songModel.findOne({ songId }, (err, song) => {
 			WSModule.runJob("EMIT_TO_ROOM", {
 				room: "admin.songs",
-				args: ["event:admin.song.added", song]
+				args: ["event:admin.verifiedSong.added", song]
 			});
 		});
 	}
 });
 
 CacheModule.runJob("SUB", {
-	channel: "song.updated",
+	channel: "song.removedVerifiedSong",
+	cb: songId => {
+		WSModule.runJob("EMIT_TO_ROOM", {
+			room: "admin.songs",
+			args: ["event:admin.verifiedSong.removed", songId]
+		});
+	}
+});
+
+CacheModule.runJob("SUB", {
+	channel: "song.updatedVerifiedSong",
 	cb: async songId => {
 		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
 		songModel.findOne({ songId }, (err, song) => {
 			WSModule.runJob("EMIT_TO_ROOM", {
 				room: "admin.songs",
-				args: ["event:admin.song.updated", song]
+				args: ["event:admin.verifiedSong.updated", song]
 			});
 		});
 	}
@@ -160,21 +203,29 @@ export default {
 	 * @param {object} session - the session object automatically added by the websocket
 	 * @param cb
 	 */
-	length: isAdminRequired(async function length(session, cb) {
+	length: isAdminRequired(async function length(session, verified, cb) {
 		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
 		async.waterfall(
 			[
 				next => {
-					songModel.countDocuments({}, next);
+					songModel.countDocuments({ verified }, next);
 				}
 			],
 			async (err, count) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log("ERROR", "SONGS_LENGTH", `Failed to get length from songs. "${err}"`);
+					this.log(
+						"ERROR",
+						"SONGS_LENGTH",
+						`Failed to get length from songs that are ${verified ? "verified" : "not verified"}. "${err}"`
+					);
 					return cb({ status: "failure", message: err });
 				}
-				this.log("SUCCESS", "SONGS_LENGTH", `Got length from songs successfully.`);
+				this.log(
+					"SUCCESS",
+					"SONGS_LENGTH",
+					`Got length from songs that are ${verified ? "verified" : "not verified"} successfully.`
+				);
 				return cb(count);
 			}
 		);
@@ -187,13 +238,13 @@ export default {
 	 * @param set - the set number to return
 	 * @param cb
 	 */
-	getSet: isAdminRequired(async function getSet(session, set, cb) {
+	getSet: isAdminRequired(async function getSet(session, set, verified, cb) {
 		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
 		async.waterfall(
 			[
 				next => {
 					songModel
-						.find({})
+						.find({ verified })
 						.skip(15 * (set - 1))
 						.limit(15)
 						.exec(next);
@@ -202,10 +253,18 @@ export default {
 			async (err, songs) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log("ERROR", "SONGS_GET_SET", `Failed to get set from songs. "${err}"`);
+					this.log(
+						"ERROR",
+						"SONGS_GET_SET",
+						`Failed to get set from songs that are ${verified ? "verified" : "not verified"}. "${err}"`
+					);
 					return cb({ status: "failure", message: err });
 				}
-				this.log("SUCCESS", "SONGS_GET_SET", `Got set from songs successfully.`);
+				this.log(
+					"SUCCESS",
+					"SONGS_GET_SET",
+					`Got set from songs that are ${verified ? "verified" : "not verified"} successfully.`
+				);
 				return cb(songs);
 			}
 		);
@@ -377,10 +436,17 @@ export default {
 
 				this.log("SUCCESS", "SONGS_UPDATE", `Successfully updated song "${songId}".`);
 
-				CacheModule.runJob("PUB", {
-					channel: "song.updated",
-					value: song.songId
-				});
+				if (song.verified) {
+					CacheModule.runJob("PUB", {
+						channel: "song.updatedVerifiedSong",
+						value: song.songId
+					});
+				} else {
+					CacheModule.runJob("PUB", {
+						channel: "song.updatedUnverifiedSong",
+						value: song.songId
+					});
+				}
 
 				return cb({
 					status: "success",
@@ -439,7 +505,17 @@ export default {
 
 				this.log("SUCCESS", "SONGS_REMOVE", `Successfully remove song "${songId}".`);
 
-				CacheModule.runJob("PUB", { channel: "song.removed", value: songId });
+				if (song.verified) {
+					CacheModule.runJob("PUB", {
+						channel: "song.removedVerifiedSong",
+						value: songId
+					});
+				} else {
+					CacheModule.runJob("PUB", {
+						channel: "song.removedUnverifiedSong",
+						value: songId
+					});
+				}
 
 				return cb({
 					status: "success",
@@ -450,80 +526,316 @@ export default {
 	}),
 
 	/**
-	 * Adds a song
+	 * Requests a song
+	 *
+	 * @param {object} session - the session object automatically added by the websocket
+	 * @param {string} songId - the id of the song that gets requested
+	 * @param {Function} cb - gets called with the result
+	 */
+	request: isLoginRequired(async function add(session, songId, cb) {
+		const requestedAt = Date.now();
+		const SongModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
+		const UserModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
+
+		async.waterfall(
+			[
+				next => {
+					SongModel.findOne({ songId }, next);
+				},
+
+				// Get YouTube data from id
+				(song, next) => {
+					if (song) return next("This song is already in the database.");
+					// TODO Add err object as first param of callback
+					return YouTubeModule.runJob("GET_SONG", { songId }, this)
+						.then(response => {
+							const { song } = response;
+							song.duration = -1;
+							song.artists = [];
+							song.genres = [];
+							song.skipDuration = 0;
+							song.thumbnail = `${config.get("domain")}/assets/notes.png`;
+							song.explicit = false;
+							song.requestedBy = session.userId;
+							song.requestedAt = requestedAt;
+							song.verified = false;
+							next(null, song);
+						})
+						.catch(next);
+				},
+				(newSong, next) => {
+					const song = new SongModel(newSong);
+					song.save({ validateBeforeSave: false }, err => {
+						if (err) return next(err, song);
+						return next(null, song);
+					});
+				},
+				(song, next) => {
+					UserModel.findOne({ _id: session.userId }, (err, user) => {
+						if (err) return next(err);
+
+						user.statistics.songsRequested += 1;
+
+						return user.save(err => {
+							if (err) return next(err);
+							return next(null, song);
+						});
+					});
+				}
+			],
+			async (err, song) => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+					this.log(
+						"ERROR",
+						"SONGS_REQUEST",
+						`Requesting song "${songId}" failed for user ${session.userId}. "${err}"`
+					);
+					return cb({ status: "failure", message: err });
+				}
+				CacheModule.runJob("PUB", {
+					channel: "song.newUnverifiedSong",
+					value: song._id
+				});
+				this.log(
+					"SUCCESS",
+					"SONGS_REQUEST",
+					`User "${session.userId}" successfully requested song "${songId}".`
+				);
+				return cb({
+					status: "success",
+					message: "Successfully requested that song"
+				});
+			}
+		);
+	}),
+
+	/**
+	 * Verifies a song
 	 *
 	 * @param session
 	 * @param song - the song object
 	 * @param cb
 	 */
-	add: isAdminRequired(async function add(session, song, cb) {
+	verify: isAdminRequired(async function add(session, songId, cb) {
 		const SongModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
 		async.waterfall(
 			[
 				next => {
-					SongModel.findOne({ songId: song.songId }, next);
+					SongModel.findOne({ songId }, next);
 				},
 
-				(existingSong, next) => {
-					if (existingSong) return next("Song is already in rotation.");
-					return next();
+				(song, next) => {
+					if (!song) return next("This song is not in the database.");
+					return next(null, song);
 				},
 
-				next => {
-					const newSong = new SongModel(song);
-					newSong.acceptedBy = session.userId;
-					newSong.acceptedAt = Date.now();
-					newSong.save(next);
+				(song, next) => {
+					song.acceptedBy = session.userId;
+					song.acceptedAt = Date.now();
+					song.verified = true;
+					song.save(err => {
+						next(err, song);
+					});
 				},
 
-				(res, next) => {
-					this.module
-						.runJob(
-							"RUN_ACTION2",
-							{
-								session,
-								namespace: "queueSongs",
-								action: "remove",
-								args: [song._id]
-							},
-							this
-						)
-						.finally(() => {
-							song.genres.forEach(genre => {
-								PlaylistsModule.runJob("AUTOFILL_GENRE_PLAYLIST", { genre })
-									.then(() => {})
-									.catch(() => {});
-							});
+				(song, next) => {
+					song.genres.forEach(genre => {
+						PlaylistsModule.runJob("AUTOFILL_GENRE_PLAYLIST", { genre })
+							.then(() => {})
+							.catch(() => {});
+					});
 
-							next();
-						});
+					next(null, song);
 				}
 			],
-			async err => {
+			async (err, song) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 
-					this.log("ERROR", "SONGS_ADD", `User "${session.userId}" failed to add song. "${err}"`);
+					this.log("ERROR", "SONGS_VERIFY", `User "${session.userId}" failed to verify song. "${err}"`);
 
 					return cb({ status: "failure", message: err });
 				}
 
-				this.log("SUCCESS", "SONGS_ADD", `User "${session.userId}" successfully added song "${song.songId}".`);
+				this.log("SUCCESS", "SONGS_VERIFY", `User "${session.userId}" successfully verified song "${songId}".`);
 
 				CacheModule.runJob("PUB", {
-					channel: "song.added",
-					value: song.songId
+					channel: "song.newVerifiedSong",
+					value: song._id
 				});
 
 				return cb({
 					status: "success",
-					message: "Song has been moved from the queue successfully."
+					message: "Song has been verified successfully."
 				});
 			}
 		);
 		// TODO Check if video is in queue and Add the song to the appropriate stations
 	}),
 
+	/**
+	 * Requests a set of songs
+	 *
+	 * @param {object} session - the session object automatically added by the websocket
+	 * @param {string} url - the url of the the YouTube playlist
+	 * @param {boolean} musicOnly - whether to only get music from the playlist
+	 * @param {Function} cb - gets called with the result
+	 */
+	requestSet: isLoginRequired(function requestSet(session, url, musicOnly, cb) {
+		async.waterfall(
+			[
+				next => {
+					YouTubeModule.runJob(
+						"GET_PLAYLIST",
+						{
+							url,
+							musicOnly
+						},
+						this
+					)
+						.then(res => {
+							next(null, res.songs);
+						})
+						.catch(next);
+				},
+				(songIds, next) => {
+					let successful = 0;
+					let failed = 0;
+					let alreadyInDatabase = 0;
+
+					if (songIds.length === 0) next();
+
+					async.eachLimit(
+						songIds,
+						1,
+						(songId, next) => {
+							WSModule.runJob(
+								"RUN_ACTION2",
+								{
+									session,
+									namespace: "songs",
+									action: "request",
+									args: [songId]
+								},
+								this
+							)
+								.then(res => {
+									if (res.status === "success") successful += 1;
+									else failed += 1;
+									if (res.message === "This song is already in the database.") alreadyInDatabase += 1;
+								})
+								.catch(() => {
+									failed += 1;
+								})
+								.finally(() => {
+									next();
+								});
+						},
+						() => {
+							next(null, { successful, failed, alreadyInDatabase });
+						}
+					);
+				}
+			],
+			async (err, response) => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+					this.log(
+						"ERROR",
+						"REQUEST_SET",
+						`Importing a YouTube playlist to be requested failed for user "${session.userId}". "${err}"`
+					);
+					return cb({ status: "failure", message: err });
+				}
+				this.log(
+					"SUCCESS",
+					"REQUEST_SET",
+					`Successfully imported a YouTube playlist to be requested for user "${session.userId}".`
+				);
+				return cb({
+					status: "success",
+					message: `Playlist is done importing. ${response.successful} were added succesfully, ${response.failed} failed (${response.alreadyInDatabase} were already in database)`
+				});
+			}
+		);
+	}),
+
+	// /**
+	//  * Adds a song
+	//  *
+	//  * @param session
+	//  * @param song - the song object
+	//  * @param cb
+	//  */
+	// add: isAdminRequired(async function add(session, song, cb) {
+	// 	const SongModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
+	// 	async.waterfall(
+	// 		[
+	// 			next => {
+	// 				SongModel.findOne({ songId: song.songId }, next);
+	// 			},
+
+	// 			(existingSong, next) => {
+	// 				if (existingSong) return next("Song is already in rotation.");
+	// 				return next();
+	// 			},
+
+	// 			next => {
+	// 				const newSong = new SongModel(song);
+	// 				newSong.acceptedBy = session.userId;
+	// 				newSong.acceptedAt = Date.now();
+	// 				newSong.save(next);
+	// 			},
+
+	// 			(res, next) => {
+	// 				this.module
+	// 					.runJob(
+	// 						"RUN_ACTION2",
+	// 						{
+	// 							session,
+	// 							namespace: "queueSongs",
+	// 							action: "remove",
+	// 							args: [song._id]
+	// 						},
+	// 						this
+	// 					)
+	// 					.finally(() => {
+	// 						song.genres.forEach(genre => {
+	// 							PlaylistsModule.runJob("AUTOFILL_GENRE_PLAYLIST", { genre })
+	// 								.then(() => {})
+	// 								.catch(() => {});
+	// 						});
+
+	// 						next();
+	// 					});
+	// 			}
+	// 		],
+	// 		async err => {
+	// 			if (err) {
+	// 				err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+
+	// 				this.log("ERROR", "SONGS_ADD", `User "${session.userId}" failed to add song. "${err}"`);
+
+	// 				return cb({ status: "failure", message: err });
+	// 			}
+
+	// 			this.log("SUCCESS", "SONGS_ADD", `User "${session.userId}" successfully added song "${song.songId}".`);
+
+	// 			CacheModule.runJob("PUB", {
+	// 				channel: "song.added",
+	// 				value: song.songId
+	// 			});
+
+	// 			return cb({
+	// 				status: "success",
+	// 				message: "Song has been moved from the queue successfully."
+	// 			});
+	// 		}
+	// 	);
+	// 	// TODO Check if video is in queue and Add the song to the appropriate stations
+	// }),
+
 	/**
 	 * Likes a song
 	 *

+ 3 - 2
backend/logic/db/schemas/song.js

@@ -11,8 +11,9 @@ export default {
 	explicit: { type: Boolean, default: false, required: true },
 	requestedBy: { type: String, required: true },
 	requestedAt: { type: Date, required: true },
-	acceptedBy: { type: String, required: true },
-	acceptedAt: { type: Date, default: Date.now, required: true },
+	acceptedBy: { type: String, required: true }, // TODO Should be verifiedBy
+	acceptedAt: { type: Date, default: Date.now, required: true }, // TODO Should be verifiedAt
 	discogs: { type: Object },
+	verified: false,
 	documentVersion: { type: Number, default: 1, required: true }
 };

+ 2 - 2
backend/logic/songs.js

@@ -318,7 +318,7 @@ class _SongsModule extends CoreClass {
 			async.waterfall(
 				[
 					next => {
-						SongsModule.songModel.find({}, { genres: 1, _id: false }, next);
+						SongsModule.songModel.find({ verified: true }, { genres: 1, _id: false }, next);
 					},
 
 					(songs, next) => {
@@ -356,7 +356,7 @@ class _SongsModule extends CoreClass {
 				[
 					next => {
 						SongsModule.songModel.find(
-							{ genres: { $regex: new RegExp(`^${payload.genre.toLowerCase()}$`, "i") } },
+							{ verified: true, genres: { $regex: new RegExp(`^${payload.genre.toLowerCase()}$`, "i") } },
 							next
 						);
 					}

+ 2 - 2
frontend/src/components/modals/AddSongToQueue.vue

@@ -280,7 +280,7 @@ export default {
 					}
 				);
 			} else {
-				this.socket.dispatch("queueSongs.add", songId, data => {
+				this.socket.dispatch("songs.request", songId, data => {
 					if (data.status !== "success")
 						new Toast({
 							content: `Error: ${data.message}`,
@@ -319,7 +319,7 @@ export default {
 			}, 750);
 
 			return this.socket.dispatch(
-				"queueSongs.addSetToQueue",
+				"songs.requestSet",
 				this.search.playlist.query,
 				this.search.playlist.isImportingOnlyMusic,
 				res => {

+ 137 - 154
frontend/src/components/modals/EditSong.vue

@@ -519,7 +519,7 @@ export default {
 	components: { Modal, FloatingBox, SaveButton },
 	props: {
 		songId: { type: String, default: null },
-		songType: { type: String, default: null },
+		// songType: { type: String, default: null },
 		sector: { type: String, default: "admin" }
 	},
 	data() {
@@ -614,151 +614,139 @@ export default {
 
 		this.useHTTPS = await lofig.get("cookie.secure");
 
-		this.socket.dispatch(
-			`${this.songType}.getSongFromMusareId`,
-			this.songId,
-			res => {
-				if (res.status === "success") {
-					const { song } = res.data;
-					// this.song = { ...song };
-					// if (this.song.discogs === undefined)
-					// 	this.song.discogs = null;
-					this.editSong(song);
-
-					this.songDataLoaded = true;
-
-					// this.edit(res.data.song);
-
-					this.discogsQuery = this.song.title;
-
-					this.interval = setInterval(() => {
-						if (
-							this.song.duration !== -1 &&
-							this.video.paused === false &&
-							this.playerReady &&
-							this.video.player.getCurrentTime() -
-								this.song.skipDuration >
-								this.song.duration
-						) {
-							this.video.paused = false;
-							this.video.player.stopVideo();
-							this.drawCanvas();
-						}
-						if (this.playerReady) {
-							this.youtubeVideoCurrentTime = this.video.player
-								.getCurrentTime()
+		this.socket.dispatch(`songs.getSongFromMusareId`, this.songId, res => {
+			if (res.status === "success") {
+				const { song } = res.data;
+				// this.song = { ...song };
+				// if (this.song.discogs === undefined)
+				// 	this.song.discogs = null;
+				this.editSong(song);
+
+				this.songDataLoaded = true;
+
+				// this.edit(res.data.song);
+
+				this.discogsQuery = this.song.title;
+
+				this.interval = setInterval(() => {
+					if (
+						this.song.duration !== -1 &&
+						this.video.paused === false &&
+						this.playerReady &&
+						this.video.player.getCurrentTime() -
+							this.song.skipDuration >
+							this.song.duration
+					) {
+						this.video.paused = false;
+						this.video.player.stopVideo();
+						this.drawCanvas();
+					}
+					if (this.playerReady) {
+						this.youtubeVideoCurrentTime = this.video.player
+							.getCurrentTime()
+							.toFixed(3);
+					}
+
+					if (this.video.paused === false) this.drawCanvas();
+				}, 200);
+
+				this.video.player = new window.YT.Player("editSongPlayer", {
+					height: 298,
+					width: 530,
+					videoId: this.song.songId,
+					host: "https://www.youtube-nocookie.com",
+					playerVars: {
+						controls: 0,
+						iv_load_policy: 3,
+						rel: 0,
+						showinfo: 0,
+						autoplay: 1
+					},
+					startSeconds: this.song.skipDuration,
+					events: {
+						onReady: () => {
+							let volume = parseInt(
+								localStorage.getItem("volume")
+							);
+							volume = typeof volume === "number" ? volume : 20;
+							console.log(`Seekto: ${this.song.skipDuration}`);
+							this.video.player.seekTo(this.song.skipDuration);
+							this.video.player.setVolume(volume);
+							if (volume > 0) this.video.player.unMute();
+							this.youtubeVideoDuration = this.video.player
+								.getDuration()
 								.toFixed(3);
-						}
+							this.youtubeVideoNote = "(~)";
+							this.playerReady = true;
 
-						if (this.video.paused === false) this.drawCanvas();
-					}, 200);
-
-					this.video.player = new window.YT.Player("editSongPlayer", {
-						height: 298,
-						width: 530,
-						videoId: this.song.songId,
-						host: "https://www.youtube-nocookie.com",
-						playerVars: {
-							controls: 0,
-							iv_load_policy: 3,
-							rel: 0,
-							showinfo: 0,
-							autoplay: 1
+							this.drawCanvas();
 						},
-						startSeconds: this.song.skipDuration,
-						events: {
-							onReady: () => {
-								let volume = parseInt(
-									localStorage.getItem("volume")
-								);
-								volume =
-									typeof volume === "number" ? volume : 20;
-								console.log(
-									`Seekto: ${this.song.skipDuration}`
-								);
-								this.video.player.seekTo(
-									this.song.skipDuration
+						onStateChange: event => {
+							this.drawCanvas();
+
+							if (event.data === 1) {
+								if (!this.video.autoPlayed) {
+									this.video.autoPlayed = true;
+									return this.video.player.stopVideo();
+								}
+
+								this.video.paused = false;
+								let youtubeDuration = this.video.player.getDuration();
+								this.youtubeVideoDuration = youtubeDuration.toFixed(
+									3
 								);
-								this.video.player.setVolume(volume);
-								if (volume > 0) this.video.player.unMute();
-								this.youtubeVideoDuration = this.video.player
-									.getDuration()
-									.toFixed(3);
-								this.youtubeVideoNote = "(~)";
-								this.playerReady = true;
-
-								this.drawCanvas();
-							},
-							onStateChange: event => {
-								this.drawCanvas();
-
-								if (event.data === 1) {
-									if (!this.video.autoPlayed) {
-										this.video.autoPlayed = true;
-										return this.video.player.stopVideo();
-									}
-
-									this.video.paused = false;
-									let youtubeDuration = this.video.player.getDuration();
-									this.youtubeVideoDuration = youtubeDuration.toFixed(
-										3
-									);
-									this.youtubeVideoNote = "";
-
-									if (this.song.duration === -1)
-										this.song.duration = youtubeDuration;
-
-									youtubeDuration -= this.song.skipDuration;
-									if (
-										this.song.duration >
-										youtubeDuration + 1
-									) {
-										this.video.player.stopVideo();
-										this.video.paused = true;
-										return new Toast({
-											content:
-												"Video can't play. Specified duration is bigger than the YouTube song duration.",
-											timeout: 4000
-										});
-									}
-									if (this.song.duration <= 0) {
-										this.video.player.stopVideo();
-										this.video.paused = true;
-										return new Toast({
-											content:
-												"Video can't play. Specified duration has to be more than 0 seconds.",
-											timeout: 4000
-										});
-									}
-
-									if (
-										this.video.player.getCurrentTime() <
-										this.song.skipDuration
-									) {
-										return this.video.player.seekTo(
-											this.song.skipDuration
-										);
-									}
-								} else if (event.data === 2) {
+								this.youtubeVideoNote = "";
+
+								if (this.song.duration === -1)
+									this.song.duration = youtubeDuration;
+
+								youtubeDuration -= this.song.skipDuration;
+								if (this.song.duration > youtubeDuration + 1) {
+									this.video.player.stopVideo();
 									this.video.paused = true;
+									return new Toast({
+										content:
+											"Video can't play. Specified duration is bigger than the YouTube song duration.",
+										timeout: 4000
+									});
+								}
+								if (this.song.duration <= 0) {
+									this.video.player.stopVideo();
+									this.video.paused = true;
+									return new Toast({
+										content:
+											"Video can't play. Specified duration has to be more than 0 seconds.",
+										timeout: 4000
+									});
 								}
 
-								return false;
+								if (
+									this.video.player.getCurrentTime() <
+									this.song.skipDuration
+								) {
+									return this.video.player.seekTo(
+										this.song.skipDuration
+									);
+								}
+							} else if (event.data === 2) {
+								this.video.paused = true;
 							}
+
+							return false;
 						}
-					});
-				} else {
-					new Toast({
-						content: "Song with that ID not found",
-						timeout: 3000
-					});
-					this.closeModal({
-						sector: this.sector,
-						modal: "editSong"
-					});
-				}
+					}
+				});
+			} else {
+				new Toast({
+					content: "Song with that ID not found",
+					timeout: 3000
+				});
+				this.closeModal({
+					sector: this.sector,
+					modal: "editSong"
+				});
 			}
-		);
+		});
 
 		let volume = parseFloat(localStorage.getItem("volume"));
 		volume =
@@ -1083,24 +1071,19 @@ export default {
 
 			saveButtonRef.status = "disabled";
 
-			return this.socket.dispatch(
-				`${this.songType}.update`,
-				song._id,
-				song,
-				res => {
-					new Toast({ content: res.message, timeout: 4000 });
-
-					if (res.status === "success")
-						saveButtonRef.handleSuccessfulSave();
-					else saveButtonRef.handleFailedSave();
-
-					if (close)
-						this.closeModal({
-							sector: this.sector,
-							modal: "editSong"
-						});
-				}
-			);
+			return this.socket.dispatch(`songs.update`, song._id, song, res => {
+				new Toast({ content: res.message, timeout: 4000 });
+
+				if (res.status === "success")
+					saveButtonRef.handleSuccessfulSave();
+				else saveButtonRef.handleFailedSave();
+
+				if (close)
+					this.closeModal({
+						sector: this.sector,
+						modal: "editSong"
+					});
+			});
 		},
 		toggleAPIResult(index) {
 			const apiResult = this.discogs.apiResults[index];

+ 27 - 21
frontend/src/pages/Admin/index.vue

@@ -4,21 +4,27 @@
 		<div class="tabs is-centered">
 			<ul>
 				<li
-					:class="{ 'is-active': currentTab == 'queueSongs' }"
-					@click="showTab('queueSongs')"
+					:class="{ 'is-active': currentTab == 'unverifiedSongs' }"
+					@click="showTab('unverifiedSongs')"
 				>
-					<router-link class="tab queueSongs" to="/admin/queuesongs">
-						<i class="material-icons">queue_music</i>
-						<span>&nbsp;Queue Songs</span>
+					<router-link
+						class="tab unverifiedSongs"
+						to="/admin/unverifiedSongs"
+					>
+						<i class="material-icons">music_note</i>
+						<span>&nbsp;Unverified Songs</span>
 					</router-link>
 				</li>
 				<li
-					:class="{ 'is-active': currentTab == 'songs' }"
-					@click="showTab('songs')"
+					:class="{ 'is-active': currentTab == 'verifiedSongs' }"
+					@click="showTab('verifiedSongs')"
 				>
-					<router-link class="tab songs" to="/admin/songs">
+					<router-link
+						class="tab verifiedSongs"
+						to="/admin/verifiedSongs"
+					>
 						<i class="material-icons">music_note</i>
-						<span>&nbsp;Songs</span>
+						<span>&nbsp;Verified Songs</span>
 					</router-link>
 				</li>
 				<li
@@ -102,8 +108,8 @@
 			</ul>
 		</div>
 
-		<queue-songs v-if="currentTab == 'queueSongs'" />
-		<songs v-if="currentTab == 'songs'" />
+		<unverified-songs v-if="currentTab == 'unverifiedSongs'" />
+		<verified-songs v-if="currentTab == 'verifiedSongs'" />
 		<stations v-if="currentTab == 'stations'" />
 		<playlists v-if="currentTab == 'playlists'" />
 		<reports v-if="currentTab == 'reports'" />
@@ -121,8 +127,8 @@ import MainHeader from "../../components/layout/MainHeader.vue";
 export default {
 	components: {
 		MainHeader,
-		QueueSongs: () => import("./tabs/QueueSongs.vue"),
-		Songs: () => import("./tabs/Songs.vue"),
+		UnverifiedSongs: () => import("./tabs/UnverifiedSongs.vue"),
+		VerifiedSongs: () => import("./tabs/VerifiedSongs.vue"),
 		Stations: () => import("./tabs/Stations.vue"),
 		Playlists: () => import("./tabs/Playlists.vue"),
 		Reports: () => import("./tabs/Reports.vue"),
@@ -134,7 +140,7 @@ export default {
 	},
 	data() {
 		return {
-			currentTab: "queueSongs"
+			currentTab: "unverifiedSongs"
 		};
 	},
 	watch: {
@@ -148,11 +154,11 @@ export default {
 	methods: {
 		changeTab(path) {
 			switch (path) {
-				case "/admin/queuesongs":
-					this.currentTab = "queueSongs";
+				case "/admin/unverifiedsongs":
+					this.currentTab = "unverifiedSongs";
 					break;
-				case "/admin/songs":
-					this.currentTab = "songs";
+				case "/admin/verifiedsongs":
+					this.currentTab = "verifiedSongs";
 					break;
 				case "/admin/stations":
 					this.currentTab = "stations";
@@ -179,7 +185,7 @@ export default {
 					this.currentTab = "punishments";
 					break;
 				default:
-					this.currentTab = "queueSongs";
+					this.currentTab = "verifiedSongs";
 			}
 		},
 		showTab(tab) {
@@ -209,11 +215,11 @@ export default {
 	padding-top: 10px;
 	margin-top: -10px;
 	background-color: var(--white);
-	.queueSongs {
+	.unverifiedSongs {
 		color: var(--teal);
 		border-color: var(--teal);
 	}
-	.songs {
+	.verifiedSongs {
 		color: var(--primary-color);
 		border-color: var(--primary-color);
 	}

+ 18 - 16
frontend/src/pages/Admin/tabs/QueueSongs.vue → frontend/src/pages/Admin/tabs/UnverifiedSongs.vue

@@ -1,6 +1,6 @@
 <template>
 	<div @scroll="handleScroll">
-		<metadata title="Admin | Queue songs" />
+		<metadata title="Admin | Unverified songs" />
 		<div class="container">
 			<p>
 				<span>Sets loaded: {{ setsLoaded }} / {{ maxSets }}</span>
@@ -109,11 +109,7 @@
 				</tbody>
 			</table>
 		</div>
-		<edit-song
-			v-if="modals.editSong"
-			:song-id="editingSongId"
-			song-type="queueSongs"
-		/>
+		<edit-song v-if="modals.editSong" :song-id="editingSongId" />
 		<floating-box
 			id="keyboardShortcutsHelper"
 			ref="keyboardShortcutsHelper"
@@ -121,7 +117,9 @@
 			<template #body>
 				<div>
 					<div>
-						<span class="biggest"><b>Queue songs page</b></span>
+						<span class="biggest"
+							><b>Unverified songs page</b></span
+						>
 						<span
 							><b>Arrow keys up/down</b> - Moves between
 							songs</span
@@ -223,17 +221,17 @@ export default {
 		}
 	},
 	mounted() {
-		this.socket.on("event:admin.queueSong.added", queueSong => {
-			this.songs.push(queueSong);
+		this.socket.on("event:admin.unverifiedSong.added", song => {
+			this.songs.push(song);
 		});
 
-		this.socket.on("event:admin.queueSong.removed", songId => {
+		this.socket.on("event:admin.unverifiedSong.removed", songId => {
 			this.songs = this.songs.filter(song => {
 				return song._id !== songId;
 			});
 		});
 
-		this.socket.on("event:admin.queueSong.updated", updatedSong => {
+		this.socket.on("event:admin.unverifiedSong.updated", updatedSong => {
 			for (let i = 0; i < this.songs.length; i += 1) {
 				const song = this.songs[i];
 				if (song._id === updatedSong._id) {
@@ -257,7 +255,7 @@ export default {
 			this.openModal({ sector: "admin", modal: "editSong" });
 		},
 		add(song) {
-			this.socket.dispatch("songs.add", song, res => {
+			this.socket.dispatch("songs.verify", song.songId, res => {
 				if (res.status === "success")
 					new Toast({ content: res.message, timeout: 2000 });
 				else new Toast({ content: res.message, timeout: 4000 });
@@ -269,7 +267,7 @@ export default {
 				"Are you sure you want to delete this song?"
 			);
 			if (dialogResult !== true) return;
-			this.socket.dispatch("queueSongs.remove", id, res => {
+			this.socket.dispatch("songs.remove", id, res => {
 				if (res.status === "success")
 					new Toast({ content: res.message, timeout: 2000 });
 				else new Toast({ content: res.message, timeout: 4000 });
@@ -280,7 +278,7 @@ export default {
 			if (this.position >= this.maxPosition) return;
 			this.isGettingSet = true;
 
-			this.socket.dispatch("queueSongs.getSet", this.position, data => {
+			this.socket.dispatch("songs.getSet", this.position, false, data => {
 				data.forEach(song => this.songs.push(song));
 
 				this.position += 1;
@@ -305,13 +303,17 @@ export default {
 			if (this.songs.length > 0)
 				this.position = Math.ceil(this.songs.length / 15) + 1;
 
-			this.socket.dispatch("queueSongs.length", length => {
+			this.socket.dispatch("songs.length", false, length => {
 				this.maxPosition = Math.ceil(length / 15) + 1;
 
 				this.getSet();
 			});
 
-			this.socket.dispatch("apis.joinAdminRoom", "queue", () => {});
+			this.socket.dispatch(
+				"apis.joinAdminRoom",
+				"unverifiedSongs",
+				() => {}
+			);
 		},
 		// ...mapActions("admin/songs", ["editSong"]),
 		...mapActions("modals/editSong", ["stopVideo"]),

+ 7 - 5
frontend/src/pages/Admin/tabs/Songs.vue → frontend/src/pages/Admin/tabs/VerifiedSongs.vue

@@ -310,13 +310,15 @@ export default {
 		}
 	},
 	mounted() {
-		this.socket.on("event:admin.song.added", song => this.addSong(song));
+		this.socket.on("event:admin.verifiedSong.added", song =>
+			this.addSong(song)
+		);
 
-		this.socket.on("event:admin.song.removed", songId =>
+		this.socket.on("event:admin.verifiedSong.removed", songId =>
 			this.removeSong(songId)
 		);
 
-		this.socket.on("event:admin.song.updated", updatedSong =>
+		this.socket.on("event:admin.verifiedSong.updated", updatedSong =>
 			this.updateSong(updatedSong)
 		);
 
@@ -362,7 +364,7 @@ export default {
 			if (this.position >= this.maxPosition) return;
 			this.isGettingSet = true;
 
-			this.socket.dispatch("songs.getSet", this.position, data => {
+			this.socket.dispatch("songs.getSet", this.position, true, data => {
 				data.forEach(song => {
 					this.addSong(song);
 				});
@@ -399,7 +401,7 @@ export default {
 			if (this.songs.length > 0)
 				this.position = Math.ceil(this.songs.length / 15) + 1;
 
-			this.socket.dispatch("songs.length", length => {
+			this.socket.dispatch("songs.length", true, length => {
 				this.maxPosition = Math.ceil(length / 15) + 1;
 
 				this.getSet();