Browse Source

fix: Fix eslint errors

Owen Diffey 2 months ago
parent
commit
e64187be0d

+ 582 - 589
backend/logic/actions/playlists.js

@@ -1341,118 +1341,119 @@ export default {
 	 * @param {string} playlistId - the playlist to replace the song in
 	 * @param {Function} cb - gets called with the result
 	 */
-	replaceSongInPlaylist: isLoginRequired(async function replaceSongInPlaylist(
-		session,
-		oldMediaSource,
-		newMediaSource,
-		playlistId,
-		cb
-	) {
-		const playlistModel = await DBModule.runJob("GET_MODEL", { modelName: "playlist" }, this);
-
-		async.waterfall(
-			[
-				next => {
-					PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
-						.then(playlist => {
-							if (!playlist) return next("Playlist not found.");
-							if (playlist.createdBy !== session.userId)
-								return hasPermission("playlists.songs.add", session)
-									.then(() => next(null, playlist))
-									.catch(() => next("Invalid permissions."));
-							return next(null, playlist);
-						})
-						.catch(next);
-				},
+	replaceSongInPlaylist: isLoginRequired(
+		async function replaceSongInPlaylist(session, oldMediaSource, newMediaSource, playlistId, cb) {
+			const playlistModel = await DBModule.runJob("GET_MODEL", { modelName: "playlist" }, this);
 
-				(playlist, next) => {
-					MediaModule.runJob("GET_MEDIA", { mediaSource: newMediaSource }, this)
-						.then(res =>
-							next(null, playlist, {
-								_id: res.song._id,
-								title: res.song.title,
-								thumbnail: res.song.thumbnail,
-								artists: res.song.artists,
-								mediaSource: res.song.mediaSource
+			async.waterfall(
+				[
+					next => {
+						PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
+							.then(playlist => {
+								if (!playlist) return next("Playlist not found.");
+								if (playlist.createdBy !== session.userId)
+									return hasPermission("playlists.songs.add", session)
+										.then(() => next(null, playlist))
+										.catch(() => next("Invalid permissions."));
+								return next(null, playlist);
 							})
-						)
-						.catch(next);
-				},
+							.catch(next);
+					},
 
-				(playlist, song, next) => {
-					if (playlist.type === "user-liked" || playlist.type === "user-disliked") {
-						const oppositeType = playlist.type === "user-liked" ? "user-disliked" : "user-liked";
-						const oppositePlaylistName = oppositeType === "user-liked" ? "Liked Songs" : "Disliked Songs";
-						playlistModel.count(
-							{ type: oppositeType, createdBy: session.userId, "songs.mediaSource": song.mediaSource },
-							(err, results) => {
-								if (err) next(err);
-								else if (results > 0)
-									next(
-										`That song is already in your ${oppositePlaylistName} playlist. A song cannot be in both the Liked Songs playlist and the Disliked Songs playlist at the same time.`
-									);
-								else next(null, song);
-							}
+					(playlist, next) => {
+						MediaModule.runJob("GET_MEDIA", { mediaSource: newMediaSource }, this)
+							.then(res =>
+								next(null, playlist, {
+									_id: res.song._id,
+									title: res.song.title,
+									thumbnail: res.song.thumbnail,
+									artists: res.song.artists,
+									mediaSource: res.song.mediaSource
+								})
+							)
+							.catch(next);
+					},
+
+					(playlist, song, next) => {
+						if (playlist.type === "user-liked" || playlist.type === "user-disliked") {
+							const oppositeType = playlist.type === "user-liked" ? "user-disliked" : "user-liked";
+							const oppositePlaylistName =
+								oppositeType === "user-liked" ? "Liked Songs" : "Disliked Songs";
+							playlistModel.count(
+								{
+									type: oppositeType,
+									createdBy: session.userId,
+									"songs.mediaSource": song.mediaSource
+								},
+								(err, results) => {
+									if (err) next(err);
+									else if (results > 0)
+										next(
+											`That song is already in your ${oppositePlaylistName} playlist. A song cannot be in both the Liked Songs playlist and the Disliked Songs playlist at the same time.`
+										);
+									else next(null, song);
+								}
+							);
+						} else next(null, song);
+					},
+
+					(_song, next) => {
+						PlaylistsModule.runJob(
+							"REPLACE_SONG_IN_PLAYLIST",
+							{ playlistId, oldMediaSource, newMediaSource },
+							this
+						)
+							.then(res => {
+								const { playlist, song } = res;
+								next(null, playlist, song);
+							})
+							.catch(next);
+					}
+				],
+				async (err, playlist, newSong) => {
+					if (err) {
+						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+						this.log(
+							"ERROR",
+							"PLAYLIST_ADD_SONG",
+							`Replacing song "${oldMediaSource}" with "${newMediaSource}" in private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
 						);
-					} else next(null, song);
-				},
+						return cb({ status: "error", message: err });
+					}
 
-				(_song, next) => {
-					PlaylistsModule.runJob(
-						"REPLACE_SONG_IN_PLAYLIST",
-						{ playlistId, oldMediaSource, newMediaSource },
-						this
-					)
-						.then(res => {
-							const { playlist, song } = res;
-							next(null, playlist, song);
-						})
-						.catch(next);
-				}
-			],
-			async (err, playlist, newSong) => {
-				if (err) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
-						"ERROR",
+						"SUCCESS",
 						"PLAYLIST_ADD_SONG",
-						`Replacing song "${oldMediaSource}" with "${newMediaSource}" in private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+						`Successfully replaced song "${oldMediaSource}" with "${newMediaSource}" in private playlist "${playlistId}" for user "${session.userId}".`
 					);
-					return cb({ status: "error", message: err });
-				}
-
-				this.log(
-					"SUCCESS",
-					"PLAYLIST_ADD_SONG",
-					`Successfully replaced song "${oldMediaSource}" with "${newMediaSource}" in private playlist "${playlistId}" for user "${session.userId}".`
-				);
 
-				// NOTE: we may want to publish an activity event here
+					// NOTE: we may want to publish an activity event here
 
-				CacheModule.runJob("PUB", {
-					channel: "playlist.replaceSong",
-					value: {
-						playlistId: playlist._id,
-						song: newSong,
-						oldMediaSource,
-						createdBy: playlist.createdBy,
-						privacy: playlist.privacy
-					}
-				});
+					CacheModule.runJob("PUB", {
+						channel: "playlist.replaceSong",
+						value: {
+							playlistId: playlist._id,
+							song: newSong,
+							oldMediaSource,
+							createdBy: playlist.createdBy,
+							privacy: playlist.privacy
+						}
+					});
 
-				CacheModule.runJob("PUB", {
-					channel: "playlist.updated",
-					value: { playlistId }
-				});
+					CacheModule.runJob("PUB", {
+						channel: "playlist.updated",
+						value: { playlistId }
+					});
 
-				return cb({
-					status: "success",
-					message: "Song has been successfully replaced in the playlist",
-					data: { songs: playlist.songs }
-				});
-			}
-		);
-	}),
+					return cb({
+						status: "success",
+						message: "Song has been successfully replaced in the playlist",
+						data: { songs: playlist.songs }
+					});
+				}
+			);
+		}
+	),
 
 	/**
 	 * Adds songs to a playlist
@@ -1820,207 +1821,204 @@ export default {
 	 * @param {boolean} musicOnly - whether to only add music to the playlist
 	 * @param {Function} cb - gets called with the result
 	 */
-	addYoutubeSetToPlaylist: isLoginRequired(async function addYoutubeSetToPlaylist(
-		session,
-		url,
-		playlistId,
-		musicOnly,
-		cb
-	) {
-		let videosInPlaylistTotal = 0;
-		let songsInPlaylistTotal = 0;
-		let addSongsStats = null;
-
-		const addedSongs = [];
-
-		this.keepLongJob();
-		this.publishProgress({
-			status: "started",
-			title: "Import YouTube playlist",
-			message: "Importing YouTube playlist.",
-			id: this.toString()
-		});
-		await CacheModule.runJob("RPUSH", { key: `longJobs.${session.userId}`, value: this.toString() }, this);
-		await CacheModule.runJob(
-			"PUB",
-			{
-				channel: "longJob.added",
-				value: { jobId: this.toString(), userId: session.userId }
-			},
-			this
-		);
+	addYoutubeSetToPlaylist: isLoginRequired(
+		async function addYoutubeSetToPlaylist(session, url, playlistId, musicOnly, cb) {
+			let videosInPlaylistTotal = 0;
+			let songsInPlaylistTotal = 0;
+			let addSongsStats = null;
 
-		async.waterfall(
-			[
-				next => {
-					DBModule.runJob("GET_MODEL", { modelName: "user" }, this).then(userModel => {
-						userModel.findOne({ _id: session.userId }, (err, user) => {
-							if (user && user.role === "admin") return next(null, true);
-							return next(null, false);
-						});
-					});
-				},
+			const addedSongs = [];
 
-				(isAdmin, next) => {
-					this.publishProgress({ status: "update", message: `Importing YouTube playlist (stage 1)` });
-					const playlistRegex = /[\\?&]list=([^&#]*)/;
-					const channelRegex =
-						/\.[\w]+\/(?:(?:channel\/(UC[0-9A-Za-z_-]{21}[AQgw]))|(?:user\/?([\w-]+))|(?:c\/?([\w-]+))|(?:\/?([\w-]+)))/;
-
-					if (playlistRegex.exec(url) || channelRegex.exec(url))
-						YouTubeModule.runJob(
-							playlistRegex.exec(url) ? "GET_PLAYLIST" : "GET_CHANNEL_VIDEOS",
-							{
-								url,
-								musicOnly,
-								disableSearch: !isAdmin
-							},
-							this
-						)
-							.then(res => {
-								if (res.filteredSongs) {
-									videosInPlaylistTotal = res.songs.length;
-									songsInPlaylistTotal = res.filteredSongs.length;
-								} else {
-									songsInPlaylistTotal = videosInPlaylistTotal = res.songs.length;
-								}
-								next(null, res.songs);
-							})
-							.catch(next);
-					else next("Invalid YouTube URL.");
+			this.keepLongJob();
+			this.publishProgress({
+				status: "started",
+				title: "Import YouTube playlist",
+				message: "Importing YouTube playlist.",
+				id: this.toString()
+			});
+			await CacheModule.runJob("RPUSH", { key: `longJobs.${session.userId}`, value: this.toString() }, this);
+			await CacheModule.runJob(
+				"PUB",
+				{
+					channel: "longJob.added",
+					value: { jobId: this.toString(), userId: session.userId }
 				},
-				(youtubeIds, next) => {
-					this.publishProgress({ status: "update", message: `Importing YouTube playlist (stage 2)` });
-					let successful = 0;
-					let failed = 0;
-					let alreadyInPlaylist = 0;
-					let alreadyInLikedPlaylist = 0;
-					let alreadyInDislikedPlaylist = 0;
+				this
+			);
 
-					if (youtubeIds.length === 0) next();
+			async.waterfall(
+				[
+					next => {
+						DBModule.runJob("GET_MODEL", { modelName: "user" }, this).then(userModel => {
+							userModel.findOne({ _id: session.userId }, (err, user) => {
+								if (user && user.role === "admin") return next(null, true);
+								return next(null, false);
+							});
+						});
+					},
 
-					const mediaSources = youtubeIds.map(youtubeId => `youtube:${youtubeId}`);
+					(isAdmin, next) => {
+						this.publishProgress({ status: "update", message: `Importing YouTube playlist (stage 1)` });
+						const playlistRegex = /[\\?&]list=([^&#]*)/;
+						const channelRegex =
+							/\.[\w]+\/(?:(?:channel\/(UC[0-9A-Za-z_-]{21}[AQgw]))|(?:user\/?([\w-]+))|(?:c\/?([\w-]+))|(?:\/?([\w-]+)))/;
 
-					async.eachLimit(
-						mediaSources,
-						1,
-						(mediaSource, next) => {
-							WSModule.runJob(
-								"RUN_ACTION2",
+						if (playlistRegex.exec(url) || channelRegex.exec(url))
+							YouTubeModule.runJob(
+								playlistRegex.exec(url) ? "GET_PLAYLIST" : "GET_CHANNEL_VIDEOS",
 								{
-									session,
-									namespace: "playlists",
-									action: "addSongToPlaylist",
-									args: [true, mediaSource, playlistId]
+									url,
+									musicOnly,
+									disableSearch: !isAdmin
 								},
 								this
 							)
 								.then(res => {
-									if (res.status === "success") {
-										successful += 1;
-										addedSongs.push(mediaSource);
-									} else failed += 1;
-									if (res.message === "That song is already in the playlist") alreadyInPlaylist += 1;
-									else if (
-										res.message ===
-										"That song is already in your Liked Songs playlist. " +
-											"A song cannot be in both the Liked Songs playlist" +
-											" and the Disliked Songs playlist at the same time."
-									)
-										alreadyInLikedPlaylist += 1;
-									else if (
-										res.message ===
-										"That song is already in your Disliked Songs playlist. " +
-											"A song cannot be in both the Liked Songs playlist " +
-											"and the Disliked Songs playlist at the same time."
-									)
-										alreadyInDislikedPlaylist += 1;
-								})
-								.catch(() => {
-									failed += 1;
+									if (res.filteredSongs) {
+										videosInPlaylistTotal = res.songs.length;
+										songsInPlaylistTotal = res.filteredSongs.length;
+									} else {
+										songsInPlaylistTotal = videosInPlaylistTotal = res.songs.length;
+									}
+									next(null, res.songs);
 								})
-								.finally(() => next());
-						},
-						() => {
-							addSongsStats = {
-								successful,
-								failed,
-								alreadyInPlaylist,
-								alreadyInLikedPlaylist,
-								alreadyInDislikedPlaylist
-							};
-							next(null);
-						}
-					);
-				},
+								.catch(next);
+						else next("Invalid YouTube URL.");
+					},
+					(youtubeIds, next) => {
+						this.publishProgress({ status: "update", message: `Importing YouTube playlist (stage 2)` });
+						let successful = 0;
+						let failed = 0;
+						let alreadyInPlaylist = 0;
+						let alreadyInLikedPlaylist = 0;
+						let alreadyInDislikedPlaylist = 0;
 
-				next => {
-					this.publishProgress({ status: "update", message: `Importing YouTube playlist (stage 3)` });
-					PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
-						.then(playlist => next(null, playlist))
-						.catch(next);
-				},
+						if (youtubeIds.length === 0) next();
+
+						const mediaSources = youtubeIds.map(youtubeId => `youtube:${youtubeId}`);
+
+						async.eachLimit(
+							mediaSources,
+							1,
+							(mediaSource, next) => {
+								WSModule.runJob(
+									"RUN_ACTION2",
+									{
+										session,
+										namespace: "playlists",
+										action: "addSongToPlaylist",
+										args: [true, mediaSource, playlistId]
+									},
+									this
+								)
+									.then(res => {
+										if (res.status === "success") {
+											successful += 1;
+											addedSongs.push(mediaSource);
+										} else failed += 1;
+										if (res.message === "That song is already in the playlist")
+											alreadyInPlaylist += 1;
+										else if (
+											res.message ===
+											"That song is already in your Liked Songs playlist. " +
+												"A song cannot be in both the Liked Songs playlist" +
+												" and the Disliked Songs playlist at the same time."
+										)
+											alreadyInLikedPlaylist += 1;
+										else if (
+											res.message ===
+											"That song is already in your Disliked Songs playlist. " +
+												"A song cannot be in both the Liked Songs playlist " +
+												"and the Disliked Songs playlist at the same time."
+										)
+											alreadyInDislikedPlaylist += 1;
+									})
+									.catch(() => {
+										failed += 1;
+									})
+									.finally(() => next());
+							},
+							() => {
+								addSongsStats = {
+									successful,
+									failed,
+									alreadyInPlaylist,
+									alreadyInLikedPlaylist,
+									alreadyInDislikedPlaylist
+								};
+								next(null);
+							}
+						);
+					},
+
+					next => {
+						this.publishProgress({ status: "update", message: `Importing YouTube playlist (stage 3)` });
+						PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
+							.then(playlist => next(null, playlist))
+							.catch(next);
+					},
+
+					(playlist, next) => {
+						this.publishProgress({ status: "update", message: `Importing YouTube playlist (stage 4)` });
+						if (!playlist) return next("Playlist not found.");
+						if (playlist.createdBy !== session.userId)
+							return hasPermission("playlists.songs.add", session)
+								.then(() => next(null, playlist))
+								.catch(() => next("Invalid permissions."));
+						return next(null, playlist);
+					}
+				],
+				async (err, playlist) => {
+					if (err) {
+						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+						this.log(
+							"ERROR",
+							"PLAYLIST_IMPORT",
+							`Importing a YouTube playlist to private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+						);
+						this.publishProgress({
+							status: "error",
+							message: err
+						});
+						return cb({ status: "error", message: err });
+					}
+
+					if (playlist.privacy === "public")
+						ActivitiesModule.runJob("ADD_ACTIVITY", {
+							userId: session.userId,
+							type: "playlist__import_playlist",
+							payload: {
+								message: `Imported ${addSongsStats.successful} songs to playlist <playlistId>${playlist.displayName}</playlistId>`,
+								playlistId
+							}
+						});
 
-				(playlist, next) => {
-					this.publishProgress({ status: "update", message: `Importing YouTube playlist (stage 4)` });
-					if (!playlist) return next("Playlist not found.");
-					if (playlist.createdBy !== session.userId)
-						return hasPermission("playlists.songs.add", session)
-							.then(() => next(null, playlist))
-							.catch(() => next("Invalid permissions."));
-					return next(null, playlist);
-				}
-			],
-			async (err, playlist) => {
-				if (err) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
-						"ERROR",
+						"SUCCESS",
 						"PLAYLIST_IMPORT",
-						`Importing a YouTube playlist to private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+						`Successfully imported a YouTube playlist to private playlist "${playlistId}" for user "${session.userId}". Videos in playlist: ${videosInPlaylistTotal}, songs in playlist: ${songsInPlaylistTotal}, songs successfully added: ${addSongsStats.successful}, songs failed: ${addSongsStats.failed}, already in playlist: ${addSongsStats.alreadyInPlaylist}, already in liked ${addSongsStats.alreadyInLikedPlaylist}, already in disliked ${addSongsStats.alreadyInDislikedPlaylist}.`
 					);
 					this.publishProgress({
-						status: "error",
-						message: err
+						status: "success",
+						message: `Playlist has been imported. ${addSongsStats.successful} were added successfully, ${addSongsStats.failed} failed (${addSongsStats.alreadyInPlaylist} were already in the playlist)`
 					});
-					return cb({ status: "error", message: err });
-				}
-
-				if (playlist.privacy === "public")
-					ActivitiesModule.runJob("ADD_ACTIVITY", {
-						userId: session.userId,
-						type: "playlist__import_playlist",
-						payload: {
-							message: `Imported ${addSongsStats.successful} songs to playlist <playlistId>${playlist.displayName}</playlistId>`,
-							playlistId
+					return cb({
+						status: "success",
+						message: `Playlist has been imported. ${addSongsStats.successful} were added successfully, ${addSongsStats.failed} failed (${addSongsStats.alreadyInPlaylist} were already in the playlist)`,
+						data: {
+							songs: playlist.songs,
+							stats: {
+								videosInPlaylistTotal,
+								songsInPlaylistTotal,
+								alreadyInLikedPlaylist: addSongsStats.alreadyInLikedPlaylist,
+								alreadyInDislikedPlaylist: addSongsStats.alreadyInDislikedPlaylist
+							}
 						}
 					});
-
-				this.log(
-					"SUCCESS",
-					"PLAYLIST_IMPORT",
-					`Successfully imported a YouTube playlist to private playlist "${playlistId}" for user "${session.userId}". Videos in playlist: ${videosInPlaylistTotal}, songs in playlist: ${songsInPlaylistTotal}, songs successfully added: ${addSongsStats.successful}, songs failed: ${addSongsStats.failed}, already in playlist: ${addSongsStats.alreadyInPlaylist}, already in liked ${addSongsStats.alreadyInLikedPlaylist}, already in disliked ${addSongsStats.alreadyInDislikedPlaylist}.`
-				);
-				this.publishProgress({
-					status: "success",
-					message: `Playlist has been imported. ${addSongsStats.successful} were added successfully, ${addSongsStats.failed} failed (${addSongsStats.alreadyInPlaylist} were already in the playlist)`
-				});
-				return cb({
-					status: "success",
-					message: `Playlist has been imported. ${addSongsStats.successful} were added successfully, ${addSongsStats.failed} failed (${addSongsStats.alreadyInPlaylist} were already in the playlist)`,
-					data: {
-						songs: playlist.songs,
-						stats: {
-							videosInPlaylistTotal,
-							songsInPlaylistTotal,
-							alreadyInLikedPlaylist: addSongsStats.alreadyInLikedPlaylist,
-							alreadyInDislikedPlaylist: addSongsStats.alreadyInDislikedPlaylist
-						}
-					}
-				});
-			}
-		);
-	}),
+				}
+			);
+		}
+	),
 
 	/**
 	 * Adds a set of Soundcloud songs to a private playlist
@@ -2029,193 +2027,191 @@ export default {
 	 * @param {string} playlistId - the id of the playlist we are adding the set of songs to
 	 * @param {Function} cb - gets called with the result
 	 */
-	addSoundcloudSetToPlaylist: isLoginRequired(async function addSoundcloudSetToPlaylist(
-		session,
-		url,
-		playlistId,
-		cb
-	) {
-		if (!config.get("experimental.soundcloud"))
-			return cb({ status: "error", message: "SoundCloud is not enabled" });
+	addSoundcloudSetToPlaylist: isLoginRequired(
+		async function addSoundcloudSetToPlaylist(session, url, playlistId, cb) {
+			if (!config.get("experimental.soundcloud"))
+				return cb({ status: "error", message: "SoundCloud is not enabled" });
 
-		let songsInPlaylistTotal = 0;
-		let addSongsStats = null;
+			let songsInPlaylistTotal = 0;
+			let addSongsStats = null;
 
-		const addedSongs = [];
+			const addedSongs = [];
 
-		this.keepLongJob();
-		this.publishProgress({
-			status: "started",
-			title: "Import SoundCloud playlist",
-			message: "Importing SoundCloud playlist.",
-			id: this.toString()
-		});
-		await CacheModule.runJob("RPUSH", { key: `longJobs.${session.userId}`, value: this.toString() }, this);
-		await CacheModule.runJob(
-			"PUB",
-			{
-				channel: "longJob.added",
-				value: { jobId: this.toString(), userId: session.userId }
-			},
-			this
-		);
+			this.keepLongJob();
+			this.publishProgress({
+				status: "started",
+				title: "Import SoundCloud playlist",
+				message: "Importing SoundCloud playlist.",
+				id: this.toString()
+			});
+			await CacheModule.runJob("RPUSH", { key: `longJobs.${session.userId}`, value: this.toString() }, this);
+			await CacheModule.runJob(
+				"PUB",
+				{
+					channel: "longJob.added",
+					value: { jobId: this.toString(), userId: session.userId }
+				},
+				this
+			);
 
-		return async.waterfall(
-			[
-				next => {
-					DBModule.runJob("GET_MODEL", { modelName: "user" }, this).then(userModel => {
-						userModel.findOne({ _id: session.userId }, (err, user) => {
-							if (user && user.role === "admin") return next(null, true);
-							return next(null, false);
+			return async.waterfall(
+				[
+					next => {
+						DBModule.runJob("GET_MODEL", { modelName: "user" }, this).then(userModel => {
+							userModel.findOne({ _id: session.userId }, (err, user) => {
+								if (user && user.role === "admin") return next(null, true);
+								return next(null, false);
+							});
 						});
-					});
-				},
+					},
 
-				(isAdmin, next) => {
-					this.publishProgress({ status: "update", message: `Importing SoundCloud playlist (stage 1)` });
-					SoundcloudModule.runJob(
-						"GET_PLAYLIST",
-						{
-							url
-						},
-						this
-					)
-						.then(res => {
-							songsInPlaylistTotal = res.songs.length;
-							const mediaSources = res.songs.map(song => `soundcloud:${song}`);
-							next(null, mediaSources);
-						})
-						.catch(next);
-				},
-				(mediaSources, next) => {
-					this.publishProgress({ status: "update", message: `Importing SoundCloud playlist (stage 2)` });
-					let successful = 0;
-					let failed = 0;
-					let alreadyInPlaylist = 0;
-					let alreadyInLikedPlaylist = 0;
-					let alreadyInDislikedPlaylist = 0;
+					(isAdmin, next) => {
+						this.publishProgress({ status: "update", message: `Importing SoundCloud playlist (stage 1)` });
+						SoundcloudModule.runJob(
+							"GET_PLAYLIST",
+							{
+								url
+							},
+							this
+						)
+							.then(res => {
+								songsInPlaylistTotal = res.songs.length;
+								const mediaSources = res.songs.map(song => `soundcloud:${song}`);
+								next(null, mediaSources);
+							})
+							.catch(next);
+					},
+					(mediaSources, next) => {
+						this.publishProgress({ status: "update", message: `Importing SoundCloud playlist (stage 2)` });
+						let successful = 0;
+						let failed = 0;
+						let alreadyInPlaylist = 0;
+						let alreadyInLikedPlaylist = 0;
+						let alreadyInDislikedPlaylist = 0;
 
-					if (mediaSources.length === 0) next();
+						if (mediaSources.length === 0) next();
 
-					async.eachLimit(
-						mediaSources,
-						1,
-						(mediaSource, next) => {
-							WSModule.runJob(
-								"RUN_ACTION2",
-								{
-									session,
-									namespace: "playlists",
-									action: "addSongToPlaylist",
-									args: [true, mediaSource, playlistId]
-								},
-								this
-							)
-								.then(res => {
-									if (res.status === "success") {
-										successful += 1;
-										addedSongs.push(mediaSource);
-									} else failed += 1;
-									if (res.message === "That song is already in the playlist") alreadyInPlaylist += 1;
-									else if (
-										res.message ===
-										"That song is already in your Liked Songs playlist. " +
-											"A song cannot be in both the Liked Songs playlist" +
-											" and the Disliked Songs playlist at the same time."
-									)
-										alreadyInLikedPlaylist += 1;
-									else if (
-										res.message ===
-										"That song is already in your Disliked Songs playlist. " +
-											"A song cannot be in both the Liked Songs playlist " +
-											"and the Disliked Songs playlist at the same time."
-									)
-										alreadyInDislikedPlaylist += 1;
-								})
-								.catch(() => {
-									failed += 1;
-								})
-								.finally(() => next());
-						},
-						() => {
-							addSongsStats = {
-								successful,
-								failed,
-								alreadyInPlaylist,
-								alreadyInLikedPlaylist,
-								alreadyInDislikedPlaylist
-							};
-							next(null);
-						}
-					);
-				},
+						async.eachLimit(
+							mediaSources,
+							1,
+							(mediaSource, next) => {
+								WSModule.runJob(
+									"RUN_ACTION2",
+									{
+										session,
+										namespace: "playlists",
+										action: "addSongToPlaylist",
+										args: [true, mediaSource, playlistId]
+									},
+									this
+								)
+									.then(res => {
+										if (res.status === "success") {
+											successful += 1;
+											addedSongs.push(mediaSource);
+										} else failed += 1;
+										if (res.message === "That song is already in the playlist")
+											alreadyInPlaylist += 1;
+										else if (
+											res.message ===
+											"That song is already in your Liked Songs playlist. " +
+												"A song cannot be in both the Liked Songs playlist" +
+												" and the Disliked Songs playlist at the same time."
+										)
+											alreadyInLikedPlaylist += 1;
+										else if (
+											res.message ===
+											"That song is already in your Disliked Songs playlist. " +
+												"A song cannot be in both the Liked Songs playlist " +
+												"and the Disliked Songs playlist at the same time."
+										)
+											alreadyInDislikedPlaylist += 1;
+									})
+									.catch(() => {
+										failed += 1;
+									})
+									.finally(() => next());
+							},
+							() => {
+								addSongsStats = {
+									successful,
+									failed,
+									alreadyInPlaylist,
+									alreadyInLikedPlaylist,
+									alreadyInDislikedPlaylist
+								};
+								next(null);
+							}
+						);
+					},
 
-				next => {
-					this.publishProgress({ status: "update", message: `Importing SoundCloud playlist (stage 3)` });
-					PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
-						.then(playlist => next(null, playlist))
-						.catch(next);
-				},
+					next => {
+						this.publishProgress({ status: "update", message: `Importing SoundCloud playlist (stage 3)` });
+						PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
+							.then(playlist => next(null, playlist))
+							.catch(next);
+					},
+
+					(playlist, next) => {
+						this.publishProgress({ status: "update", message: `Importing SoundCloud playlist (stage 4)` });
+						if (!playlist) return next("Playlist not found.");
+						if (playlist.createdBy !== session.userId)
+							return hasPermission("playlists.songs.add", session)
+								.then(() => next(null, playlist))
+								.catch(() => next("Invalid permissions."));
+						return next(null, playlist);
+					}
+				],
+				async (err, playlist) => {
+					if (err) {
+						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+						this.log(
+							"ERROR",
+							"PLAYLIST_IMPORT",
+							`Importing a SoundCloud playlist to private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+						);
+						this.publishProgress({
+							status: "error",
+							message: err
+						});
+						return cb({ status: "error", message: err });
+					}
+
+					if (playlist.privacy === "public")
+						ActivitiesModule.runJob("ADD_ACTIVITY", {
+							userId: session.userId,
+							type: "playlist__import_playlist",
+							payload: {
+								message: `Imported ${addSongsStats.successful} songs to playlist <playlistId>${playlist.displayName}</playlistId>`,
+								playlistId
+							}
+						});
 
-				(playlist, next) => {
-					this.publishProgress({ status: "update", message: `Importing SoundCloud playlist (stage 4)` });
-					if (!playlist) return next("Playlist not found.");
-					if (playlist.createdBy !== session.userId)
-						return hasPermission("playlists.songs.add", session)
-							.then(() => next(null, playlist))
-							.catch(() => next("Invalid permissions."));
-					return next(null, playlist);
-				}
-			],
-			async (err, playlist) => {
-				if (err) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
-						"ERROR",
+						"SUCCESS",
 						"PLAYLIST_IMPORT",
-						`Importing a SoundCloud playlist to private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+						`Successfully imported a SoundCloud playlist to private playlist "${playlistId}" for user "${session.userId}". Songs in playlist: ${songsInPlaylistTotal}, songs successfully added: ${addSongsStats.successful}, songs failed: ${addSongsStats.failed}, already in playlist: ${addSongsStats.alreadyInPlaylist}, already in liked ${addSongsStats.alreadyInLikedPlaylist}, already in disliked ${addSongsStats.alreadyInDislikedPlaylist}.`
 					);
 					this.publishProgress({
-						status: "error",
-						message: err
+						status: "success",
+						message: `Playlist has been imported. ${addSongsStats.successful} were added successfully, ${addSongsStats.failed} failed (${addSongsStats.alreadyInPlaylist} were already in the playlist)`
 					});
-					return cb({ status: "error", message: err });
-				}
-
-				if (playlist.privacy === "public")
-					ActivitiesModule.runJob("ADD_ACTIVITY", {
-						userId: session.userId,
-						type: "playlist__import_playlist",
-						payload: {
-							message: `Imported ${addSongsStats.successful} songs to playlist <playlistId>${playlist.displayName}</playlistId>`,
-							playlistId
+					return cb({
+						status: "success",
+						message: `Playlist has been imported. ${addSongsStats.successful} were added successfully, ${addSongsStats.failed} failed (${addSongsStats.alreadyInPlaylist} were already in the playlist)`,
+						data: {
+							songs: playlist.songs,
+							stats: {
+								songsInPlaylistTotal,
+								alreadyInLikedPlaylist: addSongsStats.alreadyInLikedPlaylist,
+								alreadyInDislikedPlaylist: addSongsStats.alreadyInDislikedPlaylist
+							}
 						}
 					});
-
-				this.log(
-					"SUCCESS",
-					"PLAYLIST_IMPORT",
-					`Successfully imported a SoundCloud playlist to private playlist "${playlistId}" for user "${session.userId}". Songs in playlist: ${songsInPlaylistTotal}, songs successfully added: ${addSongsStats.successful}, songs failed: ${addSongsStats.failed}, already in playlist: ${addSongsStats.alreadyInPlaylist}, already in liked ${addSongsStats.alreadyInLikedPlaylist}, already in disliked ${addSongsStats.alreadyInDislikedPlaylist}.`
-				);
-				this.publishProgress({
-					status: "success",
-					message: `Playlist has been imported. ${addSongsStats.successful} were added successfully, ${addSongsStats.failed} failed (${addSongsStats.alreadyInPlaylist} were already in the playlist)`
-				});
-				return cb({
-					status: "success",
-					message: `Playlist has been imported. ${addSongsStats.successful} were added successfully, ${addSongsStats.failed} failed (${addSongsStats.alreadyInPlaylist} were already in the playlist)`,
-					data: {
-						songs: playlist.songs,
-						stats: {
-							songsInPlaylistTotal,
-							alreadyInLikedPlaylist: addSongsStats.alreadyInLikedPlaylist,
-							alreadyInDislikedPlaylist: addSongsStats.alreadyInDislikedPlaylist
-						}
-					}
-				});
-			}
-		);
-	}),
+				}
+			);
+		}
+	),
 
 	/**
 	 * Adds a set of Spotify songs to a private playlist
@@ -2413,166 +2409,163 @@ export default {
 	 * @param {string} playlistId - the id of the playlist we are removing the song from
 	 * @param {Function} cb - gets called with the result
 	 */
-	removeSongFromPlaylist: isLoginRequired(async function removeSongFromPlaylist(
-		session,
-		mediaSource,
-		playlistId,
-		cb
-	) {
-		async.waterfall(
-			[
-				next => {
-					if (!mediaSource || typeof mediaSource !== "string") return next("Invalid song id.");
-					if (!playlistId || typeof mediaSource !== "string") return next("Invalid playlist id.");
-					return next();
-				},
-
-				next => {
-					PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
-						.then(playlist => {
-							if (!playlist) return next("Playlist not found.");
-							if (playlist.createdBy !== session.userId)
-								return hasPermission("playlists.songs.remove", session)
-									.then(() => next(null, playlist))
-									.catch(() => next("Invalid permissions."));
-							return next(null, playlist);
-						})
-						.catch(next);
-				},
-
-				(playlist, next) => {
-					let normalizedSong = null;
-
-					const song = playlist.songs.find(song => song.mediaSource === mediaSource);
-					if (song) {
-						normalizedSong = {
-							_id: song._id,
-							title: song.title,
-							thumbnail: song.thumbnail,
-							artists: song.artists,
-							mediaSource: song.mediaSource
-						};
-					}
-
-					next(null, playlist, normalizedSong);
-				},
-
-				(playlist, newSong, next) => {
-					PlaylistsModule.runJob("REMOVE_FROM_PLAYLIST", { playlistId, mediaSource }, this)
-						.then(res => {
-							const { ratings } = res;
-							next(null, playlist, newSong, ratings);
-						})
-						.catch(next);
-				},
+	removeSongFromPlaylist: isLoginRequired(
+		async function removeSongFromPlaylist(session, mediaSource, playlistId, cb) {
+			async.waterfall(
+				[
+					next => {
+						if (!mediaSource || typeof mediaSource !== "string") return next("Invalid song id.");
+						if (!playlistId || typeof mediaSource !== "string") return next("Invalid playlist id.");
+						return next();
+					},
 
-				(playlist, newSong, ratings, next) => {
-					const { _id, title, artists, thumbnail } = newSong;
-					const songName = artists ? `${title} by ${artists.join(", ")}` : title;
+					next => {
+						PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
+							.then(playlist => {
+								if (!playlist) return next("Playlist not found.");
+								if (playlist.createdBy !== session.userId)
+									return hasPermission("playlists.songs.remove", session)
+										.then(() => next(null, playlist))
+										.catch(() => next("Invalid permissions."));
+								return next(null, playlist);
+							})
+							.catch(next);
+					},
 
-					if (playlist.type === "user" && playlist.privacy === "public") {
-						ActivitiesModule.runJob("ADD_ACTIVITY", {
-							userId: session.userId,
-							type: "playlist__remove_song",
-							payload: {
-								message: `Removed <mediaSource>${songName}</mediaSource> from playlist <playlistId>${playlist.displayName}</playlistId>`,
-								thumbnail,
-								playlistId,
-								mediaSource: newSong.mediaSource
-							}
-						});
-					}
+					(playlist, next) => {
+						let normalizedSong = null;
+
+						const song = playlist.songs.find(song => song.mediaSource === mediaSource);
+						if (song) {
+							normalizedSong = {
+								_id: song._id,
+								title: song.title,
+								thumbnail: song.thumbnail,
+								artists: song.artists,
+								mediaSource: song.mediaSource
+							};
+						}
 
-					if (ratings && (playlist.type === "user-liked" || playlist.type === "user-disliked")) {
-						const { likes, dislikes } = ratings;
+						next(null, playlist, normalizedSong);
+					},
 
-						if (_id) SongsModule.runJob("UPDATE_SONG", { songId: _id });
+					(playlist, newSong, next) => {
+						PlaylistsModule.runJob("REMOVE_FROM_PLAYLIST", { playlistId, mediaSource }, this)
+							.then(res => {
+								const { ratings } = res;
+								next(null, playlist, newSong, ratings);
+							})
+							.catch(next);
+					},
 
-						if (playlist.type === "user-liked") {
-							CacheModule.runJob("PUB", {
-								channel: "ratings.unlike",
-								value: JSON.stringify({
-									mediaSource: newSong.mediaSource,
-									userId: session.userId,
-									likes,
-									dislikes
-								})
-							});
+					(playlist, newSong, ratings, next) => {
+						const { _id, title, artists, thumbnail } = newSong;
+						const songName = artists ? `${title} by ${artists.join(", ")}` : title;
 
+						if (playlist.type === "user" && playlist.privacy === "public") {
 							ActivitiesModule.runJob("ADD_ACTIVITY", {
 								userId: session.userId,
-								type: "song__unlike",
+								type: "playlist__remove_song",
 								payload: {
-									message: `Removed <mediaSource>${title} by ${artists.join(
-										", "
-									)}</mediaSource> from your Liked Songs`,
-									mediaSource: newSong.mediaSource,
-									thumbnail
+									message: `Removed <mediaSource>${songName}</mediaSource> from playlist <playlistId>${playlist.displayName}</playlistId>`,
+									thumbnail,
+									playlistId,
+									mediaSource: newSong.mediaSource
 								}
 							});
-						} else {
-							CacheModule.runJob("PUB", {
-								channel: "ratings.undislike",
-								value: JSON.stringify({
-									mediaSource: newSong.mediaSource,
+						}
+
+						if (ratings && (playlist.type === "user-liked" || playlist.type === "user-disliked")) {
+							const { likes, dislikes } = ratings;
+
+							if (_id) SongsModule.runJob("UPDATE_SONG", { songId: _id });
+
+							if (playlist.type === "user-liked") {
+								CacheModule.runJob("PUB", {
+									channel: "ratings.unlike",
+									value: JSON.stringify({
+										mediaSource: newSong.mediaSource,
+										userId: session.userId,
+										likes,
+										dislikes
+									})
+								});
+
+								ActivitiesModule.runJob("ADD_ACTIVITY", {
 									userId: session.userId,
-									likes,
-									dislikes
-								})
-							});
+									type: "song__unlike",
+									payload: {
+										message: `Removed <mediaSource>${title} by ${artists.join(
+											", "
+										)}</mediaSource> from your Liked Songs`,
+										mediaSource: newSong.mediaSource,
+										thumbnail
+									}
+								});
+							} else {
+								CacheModule.runJob("PUB", {
+									channel: "ratings.undislike",
+									value: JSON.stringify({
+										mediaSource: newSong.mediaSource,
+										userId: session.userId,
+										likes,
+										dislikes
+									})
+								});
 
-							ActivitiesModule.runJob("ADD_ACTIVITY", {
-								userId: session.userId,
-								type: "song__undislike",
-								payload: {
-									message: `Removed <mediaSource>${title} by ${artists.join(
-										", "
-									)}</mediaSource> from your Disliked Songs`,
-									mediaSource: newSong.mediaSource,
-									thumbnail
-								}
-							});
+								ActivitiesModule.runJob("ADD_ACTIVITY", {
+									userId: session.userId,
+									type: "song__undislike",
+									payload: {
+										message: `Removed <mediaSource>${title} by ${artists.join(
+											", "
+										)}</mediaSource> from your Disliked Songs`,
+										mediaSource: newSong.mediaSource,
+										thumbnail
+									}
+								});
+							}
 						}
+
+						return next(null, playlist);
+					}
+				],
+				async (err, playlist) => {
+					if (err) {
+						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+						this.log(
+							"ERROR",
+							"PLAYLIST_REMOVE_SONG",
+							`Removing song "${mediaSource}" from private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+						);
+						return cb({ status: "error", message: err });
 					}
 
-					return next(null, playlist);
-				}
-			],
-			async (err, playlist) => {
-				if (err) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
-						"ERROR",
+						"SUCCESS",
 						"PLAYLIST_REMOVE_SONG",
-						`Removing song "${mediaSource}" from private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+						`Successfully removed song "${mediaSource}" from private playlist "${playlistId}" for user "${session.userId}".`
 					);
-					return cb({ status: "error", message: err });
-				}
-
-				this.log(
-					"SUCCESS",
-					"PLAYLIST_REMOVE_SONG",
-					`Successfully removed song "${mediaSource}" from private playlist "${playlistId}" for user "${session.userId}".`
-				);
 
-				CacheModule.runJob("PUB", {
-					channel: "playlist.removeSong",
-					value: {
-						playlistId: playlist._id,
-						mediaSource,
-						createdBy: playlist.createdBy,
-						privacy: playlist.privacy
-					}
-				});
+					CacheModule.runJob("PUB", {
+						channel: "playlist.removeSong",
+						value: {
+							playlistId: playlist._id,
+							mediaSource,
+							createdBy: playlist.createdBy,
+							privacy: playlist.privacy
+						}
+					});
 
-				return cb({
-					status: "success",
-					message: "Song has been successfully removed from playlist",
-					data: { songs: playlist.songs }
-				});
-			}
-		);
-	}),
+					return cb({
+						status: "success",
+						message: "Song has been successfully removed from playlist",
+						data: { songs: playlist.songs }
+					});
+				}
+			);
+		}
+	),
 
 	/**
 	 * Updates the displayName of a private playlist

+ 43 - 45
backend/logic/actions/users.js

@@ -1347,58 +1347,56 @@ export default {
 	 * @param {Array} favoriteStations - array of station ids (with a specific order)
 	 * @param {Function} cb - gets called with the result
 	 */
-	updateOrderOfFavoriteStations: isLoginRequired(async function updateOrderOfFavoriteStations(
-		session,
-		favoriteStations,
-		cb
-	) {
-		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
+	updateOrderOfFavoriteStations: isLoginRequired(
+		async function updateOrderOfFavoriteStations(session, favoriteStations, cb) {
+			const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
 
-		async.waterfall(
-			[
-				next => {
-					userModel.updateOne(
-						{ _id: session.userId },
-						{ $set: { favoriteStations } },
-						{ runValidators: true },
-						next
-					);
-				}
-			],
-			async err => {
-				if (err) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+			async.waterfall(
+				[
+					next => {
+						userModel.updateOne(
+							{ _id: session.userId },
+							{ $set: { favoriteStations } },
+							{ runValidators: true },
+							next
+						);
+					}
+				],
+				async err => {
+					if (err) {
+						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+
+						this.log(
+							"ERROR",
+							"UPDATE_ORDER_OF_USER_FAVORITE_STATIONS",
+							`Couldn't update order of favorite stations for user "${session.userId}" to "${favoriteStations}". "${err}"`
+						);
+
+						return cb({ status: "error", message: err });
+					}
+
+					CacheModule.runJob("PUB", {
+						channel: "user.updateOrderOfFavoriteStations",
+						value: {
+							favoriteStations,
+							userId: session.userId
+						}
+					});
 
 					this.log(
-						"ERROR",
+						"SUCCESS",
 						"UPDATE_ORDER_OF_USER_FAVORITE_STATIONS",
-						`Couldn't update order of favorite stations for user "${session.userId}" to "${favoriteStations}". "${err}"`
+						`Updated order of favorite stations for user "${session.userId}" to "${favoriteStations}".`
 					);
 
-					return cb({ status: "error", message: err });
+					return cb({
+						status: "success",
+						message: "Order of favorite stations successfully updated"
+					});
 				}
-
-				CacheModule.runJob("PUB", {
-					channel: "user.updateOrderOfFavoriteStations",
-					value: {
-						favoriteStations,
-						userId: session.userId
-					}
-				});
-
-				this.log(
-					"SUCCESS",
-					"UPDATE_ORDER_OF_USER_FAVORITE_STATIONS",
-					`Updated order of favorite stations for user "${session.userId}" to "${favoriteStations}".`
-				);
-
-				return cb({
-					status: "success",
-					message: "Order of favorite stations successfully updated"
-				});
-			}
-		);
-	}),
+			);
+		}
+	),
 
 	/**
 	 * Updates the order of a user's playlists

+ 10 - 10
backend/logic/cache/index.js

@@ -113,7 +113,7 @@ class _CacheModule extends CoreClass {
 	 * @param {string} payload.key -  name of the key to set
 	 * @param {*} payload.value - the value we want to set
 	 * @param {number} payload.ttl -  ttl of the key in seconds
-	 * @param {boolean} [payload.stringifyJson=true] - stringify 'value' if it's an Object or Array
+	 * @param {boolean} [payload.stringifyJson] - stringify 'value' if it's an Object or Array
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
 	SET(payload) {
@@ -154,7 +154,7 @@ class _CacheModule extends CoreClass {
 	 * @param {string} payload.table - name of the table we want to set a key of (table === redis hash)
 	 * @param {string} payload.key -  name of the key to set
 	 * @param {*} payload.value - the value we want to set
-	 * @param {boolean} [payload.stringifyJson=true] - stringify 'value' if it's an Object or Array
+	 * @param {boolean} [payload.stringifyJson] - stringify 'value' if it's an Object or Array
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
 	HSET(payload) {
@@ -177,7 +177,7 @@ class _CacheModule extends CoreClass {
 	 * Gets a single value
 	 * @param {object} payload - object containing payload
 	 * @param {string} payload.key - name of the key to fetch
-	 * @param {boolean} [payload.parseJson=true] - attempt to parse returned data as JSON
+	 * @param {boolean} [payload.parseJson] - attempt to parse returned data as JSON
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
 	GET(payload) {
@@ -213,7 +213,7 @@ class _CacheModule extends CoreClass {
 	 * @param {object} payload - object containing payload
 	 * @param {string} payload.table - name of the table to get the value from (table === redis hash)
 	 * @param {string} payload.key - name of the key to fetch
-	 * @param {boolean} [payload.parseJson=true] - attempt to parse returned data as JSON
+	 * @param {boolean} [payload.parseJson] - attempt to parse returned data as JSON
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
 	HGET(payload) {
@@ -279,7 +279,7 @@ class _CacheModule extends CoreClass {
 	 * Returns all the keys for a table
 	 * @param {object} payload - object containing payload
 	 * @param {string} payload.table - name of the table to get the values from (table === redis hash)
-	 * @param {boolean} [payload.parseJson=true] - attempts to parse all values as JSON by default
+	 * @param {boolean} [payload.parseJson] - attempts to parse all values as JSON by default
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
 	HGETALL(payload) {
@@ -332,7 +332,7 @@ class _CacheModule extends CoreClass {
 	 * @param {object} payload - object containing payload
 	 * @param {string} payload.channel - the name of the channel we want to publish a message to
 	 * @param {*} payload.value - the value we want to send
-	 * @param {boolean} [payload.stringifyJson=true] - stringify 'value' if it's an Object or Array
+	 * @param {boolean} [payload.stringifyJson] - stringify 'value' if it's an Object or Array
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
 	PUB(payload) {
@@ -361,7 +361,7 @@ class _CacheModule extends CoreClass {
 	 * Subscribe to a channel, caches the redis client connection
 	 * @param {object} payload - object containing payload
 	 * @param {string} payload.channel - name of the channel to subscribe to
-	 * @param {boolean} [payload.parseJson=true] - parse the message as JSON
+	 * @param {boolean} [payload.parseJson] - parse the message as JSON
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
 	SUB(payload) {
@@ -426,7 +426,7 @@ class _CacheModule extends CoreClass {
 	 * @param {object} payload - object containing payload
 	 * @param {string} payload.key -  name of the list
 	 * @param {*} payload.value - the value we want to set
-	 * @param {boolean} [payload.stringifyJson=true] - stringify 'value' if it's an Object or Array
+	 * @param {boolean} [payload.stringifyJson] - stringify 'value' if it's an Object or Array
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
 	RPUSH(payload) {
@@ -449,7 +449,7 @@ class _CacheModule extends CoreClass {
 	 * @param {object} payload - object containing payload
 	 * @param {string} payload.key -  name of the list
 	 * @param {*} payload.value - the value we want to set
-	 * @param {boolean} [payload.stringifyJson=true] - stringify 'value' if it's an Object or Array
+	 * @param {boolean} [payload.stringifyJson] - stringify 'value' if it's an Object or Array
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
 	LPUSH(payload) {
@@ -506,7 +506,7 @@ class _CacheModule extends CoreClass {
 	 * @param {object} payload - object containing payload
 	 * @param {string} payload.key -  name of the list
 	 * @param {*} payload.value - the value we want to remove
-	 * @param {boolean} [payload.stringifyJson=true] - stringify 'value' if it's an Object or Array
+	 * @param {boolean} [payload.stringifyJson] - stringify 'value' if it's an Object or Array
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
 	LREM(payload) {

+ 4 - 4
backend/logic/hooks/hasPermission.js

@@ -72,7 +72,7 @@ permissions.moderator = {
 				"admin.view.soundcloudTracks": true,
 				"admin.view.soundcloud": true,
 				"soundcloud.getArtist": true
-		  }
+			}
 		: {}),
 	...(config.get("experimental.spotify")
 		? {
@@ -85,7 +85,7 @@ permissions.moderator = {
 				"spotify.getAlternativeMediaSourcesForTracks": true,
 				"admin.view.youtubeChannels": true,
 				"youtube.getChannel": true
-		  }
+			}
 		: {})
 };
 permissions.admin = {
@@ -123,12 +123,12 @@ permissions.admin = {
 		? {
 				"soundcloud.fetchNewApiKey": true,
 				"soundcloud.testApiKey": true
-		  }
+			}
 		: {}),
 	...(config.get("experimental.spotify")
 		? {
 				"youtube.getMissingChannels": true
-		  }
+			}
 		: {})
 };
 

+ 2 - 2
backend/logic/mail/schemas/dataRequest.js

@@ -22,8 +22,8 @@ export default (to, userId, type, cb) => {
 				<br>
 				This request can be viewed and resolved in the
 				<a href="${config.get("url.secure") ? "https" : "http"}://${config.get(
-			"url.host"
-		)}/admin/users">Users tab of the admin page</a>. Note: All admins will be sent the same message.
+					"url.host"
+				)}/admin/users">Users tab of the admin page</a>. Note: All admins will be sent the same message.
 			`
 	};
 

+ 19 - 13
frontend/src/App.vue

@@ -105,9 +105,8 @@ watch(christmas, enabled => {
 });
 
 onMounted(async () => {
-	document.getElementsByTagName(
-		"html"
-	)[0].style.cssText = `--primary-color: ${configStore.primaryColor}`;
+	document.getElementsByTagName("html")[0].style.cssText =
+		`--primary-color: ${configStore.primaryColor}`;
 
 	window
 		.matchMedia("(prefers-color-scheme: dark)")
@@ -126,9 +125,8 @@ onMounted(async () => {
 	socket.onConnect(() => {
 		socketConnected.value = true;
 
-		document.getElementsByTagName(
-			"html"
-		)[0].style.cssText = `--primary-color: ${configStore.primaryColor}`;
+		document.getElementsByTagName("html")[0].style.cssText =
+			`--primary-color: ${configStore.primaryColor}`;
 
 		if (!loggedIn.value) {
 			broadcastChannel.value.user_login = new BroadcastChannel(
@@ -418,7 +416,8 @@ onMounted(async () => {
 	font-style: normal;
 	font-weight: 200;
 	src: url("/fonts/nunito-v16-latin-200.eot"); /* IE9 Compat Modes */
-	src: local(""),
+	src:
+		local(""),
 		url("/fonts/nunito-v16-latin-200.eot?#iefix")
 			format("embedded-opentype"),
 		/* IE6-IE8 */ url("/fonts/nunito-v16-latin-200.woff2") format("woff2"),
@@ -436,7 +435,8 @@ onMounted(async () => {
 	font-style: normal;
 	font-weight: 400;
 	src: url("/fonts/nunito-v16-latin-regular.eot"); /* IE9 Compat Modes */
-	src: local(""),
+	src:
+		local(""),
 		url("/fonts/nunito-v16-latin-regular.eot?#iefix")
 			format("embedded-opentype"),
 		/* IE6-IE8 */ url("/fonts/nunito-v16-latin-regular.woff2")
@@ -455,7 +455,8 @@ onMounted(async () => {
 	font-style: normal;
 	font-weight: 600;
 	src: url("/fonts/nunito-v16-latin-600.eot"); /* IE9 Compat Modes */
-	src: local(""),
+	src:
+		local(""),
 		url("/fonts/nunito-v16-latin-600.eot?#iefix")
 			format("embedded-opentype"),
 		/* IE6-IE8 */ url("/fonts/nunito-v16-latin-600.woff2") format("woff2"),
@@ -473,7 +474,8 @@ onMounted(async () => {
 	font-style: normal;
 	font-weight: 700;
 	src: url("/fonts/nunito-v16-latin-700.eot"); /* IE9 Compat Modes */
-	src: local(""),
+	src:
+		local(""),
 		url("/fonts/nunito-v16-latin-700.eot?#iefix")
 			format("embedded-opentype"),
 		/* IE6-IE8 */ url("/fonts/nunito-v16-latin-700.woff2") format("woff2"),
@@ -491,7 +493,8 @@ onMounted(async () => {
 	font-style: normal;
 	font-weight: 800;
 	src: url("/fonts/nunito-v16-latin-800.eot"); /* IE9 Compat Modes */
-	src: local(""),
+	src:
+		local(""),
 		url("/fonts/nunito-v16-latin-800.eot?#iefix")
 			format("embedded-opentype"),
 		/* IE6-IE8 */ url("/fonts/nunito-v16-latin-800.woff2") format("woff2"),
@@ -509,7 +512,8 @@ onMounted(async () => {
 	font-style: normal;
 	font-weight: 400;
 	src: url("/fonts/pacifico-v17-latin-regular.eot"); /* IE9 Compat Modes */
-	src: local(""),
+	src:
+		local(""),
 		url("/fonts/pacifico-v17-latin-regular.eot?#iefix")
 			format("embedded-opentype"),
 		/* IE6-IE8 */ url("/fonts/pacifico-v17-latin-regular.woff2")
@@ -527,7 +531,9 @@ onMounted(async () => {
 	font-style: normal;
 	font-weight: 400;
 	src: url(/fonts/MaterialIcons-Regular.ttf); /* For IE6-8 */
-	src: local("Material Icons"), local("MaterialIcons-Regular"),
+	src:
+		local("Material Icons"),
+		local("MaterialIcons-Regular"),
 		url(/fonts/MaterialIcons-Regular.ttf) format("truetype");
 }
 

+ 4 - 4
frontend/src/components/AdvancedTable.vue

@@ -63,7 +63,7 @@ const props = defineProps({
 	name: { type: String, default: null },
 	maxWidth: { type: Number, default: 1880 },
 	query: { type: Boolean, default: true },
-	keyboardShortcuts: { type: Boolean, default: true },
+	hasKeyboardShortcuts: { type: Boolean, default: true },
 	events: { type: Object as PropType<TableEvents>, default: () => {} },
 	bulkActions: {
 		type: Object as PropType<TableBulkActions>,
@@ -979,7 +979,7 @@ onMounted(async () => {
 			removeData(index);
 		});
 
-	if (props.keyboardShortcuts) {
+	if (props.hasKeyboardShortcuts) {
 		// Navigation section
 
 		// Page navigation section
@@ -1136,7 +1136,7 @@ onUnmounted(() => {
 	if (columnOrderChangedDebounceTimeout.value)
 		clearTimeout(columnOrderChangedDebounceTimeout.value);
 
-	if (props.keyboardShortcuts) {
+	if (props.hasKeyboardShortcuts) {
 		const shortcutNames = [
 			// Navigation
 			"advancedTable.previousPage",
@@ -1770,7 +1770,7 @@ watch(selectedRows, (newSelectedRows, oldSelectedRows) => {
 															] !== undefined
 																? previous[
 																		current
-																  ]
+																	]
 																: null,
 														item
 													) !== null

+ 1 - 1
frontend/src/components/FloatingBox.vue

@@ -43,7 +43,7 @@ const saveBox = () => {
 				? Math.max(
 						document.body.clientHeight - 10 - dragBox.value.height,
 						0
-				  )
+					)
 				: 10,
 		left: 10
 	});

+ 3 - 3
frontend/src/components/LongJobs.spec.ts

@@ -36,11 +36,11 @@ describe("LongJobs component", async () => {
 											message: "Successfully edited tags."
 										}
 									}
-							  }
+								}
 							: {
 									status: "error",
 									message: "Long job not found."
-							  },
+								},
 					"users.removeLongJob": () => ({
 						status: "success"
 					})
@@ -61,7 +61,7 @@ describe("LongJobs component", async () => {
 										status: "update",
 										message: "Updating tags in MongoDB."
 									}
-							  ]
+								]
 							: []
 				},
 				on: {

+ 1 - 1
frontend/src/components/PlaylistTabBase.vue

@@ -1042,7 +1042,7 @@ onMounted(() => {
 														'future',
 														null,
 														true
-												  )} songs from this playlist`
+													)} songs from this playlist`
 												: 'Your preferences are set to skip disliked songs'
 										"
 										v-tippy

+ 1 - 1
frontend/src/components/SoundcloudTrackInfo.vue

@@ -64,7 +64,7 @@ defineProps<{
 						track.soundcloudCreatedAt
 							? utils.getDateFormatted(
 									new Date(track.soundcloudCreatedAt)
-							  )
+								)
 							: "Unknown"
 					}}</span
 				>

+ 2 - 2
frontend/src/components/YoutubePlayer.vue

@@ -536,8 +536,8 @@ onBeforeUnmount(() => {
 							youtubePlayer.muted
 								? "volume_mute"
 								: youtubePlayer.volume >= 50
-								? "volume_up"
-								: "volume_down"
+									? "volume_up"
+									: "volume_down"
 						}}</i
 					>
 					<input

+ 1 - 1
frontend/src/components/modals/ConvertSpotifySongs.vue

@@ -113,7 +113,7 @@ const filteredSpotifySongs = computed(() =>
 					(alternativeMediaPerTrack[spotifySong.mediaSource] &&
 						alternativeMediaPerTrack[spotifySong.mediaSource]
 							.mediaSources.length > 0)
-		  )
+			)
 		: spotifySongs.value
 );
 

+ 3 - 2
frontend/src/components/modals/EditSong/index.vue

@@ -82,6 +82,7 @@ const {
 const { openModal, closeCurrentModal, preventCloseCbs } = useModalsStore();
 const { hasPermission } = userAuthStore;
 
+// eslint-disable-next-line vue/no-dupe-keys
 const {
 	tab,
 	video,
@@ -2250,8 +2251,8 @@ onBeforeUnmount(() => {
 												muted
 													? "volume_mute"
 													: volumeSliderValue >= 50
-													? "volume_up"
-													: "volume_down"
+														? "volume_up"
+														: "volume_down"
 											}}</i
 										>
 										<input

+ 3 - 2
frontend/src/components/modals/ManageStation/index.vue

@@ -46,6 +46,7 @@ const { socket } = useWebsocketsStore();
 const manageStationStore = useManageStationStore({
 	modalUuid: props.modalUuid
 });
+// eslint-disable-next-line vue/no-dupe-keys
 const {
 	stationId,
 	sector,
@@ -482,8 +483,8 @@ onBeforeUnmount(() => {
 			sector === 'home' && !hasPermission('stations.view.manage')
 				? 'View Queue'
 				: !hasPermission('stations.view.manage')
-				? 'Add Song to Queue'
-				: 'Manage Station'
+					? 'Add Song to Queue'
+					: 'Manage Station'
 		"
 		:style="`--primary-color: var(--${station.theme})`"
 		class="manage-station-modal"

+ 12 - 12
frontend/src/pages/Home.vue

@@ -605,21 +605,21 @@ onBeforeUnmount(() => {
 									:title="
 										element.currentSong.artists.length > 0
 											? 'Now Playing: ' +
-											  element.currentSong.title +
-											  ' by ' +
-											  element.currentSong.artists.join(
+												element.currentSong.title +
+												' by ' +
+												element.currentSong.artists.join(
 													', '
-											  )
+												)
 											: 'Now Playing: ' +
-											  element.currentSong.title
+												element.currentSong.title
 									"
 									>{{ element.currentSong.title }}
 									{{
 										element.currentSong.artists.length > 0
 											? " by " +
-											  element.currentSong.artists.join(
+												element.currentSong.artists.join(
 													", "
-											  )
+												)
 											: ""
 									}}</span
 								>
@@ -863,17 +863,17 @@ onBeforeUnmount(() => {
 							:title="
 								station.currentSong.artists.length > 0
 									? 'Now Playing: ' +
-									  station.currentSong.title +
-									  ' by ' +
-									  station.currentSong.artists.join(', ')
+										station.currentSong.title +
+										' by ' +
+										station.currentSong.artists.join(', ')
 									: 'Now Playing: ' +
-									  station.currentSong.title
+										station.currentSong.title
 							"
 							>{{ station.currentSong.title }}
 							{{
 								station.currentSong.artists.length > 0
 									? " by " +
-									  station.currentSong.artists.join(", ")
+										station.currentSong.artists.join(", ")
 									: ""
 							}}</span
 						>

+ 2 - 2
frontend/src/pages/Profile/Tabs/Playlists.vue

@@ -15,7 +15,7 @@ const props = defineProps({
 const {
 	DraggableList,
 	drag,
-	userId,
+	userId: playlistUserId,
 	isCurrentUser,
 	playlists,
 	savePlaylistOrder
@@ -24,7 +24,7 @@ const {
 const { openModal } = useModalsStore();
 
 onMounted(() => {
-	userId.value = props.userId;
+	playlistUserId.value = props.userId;
 });
 </script>
 

+ 10 - 13
frontend/src/pages/Station/index.vue

@@ -1172,7 +1172,7 @@ const sendActivityWatchMediaData = () => {
 					? 0
 					: Math.floor(
 							activityWatchMediaLastStartDuration.value / 1000
-					  ),
+						),
 			source: `station#${station.value.name}`,
 			hostname: window.location.hostname,
 			experimentalChangableListenMode:
@@ -1189,7 +1189,7 @@ const sendActivityWatchMediaData = () => {
 							key =>
 								window.YT.PlayerState[key] ===
 								youtubePlayer.value?.getPlayerState()
-					  );
+						);
 
 			videoData.playbackRate = playbackRate.value;
 		} else {
@@ -1351,9 +1351,8 @@ onMounted(async () => {
 					djs
 				});
 
-				document.getElementsByTagName(
-					"html"
-				)[0].style.cssText = `--primary-color: var(--${res.data.theme})`;
+				document.getElementsByTagName("html")[0].style.cssText =
+					`--primary-color: var(--${res.data.theme})`;
 
 				setCurrentSong({
 					currentSong: res.data.currentSong,
@@ -1748,9 +1747,8 @@ onMounted(async () => {
 			}
 
 			if (station.value.theme !== theme)
-				document.getElementsByTagName(
-					"html"
-				)[0].style.cssText = `--primary-color: var(--${theme})`;
+				document.getElementsByTagName("html")[0].style.cssText =
+					`--primary-color: var(--${theme})`;
 
 			updateStation(res.data.station);
 		}
@@ -1922,9 +1920,8 @@ onMounted(async () => {
 });
 
 onBeforeUnmount(() => {
-	document.getElementsByTagName(
-		"html"
-	)[0].style.cssText = `--primary-color: ${primaryColor.value}`;
+	document.getElementsByTagName("html")[0].style.cssText =
+		`--primary-color: ${primaryColor.value}`;
 
 	if (experimental.value.media_session) {
 		ms.removeListeners(0);
@@ -2537,8 +2534,8 @@ onBeforeUnmount(() => {
 											muted
 												? "volume_mute"
 												: volumeSliderValue >= 50
-												? "volume_up"
-												: "volume_down"
+													? "volume_up"
+													: "volume_down"
 										}}</i
 									>
 									<input