|  | @@ -1744,7 +1744,7 @@ export default {
 | 
	
		
			
				|  |  |  	 * @param session
 | 
	
		
			
				|  |  |  	 * @param cb
 | 
	
		
			
				|  |  |  	 */
 | 
	
		
			
				|  |  | -	getGenres: isAdminRequired(function getModule(session, cb) {
 | 
	
		
			
				|  |  | +	getGenres: isAdminRequired(function getGenres(session, cb) {
 | 
	
		
			
				|  |  |  		async.waterfall(
 | 
	
		
			
				|  |  |  			[
 | 
	
		
			
				|  |  |  				next => {
 | 
	
	
		
			
				|  | @@ -1774,13 +1774,78 @@ export default {
 | 
	
		
			
				|  |  |  		);
 | 
	
		
			
				|  |  |  	}),
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +	/**
 | 
	
		
			
				|  |  | +	 * Bulk update genres for selected songs
 | 
	
		
			
				|  |  | +	 *
 | 
	
		
			
				|  |  | +	 * @param session
 | 
	
		
			
				|  |  | +	 * @param method Whether to add, remove or replace genres
 | 
	
		
			
				|  |  | +	 * @param genres Array of genres to apply
 | 
	
		
			
				|  |  | +	 * @param songIds Array of songIds to apply genres to
 | 
	
		
			
				|  |  | +	 * @param cb
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	editGenres: isAdminRequired(async function editGenres(session, method, genres, songIds, cb) {
 | 
	
		
			
				|  |  | +		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
 | 
	
		
			
				|  |  | +		async.waterfall(
 | 
	
		
			
				|  |  | +			[
 | 
	
		
			
				|  |  | +				next => {
 | 
	
		
			
				|  |  | +					songModel.find({ _id: { $in: songIds } }, next);
 | 
	
		
			
				|  |  | +				},
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				(songs, next) => {
 | 
	
		
			
				|  |  | +					const songsFound = songs.map(song => song._id);
 | 
	
		
			
				|  |  | +					if (songsFound.length > 0) next(null, songsFound);
 | 
	
		
			
				|  |  | +					else next("None of the specified songs were found.");
 | 
	
		
			
				|  |  | +				},
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				(songsFound, next) => {
 | 
	
		
			
				|  |  | +					const query = {};
 | 
	
		
			
				|  |  | +					if (method === "add") {
 | 
	
		
			
				|  |  | +						query.$push = { genres: { $each: genres } };
 | 
	
		
			
				|  |  | +					} else if (method === "remove") {
 | 
	
		
			
				|  |  | +						query.$pullAll = { genres };
 | 
	
		
			
				|  |  | +					} else if (method === "replace") {
 | 
	
		
			
				|  |  | +						query.$set = { genres };
 | 
	
		
			
				|  |  | +					} else {
 | 
	
		
			
				|  |  | +						next("Invalid method.");
 | 
	
		
			
				|  |  | +					}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					songModel.updateMany({ _id: { $in: songsFound } }, query, { runValidators: true }, err => {
 | 
	
		
			
				|  |  | +						if (err) next(err);
 | 
	
		
			
				|  |  | +						async.eachLimit(
 | 
	
		
			
				|  |  | +							songsFound,
 | 
	
		
			
				|  |  | +							1,
 | 
	
		
			
				|  |  | +							(songId, next) => {
 | 
	
		
			
				|  |  | +								SongsModule.runJob("UPDATE_SONG", { songId });
 | 
	
		
			
				|  |  | +								next();
 | 
	
		
			
				|  |  | +							},
 | 
	
		
			
				|  |  | +							next
 | 
	
		
			
				|  |  | +						);
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +			],
 | 
	
		
			
				|  |  | +			async err => {
 | 
	
		
			
				|  |  | +				if (err && err !== true) {
 | 
	
		
			
				|  |  | +					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 | 
	
		
			
				|  |  | +					this.log("ERROR", "EDIT_GENRES", `User ${session.userId} failed to edit genres. '${err}'`);
 | 
	
		
			
				|  |  | +					cb({ status: "error", message: err });
 | 
	
		
			
				|  |  | +				} else {
 | 
	
		
			
				|  |  | +					this.log("SUCCESS", "EDIT_GENRES", `User ${session.userId} has successfully edited genres.`);
 | 
	
		
			
				|  |  | +					cb({
 | 
	
		
			
				|  |  | +						status: "success",
 | 
	
		
			
				|  |  | +						message: "Successfully edited genres."
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +		);
 | 
	
		
			
				|  |  | +	}),
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	/**
 | 
	
		
			
				|  |  |  	 * Gets a list of all artists
 | 
	
		
			
				|  |  |  	 *
 | 
	
		
			
				|  |  |  	 * @param session
 | 
	
		
			
				|  |  |  	 * @param cb
 | 
	
		
			
				|  |  |  	 */
 | 
	
		
			
				|  |  | -	getArtists: isAdminRequired(function getModule(session, cb) {
 | 
	
		
			
				|  |  | +	getArtists: isAdminRequired(function getArtists(session, cb) {
 | 
	
		
			
				|  |  |  		async.waterfall(
 | 
	
		
			
				|  |  |  			[
 | 
	
		
			
				|  |  |  				next => {
 | 
	
	
		
			
				|  | @@ -1810,13 +1875,78 @@ export default {
 | 
	
		
			
				|  |  |  		);
 | 
	
		
			
				|  |  |  	}),
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +	/**
 | 
	
		
			
				|  |  | +	 * Bulk update artists for selected songs
 | 
	
		
			
				|  |  | +	 *
 | 
	
		
			
				|  |  | +	 * @param session
 | 
	
		
			
				|  |  | +	 * @param method Whether to add, remove or replace artists
 | 
	
		
			
				|  |  | +	 * @param artists Array of artists to apply
 | 
	
		
			
				|  |  | +	 * @param songIds Array of songIds to apply artists to
 | 
	
		
			
				|  |  | +	 * @param cb
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	editArtists: isAdminRequired(async function editArtists(session, method, artists, songIds, cb) {
 | 
	
		
			
				|  |  | +		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
 | 
	
		
			
				|  |  | +		async.waterfall(
 | 
	
		
			
				|  |  | +			[
 | 
	
		
			
				|  |  | +				next => {
 | 
	
		
			
				|  |  | +					songModel.find({ _id: { $in: songIds } }, next);
 | 
	
		
			
				|  |  | +				},
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				(songs, next) => {
 | 
	
		
			
				|  |  | +					const songsFound = songs.map(song => song._id);
 | 
	
		
			
				|  |  | +					if (songsFound.length > 0) next(null, songsFound);
 | 
	
		
			
				|  |  | +					else next("None of the specified songs were found.");
 | 
	
		
			
				|  |  | +				},
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				(songsFound, next) => {
 | 
	
		
			
				|  |  | +					const query = {};
 | 
	
		
			
				|  |  | +					if (method === "add") {
 | 
	
		
			
				|  |  | +						query.$push = { artists: { $each: artists } };
 | 
	
		
			
				|  |  | +					} else if (method === "remove") {
 | 
	
		
			
				|  |  | +						query.$pullAll = { artists };
 | 
	
		
			
				|  |  | +					} else if (method === "replace") {
 | 
	
		
			
				|  |  | +						query.$set = { artists };
 | 
	
		
			
				|  |  | +					} else {
 | 
	
		
			
				|  |  | +						next("Invalid method.");
 | 
	
		
			
				|  |  | +					}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					songModel.updateMany({ _id: { $in: songsFound } }, query, { runValidators: true }, err => {
 | 
	
		
			
				|  |  | +						if (err) next(err);
 | 
	
		
			
				|  |  | +						async.eachLimit(
 | 
	
		
			
				|  |  | +							songsFound,
 | 
	
		
			
				|  |  | +							1,
 | 
	
		
			
				|  |  | +							(songId, next) => {
 | 
	
		
			
				|  |  | +								SongsModule.runJob("UPDATE_SONG", { songId });
 | 
	
		
			
				|  |  | +								next();
 | 
	
		
			
				|  |  | +							},
 | 
	
		
			
				|  |  | +							next
 | 
	
		
			
				|  |  | +						);
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +			],
 | 
	
		
			
				|  |  | +			async err => {
 | 
	
		
			
				|  |  | +				if (err && err !== true) {
 | 
	
		
			
				|  |  | +					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 | 
	
		
			
				|  |  | +					this.log("ERROR", "EDIT_ARTISTS", `User ${session.userId} failed to edit artists. '${err}'`);
 | 
	
		
			
				|  |  | +					cb({ status: "error", message: err });
 | 
	
		
			
				|  |  | +				} else {
 | 
	
		
			
				|  |  | +					this.log("SUCCESS", "EDIT_ARTISTS", `User ${session.userId} has successfully edited artists.`);
 | 
	
		
			
				|  |  | +					cb({
 | 
	
		
			
				|  |  | +						status: "success",
 | 
	
		
			
				|  |  | +						message: "Successfully edited artists."
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +		);
 | 
	
		
			
				|  |  | +	}),
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	/**
 | 
	
		
			
				|  |  |  	 * Gets a list of all tags
 | 
	
		
			
				|  |  |  	 *
 | 
	
		
			
				|  |  |  	 * @param session
 | 
	
		
			
				|  |  |  	 * @param cb
 | 
	
		
			
				|  |  |  	 */
 | 
	
		
			
				|  |  | -	getTags: isAdminRequired(function getModule(session, cb) {
 | 
	
		
			
				|  |  | +	getTags: isAdminRequired(function getTags(session, cb) {
 | 
	
		
			
				|  |  |  		async.waterfall(
 | 
	
		
			
				|  |  |  			[
 | 
	
		
			
				|  |  |  				next => {
 | 
	
	
		
			
				|  | @@ -1844,5 +1974,70 @@ export default {
 | 
	
		
			
				|  |  |  				}
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  		);
 | 
	
		
			
				|  |  | +	}),
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/**
 | 
	
		
			
				|  |  | +	 * Bulk update tags for selected songs
 | 
	
		
			
				|  |  | +	 *
 | 
	
		
			
				|  |  | +	 * @param session
 | 
	
		
			
				|  |  | +	 * @param method Whether to add, remove or replace tags
 | 
	
		
			
				|  |  | +	 * @param tags Array of tags to apply
 | 
	
		
			
				|  |  | +	 * @param songIds Array of songIds to apply tags to
 | 
	
		
			
				|  |  | +	 * @param cb
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	editTags: isAdminRequired(async function editTags(session, method, tags, songIds, cb) {
 | 
	
		
			
				|  |  | +		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
 | 
	
		
			
				|  |  | +		async.waterfall(
 | 
	
		
			
				|  |  | +			[
 | 
	
		
			
				|  |  | +				next => {
 | 
	
		
			
				|  |  | +					songModel.find({ _id: { $in: songIds } }, next);
 | 
	
		
			
				|  |  | +				},
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				(songs, next) => {
 | 
	
		
			
				|  |  | +					const songsFound = songs.map(song => song._id);
 | 
	
		
			
				|  |  | +					if (songsFound.length > 0) next(null, songsFound);
 | 
	
		
			
				|  |  | +					else next("None of the specified songs were found.");
 | 
	
		
			
				|  |  | +				},
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				(songsFound, next) => {
 | 
	
		
			
				|  |  | +					const query = {};
 | 
	
		
			
				|  |  | +					if (method === "add") {
 | 
	
		
			
				|  |  | +						query.$push = { tags: { $each: tags } };
 | 
	
		
			
				|  |  | +					} else if (method === "remove") {
 | 
	
		
			
				|  |  | +						query.$pullAll = { tags };
 | 
	
		
			
				|  |  | +					} else if (method === "replace") {
 | 
	
		
			
				|  |  | +						query.$set = { tags };
 | 
	
		
			
				|  |  | +					} else {
 | 
	
		
			
				|  |  | +						next("Invalid method.");
 | 
	
		
			
				|  |  | +					}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					songModel.updateMany({ _id: { $in: songsFound } }, query, { runValidators: true }, err => {
 | 
	
		
			
				|  |  | +						if (err) next(err);
 | 
	
		
			
				|  |  | +						async.eachLimit(
 | 
	
		
			
				|  |  | +							songsFound,
 | 
	
		
			
				|  |  | +							1,
 | 
	
		
			
				|  |  | +							(songId, next) => {
 | 
	
		
			
				|  |  | +								SongsModule.runJob("UPDATE_SONG", { songId });
 | 
	
		
			
				|  |  | +								next();
 | 
	
		
			
				|  |  | +							},
 | 
	
		
			
				|  |  | +							next
 | 
	
		
			
				|  |  | +						);
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +			],
 | 
	
		
			
				|  |  | +			async err => {
 | 
	
		
			
				|  |  | +				if (err && err !== true) {
 | 
	
		
			
				|  |  | +					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 | 
	
		
			
				|  |  | +					this.log("ERROR", "EDIT_TAGS", `User ${session.userId} failed to edit tags. '${err}'`);
 | 
	
		
			
				|  |  | +					cb({ status: "error", message: err });
 | 
	
		
			
				|  |  | +				} else {
 | 
	
		
			
				|  |  | +					this.log("SUCCESS", "EDIT_TAGS", `User ${session.userId} has successfully edited tags.`);
 | 
	
		
			
				|  |  | +					cb({
 | 
	
		
			
				|  |  | +						status: "success",
 | 
	
		
			
				|  |  | +						message: "Successfully edited tags."
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +		);
 | 
	
		
			
				|  |  |  	})
 | 
	
		
			
				|  |  |  };
 |