Преглед изворни кода

refactor: Grouped and renamed permissions

Owen Diffey пре 1 година
родитељ
комит
1f540c964b

+ 1 - 1
backend/logic/actions/dataRequests.js

@@ -40,7 +40,7 @@ export default {
 	 * @param cb
 	 */
 	getData: useHasPermission(
-		"dataRequests.getData",
+		"admin.view.users",
 		async function getData(session, page, pageSize, properties, sort, queries, operator, cb) {
 			async.waterfall(
 				[

+ 1 - 1
backend/logic/actions/media.js

@@ -850,7 +850,7 @@ export default {
 	 * @param cb
 	 */
 	getImportJobs: useHasPermission(
-		"media.getImportJobs",
+		"admin.view.import",
 		async function getImportJobs(session, page, pageSize, properties, sort, queries, operator, cb) {
 			async.waterfall(
 				[

+ 1 - 1
backend/logic/actions/news.js

@@ -70,7 +70,7 @@ export default {
 	 * @param cb
 	 */
 	getData: useHasPermission(
-		"news.getData",
+		"admin.view.news",
 		async function getSet(session, page, pageSize, properties, sort, queries, operator, cb) {
 			async.waterfall(
 				[

+ 264 - 279
backend/logic/actions/playlists.js

@@ -277,7 +277,7 @@ export default {
 	 * @param cb
 	 */
 	getData: useHasPermission(
-		"playlists.getData",
+		"admin.view.playlists",
 		async function getSet(session, page, pageSize, properties, sort, queries, operator, cb) {
 			async.waterfall(
 				[
@@ -437,44 +437,41 @@ export default {
 	 * @param {string} query - the page
 	 * @param {Function} cb - gets called with the result
 	 */
-	searchOfficial: useHasPermission(
-		"playlists.searchOfficial",
-		async function searchOfficial(session, query, page, cb) {
-			async.waterfall(
-				[
-					next => {
-						if ((!query && query !== "") || typeof query !== "string") next("Invalid query.");
-						else next();
-					},
+	searchOfficial: useHasPermission("playlists.get", async function searchOfficial(session, query, page, cb) {
+		async.waterfall(
+			[
+				next => {
+					if ((!query && query !== "") || typeof query !== "string") next("Invalid query.");
+					else next();
+				},
 
-					next => {
-						PlaylistsModule.runJob("SEARCH", {
-							query,
-							includeGenre: true,
-							includePrivate: true,
-							includeSongs: true,
-							page
+				next => {
+					PlaylistsModule.runJob("SEARCH", {
+						query,
+						includeGenre: true,
+						includePrivate: true,
+						includeSongs: true,
+						page
+					})
+						.then(response => {
+							next(null, response);
 						})
-							.then(response => {
-								next(null, response);
-							})
-							.catch(err => {
-								next(err);
-							});
-					}
-				],
-				async (err, data) => {
-					if (err) {
-						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-						this.log("ERROR", "PLAYLISTS_SEARCH_OFFICIAL", `Searching playlists failed. "${err}"`);
-						return cb({ status: "error", message: err });
-					}
-					this.log("SUCCESS", "PLAYLISTS_SEARCH_OFFICIAL", "Searching playlists successful.");
-					return cb({ status: "success", data });
+						.catch(err => {
+							next(err);
+						});
 				}
-			);
-		}
-	),
+			],
+			async (err, data) => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+					this.log("ERROR", "PLAYLISTS_SEARCH_OFFICIAL", `Searching playlists failed. "${err}"`);
+					return cb({ status: "error", message: err });
+				}
+				this.log("SUCCESS", "PLAYLISTS_SEARCH_OFFICIAL", "Searching playlists successful.");
+				return cb({ status: "success", data });
+			}
+		);
+	}),
 
 	/**
 	 * Gets the first song from a private playlist
@@ -821,7 +818,7 @@ export default {
 				(playlist, next) => {
 					if (!playlist) return next("Playlist not found");
 					if (playlist.privacy !== "public" && playlist.createdBy !== session.userId)
-						return hasPermission("playlists.getPlaylist", session)
+						return hasPermission("playlists.get", session)
 							.then(() => next(null, playlist))
 							.catch(() => next("User unauthorised to view playlist."));
 					return next(null, playlist);
@@ -872,7 +869,7 @@ export default {
 				(playlist, next) => {
 					if (!playlist) return next("Playlist not found");
 					if (playlist.privacy !== "public")
-						return hasPermission("stations.getPlaylist", session, stationId)
+						return hasPermission("stations.view", session, stationId)
 							.then(() => next(null, playlist))
 							.catch(() => next("User unauthorised to view playlist."));
 					return next(null, playlist);
@@ -1002,7 +999,7 @@ export default {
 						.then(playlist => {
 							if (!playlist) return next("Playlist not found.");
 							if (playlist.createdBy !== session.userId)
-								return hasPermission("playlists.repositionSong", session)
+								return hasPermission("playlists.songs.reposition", session)
 									.then(() => next())
 									.catch(() => next("Invalid permissions."));
 							return next();
@@ -1090,7 +1087,7 @@ export default {
 						.then(playlist => {
 							if (!playlist) return next("Playlist not found.");
 							if (playlist.createdBy !== session.userId)
-								return hasPermission("playlists.addSongToPlaylist", session)
+								return hasPermission("playlists.songs.add", session)
 									.then(() => next(null, playlist))
 									.catch(() => next("Invalid permissions."));
 							return next(null, playlist);
@@ -1357,7 +1354,7 @@ export default {
 					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.addSetToPlaylist", session)
+						return hasPermission("playlists.songs.add", session)
 							.then(() => next(null, playlist))
 							.catch(() => next("Invalid permissions."));
 					return next(null, playlist);
@@ -1436,7 +1433,7 @@ export default {
 						.then(playlist => {
 							if (!playlist) return next("Playlist not found.");
 							if (playlist.createdBy !== session.userId)
-								return hasPermission("playlists.removeSongFromPlaylist", session)
+								return hasPermission("playlists.songs.remove", session)
 									.then(() => next(null, playlist))
 									.catch(() => next("Invalid permissions."));
 							return next(null, playlist);
@@ -1905,7 +1902,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	updatePrivacyAdmin: useHasPermission(
-		"playlists.updatePrivacy",
+		"playlists.update.privacy",
 		async function updatePrivacyAdmin(session, playlistId, privacy, cb) {
 			const playlistModel = await DBModule.runJob("GET_MODEL", { modelName: "playlist" }, this);
 
@@ -1979,62 +1976,59 @@ export default {
 	 * @param {object} session - the session object automatically added by socket.io
 	 * @param {Function} cb - gets called with the result
 	 */
-	deleteOrphanedStationPlaylists: useHasPermission(
-		"playlists.deleteOrphanedStationPlaylists",
-		async function index(session, cb) {
-			this.keepLongJob();
-			this.publishProgress({
-				status: "started",
-				title: "Delete orphaned station playlists",
-				message: "Deleting orphaned station playlists.",
-				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
-			);
+	deleteOrphanedStationPlaylists: useHasPermission("playlists.deleteOrphaned", async function index(session, cb) {
+		this.keepLongJob();
+		this.publishProgress({
+			status: "started",
+			title: "Delete orphaned station playlists",
+			message: "Deleting orphaned station playlists.",
+			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
+		);
 
-			async.waterfall(
-				[
-					next => {
-						PlaylistsModule.runJob("DELETE_ORPHANED_STATION_PLAYLISTS", {}, this)
-							.then(() => next())
-							.catch(next);
-					}
-				],
-				async err => {
-					if (err) {
-						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-						this.log(
-							"ERROR",
-							"PLAYLISTS_DELETE_ORPHANED_STATION_PLAYLISTS",
-							`Deleting orphaned station playlists failed. "${err}"`
-						);
-						this.publishProgress({
-							status: "error",
-							message: err
-						});
-						return cb({ status: "error", message: err });
-					}
+		async.waterfall(
+			[
+				next => {
+					PlaylistsModule.runJob("DELETE_ORPHANED_STATION_PLAYLISTS", {}, this)
+						.then(() => next())
+						.catch(next);
+				}
+			],
+			async err => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
-						"SUCCESS",
+						"ERROR",
 						"PLAYLISTS_DELETE_ORPHANED_STATION_PLAYLISTS",
-						"Deleting orphaned station playlists successful."
+						`Deleting orphaned station playlists failed. "${err}"`
 					);
 					this.publishProgress({
-						status: "success",
-						message: "Successfully deleted orphaned station playlists."
+						status: "error",
+						message: err
 					});
-					return cb({ status: "success", message: "Successfully deleted orphaned station playlists." });
+					return cb({ status: "error", message: err });
 				}
-			);
-		}
-	),
+				this.log(
+					"SUCCESS",
+					"PLAYLISTS_DELETE_ORPHANED_STATION_PLAYLISTS",
+					"Deleting orphaned station playlists successful."
+				);
+				this.publishProgress({
+					status: "success",
+					message: "Successfully deleted orphaned station playlists."
+				});
+				return cb({ status: "success", message: "Successfully deleted orphaned station playlists." });
+			}
+		);
+	}),
 
 	/**
 	 * Deletes all orphaned genre playlists
@@ -2042,62 +2036,59 @@ export default {
 	 * @param {object} session - the session object automatically added by socket.io
 	 * @param {Function} cb - gets called with the result
 	 */
-	deleteOrphanedGenrePlaylists: useHasPermission(
-		"playlists.deleteOrphanedGenrePlaylists",
-		async function index(session, cb) {
-			this.keepLongJob();
-			this.publishProgress({
-				status: "started",
-				title: "Delete orphaned genre playlists",
-				message: "Deleting orphaned genre playlists.",
-				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
-			);
+	deleteOrphanedGenrePlaylists: useHasPermission("playlists.deleteOrphaned", async function index(session, cb) {
+		this.keepLongJob();
+		this.publishProgress({
+			status: "started",
+			title: "Delete orphaned genre playlists",
+			message: "Deleting orphaned genre playlists.",
+			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
+		);
 
-			async.waterfall(
-				[
-					next => {
-						PlaylistsModule.runJob("DELETE_ORPHANED_GENRE_PLAYLISTS", {}, this)
-							.then(() => next())
-							.catch(next);
-					}
-				],
-				async err => {
-					if (err) {
-						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-						this.log(
-							"ERROR",
-							"PLAYLISTS_DELETE_ORPHANED_GENRE_PLAYLISTS",
-							`Deleting orphaned genre playlists failed. "${err}"`
-						);
-						this.publishProgress({
-							status: "error",
-							message: err
-						});
-						return cb({ status: "error", message: err });
-					}
+		async.waterfall(
+			[
+				next => {
+					PlaylistsModule.runJob("DELETE_ORPHANED_GENRE_PLAYLISTS", {}, this)
+						.then(() => next())
+						.catch(next);
+				}
+			],
+			async err => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
-						"SUCCESS",
+						"ERROR",
 						"PLAYLISTS_DELETE_ORPHANED_GENRE_PLAYLISTS",
-						"Deleting orphaned genre playlists successful."
+						`Deleting orphaned genre playlists failed. "${err}"`
 					);
 					this.publishProgress({
-						status: "success",
-						message: "Successfully deleted orphaned genre playlists."
+						status: "error",
+						message: err
 					});
-					return cb({ status: "success", message: "Successfully deleted orphaned genre playlists." });
+					return cb({ status: "error", message: err });
 				}
-			);
-		}
-	),
+				this.log(
+					"SUCCESS",
+					"PLAYLISTS_DELETE_ORPHANED_GENRE_PLAYLISTS",
+					"Deleting orphaned genre playlists successful."
+				);
+				this.publishProgress({
+					status: "success",
+					message: "Successfully deleted orphaned genre playlists."
+				});
+				return cb({ status: "success", message: "Successfully deleted orphaned genre playlists." });
+			}
+		);
+	}),
 
 	/**
 	 * Requests orpahned playlist songs
@@ -2170,7 +2161,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	clearAndRefillStationPlaylist: useHasPermission(
-		"playlists.clearAndRefillStationPlaylist",
+		"playlists.clearAndRefill",
 		async function index(session, playlistId, cb) {
 			async.waterfall(
 				[
@@ -2223,7 +2214,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	clearAndRefillGenrePlaylist: useHasPermission(
-		"playlists.clearAndRefillGenrePlaylist",
+		"playlists.clearAndRefill",
 		async function index(session, playlistId, cb) {
 			async.waterfall(
 				[
@@ -2275,7 +2266,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	clearAndRefillAllStationPlaylists: useHasPermission(
-		"playlists.clearAndRefillAllStationPlaylists",
+		"playlists.clearAndRefillAll",
 		async function index(session, cb) {
 			this.keepLongJob();
 			this.publishProgress({
@@ -2371,96 +2362,93 @@ export default {
 	 * @param {object} session - the session object automatically added by socket.io
 	 * @param {Function} cb - gets called with the result
 	 */
-	clearAndRefillAllGenrePlaylists: useHasPermission(
-		"playlists.clearAndRefillAllGenrePlaylists",
-		async function index(session, cb) {
-			this.keepLongJob();
-			this.publishProgress({
-				status: "started",
-				title: "Clear and refill all genre playlists",
-				message: "Clearing and refilling all genre playlists.",
-				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 }
+	clearAndRefillAllGenrePlaylists: useHasPermission("playlists.clearAndRefillAll", async function index(session, cb) {
+		this.keepLongJob();
+		this.publishProgress({
+			status: "started",
+			title: "Clear and refill all genre playlists",
+			message: "Clearing and refilling all genre playlists.",
+			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
+		);
+
+		async.waterfall(
+			[
+				next => {
+					PlaylistsModule.runJob("GET_ALL_GENRE_PLAYLISTS", {}, this)
+						.then(response => {
+							next(null, response.playlists);
+						})
+						.catch(err => {
+							next(err);
+						});
 				},
-				this
-			);
 
-			async.waterfall(
-				[
-					next => {
-						PlaylistsModule.runJob("GET_ALL_GENRE_PLAYLISTS", {}, this)
-							.then(response => {
-								next(null, response.playlists);
-							})
-							.catch(err => {
-								next(err);
+				(playlists, next) => {
+					async.eachLimit(
+						playlists,
+						1,
+						(playlist, next) => {
+							this.publishProgress({
+								status: "update",
+								message: `Clearing and refilling "${playlist._id}"`
 							});
-					},
-
-					(playlists, next) => {
-						async.eachLimit(
-							playlists,
-							1,
-							(playlist, next) => {
-								this.publishProgress({
-									status: "update",
-									message: `Clearing and refilling "${playlist._id}"`
+							PlaylistsModule.runJob(
+								"CLEAR_AND_REFILL_GENRE_PLAYLIST",
+								{ playlistId: playlist._id },
+								this
+							)
+								.then(() => {
+									next();
+								})
+								.catch(err => {
+									next(err);
 								});
-								PlaylistsModule.runJob(
-									"CLEAR_AND_REFILL_GENRE_PLAYLIST",
-									{ playlistId: playlist._id },
-									this
-								)
-									.then(() => {
-										next();
-									})
-									.catch(err => {
-										next(err);
-									});
-							},
-							next
-						);
-					}
-				],
-				async err => {
-					if (err) {
-						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-
-						this.log(
-							"ERROR",
-							"PLAYLIST_CLEAR_AND_REFILL_ALL_GENRE_PLAYLISTS",
-							`Clearing and refilling all genre playlists failed for user "${session.userId}". "${err}"`
-						);
-						this.publishProgress({
-							status: "error",
-							message: err
-						});
-						return cb({ status: "error", message: err });
-					}
+						},
+						next
+					);
+				}
+			],
+			async err => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 
 					this.log(
-						"SUCCESS",
+						"ERROR",
 						"PLAYLIST_CLEAR_AND_REFILL_ALL_GENRE_PLAYLISTS",
-						`Successfully cleared and refilled all genre playlists for user "${session.userId}".`
+						`Clearing and refilling all genre playlists failed for user "${session.userId}". "${err}"`
 					);
 					this.publishProgress({
-						status: "success",
-						message: "Playlists have been successfully cleared and refilled."
-					});
-					return cb({
-						status: "success",
-						message: "Playlists have been successfully cleared and refilled"
+						status: "error",
+						message: err
 					});
+					return cb({ status: "error", message: err });
 				}
-			);
-		}
-	),
+
+				this.log(
+					"SUCCESS",
+					"PLAYLIST_CLEAR_AND_REFILL_ALL_GENRE_PLAYLISTS",
+					`Successfully cleared and refilled all genre playlists for user "${session.userId}".`
+				);
+				this.publishProgress({
+					status: "success",
+					message: "Playlists have been successfully cleared and refilled."
+				});
+				return cb({
+					status: "success",
+					message: "Playlists have been successfully cleared and refilled"
+				});
+			}
+		);
+	}),
 
 	/**
 	 * Create missing genre playlists
@@ -2468,69 +2456,66 @@ export default {
 	 * @param {object} session - the session object automatically added by socket.io
 	 * @param {Function} cb - gets called with the result
 	 */
-	createMissingGenrePlaylists: useHasPermission(
-		"playlists.createMissingGenrePlaylists",
-		async function index(session, cb) {
-			this.keepLongJob();
-			this.publishProgress({
-				status: "started",
-				title: "Create missing genre playlists",
-				message: "Creating missing genre playlists.",
-				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
-			);
-
-			async.waterfall(
-				[
-					next => {
-						PlaylistsModule.runJob("CREATE_MISSING_GENRE_PLAYLISTS", this)
-							.then(() => {
-								next();
-							})
-							.catch(err => {
-								next(err);
-							});
-					}
-				],
-				async err => {
-					if (err) {
-						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+	createMissingGenrePlaylists: useHasPermission("playlists.createMissing", async function index(session, cb) {
+		this.keepLongJob();
+		this.publishProgress({
+			status: "started",
+			title: "Create missing genre playlists",
+			message: "Creating missing genre playlists.",
+			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.log(
-							"ERROR",
-							"PLAYLIST_CREATE_MISSING_GENRE_PLAYLISTS",
-							`Creating missing genre playlists failed for user "${session.userId}". "${err}"`
-						);
-						this.publishProgress({
-							status: "error",
-							message: err
+		async.waterfall(
+			[
+				next => {
+					PlaylistsModule.runJob("CREATE_MISSING_GENRE_PLAYLISTS", this)
+						.then(() => {
+							next();
+						})
+						.catch(err => {
+							next(err);
 						});
-						return cb({ status: "error", message: err });
-					}
+				}
+			],
+			async err => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 
 					this.log(
-						"SUCCESS",
+						"ERROR",
 						"PLAYLIST_CREATE_MISSING_GENRE_PLAYLISTS",
-						`Successfully created missing genre playlists for user "${session.userId}".`
+						`Creating missing genre playlists failed for user "${session.userId}". "${err}"`
 					);
 					this.publishProgress({
-						status: "success",
-						message: "Missing genre playlists have been successfully created."
-					});
-					return cb({
-						status: "success",
-						message: "Missing genre playlists have been successfully created"
+						status: "error",
+						message: err
 					});
+					return cb({ status: "error", message: err });
 				}
-			);
-		}
-	)
+
+				this.log(
+					"SUCCESS",
+					"PLAYLIST_CREATE_MISSING_GENRE_PLAYLISTS",
+					`Successfully created missing genre playlists for user "${session.userId}".`
+				);
+				this.publishProgress({
+					status: "success",
+					message: "Missing genre playlists have been successfully created."
+				});
+				return cb({
+					status: "success",
+					message: "Missing genre playlists have been successfully created"
+				});
+			}
+		);
+	})
 };

+ 3 - 3
backend/logic/actions/punishments.js

@@ -41,7 +41,7 @@ export default {
 	 * @param cb
 	 */
 	getData: useHasPermission(
-		"punishments.getData",
+		"admin.view.punishments",
 		async function getSet(session, page, pageSize, properties, sort, queries, operator, cb) {
 			async.waterfall(
 				[
@@ -206,7 +206,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	getPunishmentsForUser: useHasPermission(
-		"punishments.getPunishmentsForUser",
+		"punishments.get",
 		async function getPunishmentsForUser(session, userId, cb) {
 			const punishmentModel = await DBModule.runJob("GET_MODEL", { modelName: "punishment" }, this);
 
@@ -236,7 +236,7 @@ export default {
 	 * @param {string} punishmentId - the punishment id
 	 * @param {Function} cb - gets called with the result
 	 */
-	findOne: useHasPermission("punishments.findOne", async function findOne(session, punishmentId, cb) {
+	findOne: useHasPermission("punishments.get", async function findOne(session, punishmentId, cb) {
 		const punishmentModel = await DBModule.runJob("GET_MODEL", { modelName: "punishment" }, this);
 
 		async.waterfall([next => punishmentModel.findOne({ _id: punishmentId }, next)], async (err, punishment) => {

+ 54 - 64
backend/logic/actions/reports.js

@@ -101,7 +101,7 @@ export default {
 	 * @param cb
 	 */
 	getData: useHasPermission(
-		"reports.getData",
+		"admin.view.reports",
 		async function getSet(session, page, pageSize, properties, sort, queries, operator, cb) {
 			async.waterfall(
 				[
@@ -200,7 +200,7 @@ export default {
 	 * @param {string} reportId - the id of the report to return
 	 * @param {Function} cb - gets called with the result
 	 */
-	findOne: useHasPermission("reports.findOne", async function findOne(session, reportId, cb) {
+	findOne: useHasPermission("reports.get", async function findOne(session, reportId, cb) {
 		const reportModel = await DBModule.runJob("GET_MODEL", { modelName: "report" }, this);
 		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
 
@@ -249,70 +249,60 @@ export default {
 	 * @param {string} songId - the id of the song to index reports for
 	 * @param {Function} cb - gets called with the result
 	 */
-	getReportsForSong: useHasPermission(
-		"reports.getReportsForSong",
-		async function getReportsForSong(session, songId, cb) {
-			const reportModel = await DBModule.runJob("GET_MODEL", { modelName: "report" }, this);
-			const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
+	getReportsForSong: useHasPermission("reports.get", async function getReportsForSong(session, songId, cb) {
+		const reportModel = await DBModule.runJob("GET_MODEL", { modelName: "report" }, this);
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
 
-			async.waterfall(
-				[
-					next =>
-						reportModel
-							.find({ "song._id": songId, resolved: false })
-							.sort({ createdAt: "desc" })
-							.exec(next),
-
-					(_reports, next) => {
-						const reports = [];
-
-						async.each(
-							_reports,
-							(report, cb) => {
-								userModel
-									.findById(report.createdBy)
-									.select({ avatar: -1, name: -1, username: -1 })
-									.exec((err, user) => {
-										if (!user)
-											reports.push({
-												...report._doc,
-												createdBy: { _id: report.createdBy }
-											});
-										else
-											reports.push({
-												...report._doc,
-												createdBy: {
-													avatar: user.avatar,
-													name: user.name,
-													username: user.username,
-													_id: report.createdBy
-												}
-											});
+		async.waterfall(
+			[
+				next =>
+					reportModel.find({ "song._id": songId, resolved: false }).sort({ createdAt: "desc" }).exec(next),
 
-										return cb(err);
-									});
-							},
-							err => next(err, reports)
-						);
-					}
-				],
-				async (err, reports) => {
-					if (err) {
-						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-						this.log(
-							"ERROR",
-							"GET_REPORTS_FOR_SONG",
-							`Indexing reports for song "${songId}" failed. "${err}"`
-						);
-						return cb({ status: "error", message: err });
-					}
+				(_reports, next) => {
+					const reports = [];
+
+					async.each(
+						_reports,
+						(report, cb) => {
+							userModel
+								.findById(report.createdBy)
+								.select({ avatar: -1, name: -1, username: -1 })
+								.exec((err, user) => {
+									if (!user)
+										reports.push({
+											...report._doc,
+											createdBy: { _id: report.createdBy }
+										});
+									else
+										reports.push({
+											...report._doc,
+											createdBy: {
+												avatar: user.avatar,
+												name: user.name,
+												username: user.username,
+												_id: report.createdBy
+											}
+										});
 
-					this.log("SUCCESS", "GET_REPORTS_FOR_SONG", `Indexing reports for song "${songId}" successful.`);
-					return cb({ status: "success", data: { reports } });
+									return cb(err);
+								});
+						},
+						err => next(err, reports)
+					);
 				}
-			);
-		}
-	),
+			],
+			async (err, reports) => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+					this.log("ERROR", "GET_REPORTS_FOR_SONG", `Indexing reports for song "${songId}" failed. "${err}"`);
+					return cb({ status: "error", message: err });
+				}
+
+				this.log("SUCCESS", "GET_REPORTS_FOR_SONG", `Indexing reports for song "${songId}" successful.`);
+				return cb({ status: "success", data: { reports } });
+			}
+		);
+	}),
 
 	/**
 	 * Gets all a users reports for a specific songId
@@ -396,7 +386,7 @@ export default {
 	 * @param {boolean} resolved - whether to set to resolved to true or false
 	 * @param {Function} cb - gets called with the result
 	 */
-	resolve: useHasPermission("reports.resolve", async function resolve(session, reportId, resolved, cb) {
+	resolve: useHasPermission("reports.update", async function resolve(session, reportId, resolved, cb) {
 		const reportModel = await DBModule.runJob("GET_MODEL", { modelName: "report" }, this);
 
 		async.waterfall(
@@ -461,7 +451,7 @@ export default {
 	 * @param {string} issueId - the id of the issue within the report
 	 * @param {Function} cb - gets called with the result
 	 */
-	toggleIssue: useHasPermission("reports.toggleIssue", async function toggleIssue(session, reportId, issueId, cb) {
+	toggleIssue: useHasPermission("reports.update", async function toggleIssue(session, reportId, issueId, cb) {
 		const reportModel = await DBModule.runJob("GET_MODEL", { modelName: "report" }, this);
 
 		async.waterfall(

+ 123 - 129
backend/logic/actions/songs.js

@@ -48,7 +48,7 @@ export default {
 	 * @param {object} session - the session object automatically added by the websocket
 	 * @param cb
 	 */
-	length: useHasPermission("songs.length", async function length(session, cb) {
+	length: useHasPermission("songs.get", async function length(session, cb) {
 		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
 		async.waterfall(
 			[
@@ -81,7 +81,7 @@ export default {
 	 * @param cb
 	 */
 	getData: useHasPermission(
-		"songs.getData",
+		"admin.view.songs",
 		async function getSet(session, page, pageSize, properties, sort, queries, operator, cb) {
 			async.waterfall(
 				[
@@ -270,7 +270,7 @@ export default {
 	 * @param {string} songId - the song id
 	 * @param {Function} cb
 	 */
-	getSongFromSongId: useHasPermission("songs.getSongFromId", function getSongFromSongId(session, songId, cb) {
+	getSongFromSongId: useHasPermission("songs.get", function getSongFromSongId(session, songId, cb) {
 		async.waterfall(
 			[
 				next => {
@@ -299,45 +299,42 @@ export default {
 	 * @param {Array} youtubeIds - the song ids
 	 * @param {Function} cb
 	 */
-	getSongsFromYoutubeIds: useHasPermission(
-		"songs.getSongsFromYoutubeIds",
-		function getSongsFromYoutubeIds(session, youtubeIds, cb) {
-			async.waterfall(
-				[
-					next => {
-						SongsModule.runJob(
-							"GET_SONGS",
-							{
-								youtubeIds,
-								properties: [
-									"youtubeId",
-									"title",
-									"artists",
-									"thumbnail",
-									"duration",
-									"verified",
-									"_id",
-									"youtubeVideoId"
-								]
-							},
-							this
-						)
-							.then(response => next(null, response.songs))
-							.catch(err => next(err));
-					}
-				],
-				async (err, songs) => {
-					if (err) {
-						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-						this.log("ERROR", "SONGS_GET_SONGS_FROM_MUSARE_IDS", `Failed to get songs. "${err}"`);
-						return cb({ status: "error", message: err });
-					}
-					this.log("SUCCESS", "SONGS_GET_SONGS_FROM_MUSARE_IDS", `Got songs successfully.`);
-					return cb({ status: "success", data: { songs } });
+	getSongsFromYoutubeIds: useHasPermission("songs.get", function getSongsFromYoutubeIds(session, youtubeIds, cb) {
+		async.waterfall(
+			[
+				next => {
+					SongsModule.runJob(
+						"GET_SONGS",
+						{
+							youtubeIds,
+							properties: [
+								"youtubeId",
+								"title",
+								"artists",
+								"thumbnail",
+								"duration",
+								"verified",
+								"_id",
+								"youtubeVideoId"
+							]
+						},
+						this
+					)
+						.then(response => next(null, response.songs))
+						.catch(err => next(err));
 				}
-			);
-		}
-	),
+			],
+			async (err, songs) => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+					this.log("ERROR", "SONGS_GET_SONGS_FROM_MUSARE_IDS", `Failed to get songs. "${err}"`);
+					return cb({ status: "error", message: err });
+				}
+				this.log("SUCCESS", "SONGS_GET_SONGS_FROM_MUSARE_IDS", `Got songs successfully.`);
+				return cb({ status: "success", data: { songs } });
+			}
+		);
+	}),
 
 	/**
 	 * Creates a song
@@ -724,7 +721,7 @@ export default {
 	 * @param songIds - array of song ids
 	 * @param cb
 	 */
-	removeMany: useHasPermission("songs.removeMany", async function remove(session, songIds, cb) {
+	removeMany: useHasPermission("songs.remove", async function remove(session, songIds, cb) {
 		const successful = [];
 		const failed = [];
 
@@ -926,7 +923,7 @@ export default {
 	 * @param songIds - array of song ids
 	 * @param cb
 	 */
-	verifyMany: useHasPermission("songs.verifyMany", async function verifyMany(session, songIds, cb) {
+	verifyMany: useHasPermission("songs.verify", async function verifyMany(session, songIds, cb) {
 		const successful = [];
 		const failed = [];
 
@@ -1022,7 +1019,7 @@ export default {
 	 * @param songId - the song id
 	 * @param cb
 	 */
-	unverify: useHasPermission("songs.unverify", async function add(session, songId, cb) {
+	unverify: useHasPermission("songs.verify", async function add(session, songId, cb) {
 		const SongModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
 		async.waterfall(
 			[
@@ -1087,7 +1084,7 @@ export default {
 	 * @param songIds - array of song ids
 	 * @param cb
 	 */
-	unverifyMany: useHasPermission("songs.unverifyMany", async function unverifyMany(session, songIds, cb) {
+	unverifyMany: useHasPermission("songs.verify", async function unverifyMany(session, songIds, cb) {
 		const successful = [];
 		const failed = [];
 
@@ -1190,7 +1187,7 @@ export default {
 	 * @param session
 	 * @param cb
 	 */
-	getGenres: useHasPermission("songs.getGenres", function getGenres(session, cb) {
+	getGenres: useHasPermission("songs.get", function getGenres(session, cb) {
 		async.waterfall(
 			[
 				next => {
@@ -1229,7 +1226,7 @@ export default {
 	 * @param songIds Array of songIds to apply genres to
 	 * @param cb
 	 */
-	editGenres: useHasPermission("songs.editGenres", async function editGenres(session, method, genres, songIds, cb) {
+	editGenres: useHasPermission("songs.update", async function editGenres(session, method, genres, songIds, cb) {
 		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
 
 		this.keepLongJob();
@@ -1318,7 +1315,7 @@ export default {
 	 * @param session
 	 * @param cb
 	 */
-	getArtists: useHasPermission("songs.getArtists", function getArtists(session, cb) {
+	getArtists: useHasPermission("songs.get", function getArtists(session, cb) {
 		async.waterfall(
 			[
 				next => {
@@ -1357,91 +1354,88 @@ export default {
 	 * @param songIds Array of songIds to apply artists to
 	 * @param cb
 	 */
-	editArtists: useHasPermission(
-		"songs.editArtists",
-		async function editArtists(session, method, artists, songIds, cb) {
-			const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
-
-			this.keepLongJob();
-			this.publishProgress({
-				status: "started",
-				title: "Bulk editing artists",
-				message: "Updating artists.",
-				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 }
+	editArtists: useHasPermission("songs.update", async function editArtists(session, method, artists, songIds, cb) {
+		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
+
+		this.keepLongJob();
+		this.publishProgress({
+			status: "started",
+			title: "Bulk editing artists",
+			message: "Updating artists.",
+			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
+		);
+
+		async.waterfall(
+			[
+				next => {
+					songModel.find({ _id: { $in: songIds } }, next);
 				},
-				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.$addToSet = { artists: { $each: artists } };
-						} else if (method === "remove") {
-							query.$pullAll = { artists };
-						} else if (method === "replace") {
-							query.$set = { artists };
-						} else {
-							next("Invalid method.");
-							return;
-						}
+				(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.");
+				},
 
-						this.publishProgress({
-							status: "update",
-							message: "Updating artists in MongoDB."
-						});
-						songModel.updateMany({ _id: { $in: songsFound } }, query, { runValidators: true }, err => {
-							if (err) {
-								next(err);
-								return;
-							}
-							SongsModule.runJob("UPDATE_SONGS", { songIds: songsFound });
-							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}'`);
-						this.publishProgress({
-							status: "error",
-							message: err
-						});
-						cb({ status: "error", message: err });
+				(songsFound, next) => {
+					const query = {};
+					if (method === "add") {
+						query.$addToSet = { artists: { $each: artists } };
+					} else if (method === "remove") {
+						query.$pullAll = { artists };
+					} else if (method === "replace") {
+						query.$set = { artists };
 					} else {
-						this.log("SUCCESS", "EDIT_ARTISTS", `User ${session.userId} has successfully edited artists.`);
-						this.publishProgress({
-							status: "success",
-							message: "Successfully edited artists."
-						});
-						cb({
-							status: "success",
-							message: "Successfully edited artists."
-						});
+						next("Invalid method.");
+						return;
 					}
+
+					this.publishProgress({
+						status: "update",
+						message: "Updating artists in MongoDB."
+					});
+					songModel.updateMany({ _id: { $in: songsFound } }, query, { runValidators: true }, err => {
+						if (err) {
+							next(err);
+							return;
+						}
+						SongsModule.runJob("UPDATE_SONGS", { songIds: songsFound });
+						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}'`);
+					this.publishProgress({
+						status: "error",
+						message: err
+					});
+					cb({ status: "error", message: err });
+				} else {
+					this.log("SUCCESS", "EDIT_ARTISTS", `User ${session.userId} has successfully edited artists.`);
+					this.publishProgress({
+						status: "success",
+						message: "Successfully edited artists."
+					});
+					cb({
+						status: "success",
+						message: "Successfully edited artists."
+					});
+				}
+			}
+		);
+	}),
 
 	/**
 	 * Gets a list of all tags
@@ -1449,7 +1443,7 @@ export default {
 	 * @param session
 	 * @param cb
 	 */
-	getTags: useHasPermission("songs.getTags", function getTags(session, cb) {
+	getTags: useHasPermission("songs.get", function getTags(session, cb) {
 		async.waterfall(
 			[
 				next => {
@@ -1488,7 +1482,7 @@ export default {
 	 * @param songIds Array of songIds to apply tags to
 	 * @param cb
 	 */
-	editTags: useHasPermission("songs.editTags", async function editTags(session, method, tags, songIds, cb) {
+	editTags: useHasPermission("songs.update", async function editTags(session, method, tags, songIds, cb) {
 		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
 
 		this.keepLongJob();

+ 11 - 11
backend/logic/actions/stations.js

@@ -457,7 +457,7 @@ export default {
 	 * @param cb
 	 */
 	getData: useHasPermission(
-		"stations.getData",
+		"admin.view.stations",
 		async function getSet(session, page, pageSize, properties, sort, queries, operator, cb) {
 			async.waterfall(
 				[
@@ -1426,7 +1426,7 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					hasPermission("stations.pause", session, stationId)
+					hasPermission("stations.playback.toggle", session, stationId)
 						.then(() => next())
 						.catch(next);
 				},
@@ -1495,7 +1495,7 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					hasPermission("stations.resume", session, stationId)
+					hasPermission("stations.playback.toggle", session, stationId)
 						.then(() => next())
 						.catch(next);
 				},
@@ -1834,7 +1834,7 @@ export default {
 						station.requests.access === "owner" ||
 						(station.requests.access === "user" && station.privacy === "private")
 					) {
-						return hasPermission("stations.addToQueue", session, stationId)
+						return hasPermission("stations.request", session, stationId)
 							.then(() => next(null, station))
 							.catch(() => next("You do not have permission to add songs to queue."));
 					}
@@ -1907,7 +1907,7 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					hasPermission("stations.removeFromQueue", session, stationId)
+					hasPermission("stations.queue.remove", session, stationId)
 						.then(() => next())
 						.catch(next);
 				},
@@ -2015,7 +2015,7 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					hasPermission("stations.repositionSongInQueue", session, stationId)
+					hasPermission("stations.queue.reposition", session, stationId)
 						.then(() => next())
 						.catch(next);
 				},
@@ -2099,7 +2099,7 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					hasPermission("stations.autofillPlaylist", session, stationId)
+					hasPermission("stations.autofill", session, stationId)
 						.then(() => next())
 						.catch(next);
 				},
@@ -2174,7 +2174,7 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					hasPermission("stations.removeAutofillPlaylist", session, stationId)
+					hasPermission("stations.autofill", session, stationId)
 						.then(() => next())
 						.catch(next);
 				},
@@ -2247,7 +2247,7 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					hasPermission("stations.blacklistPlaylist", session, stationId)
+					hasPermission("stations.blacklist", session, stationId)
 						.then(() => next())
 						.catch(next);
 				},
@@ -2320,7 +2320,7 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					hasPermission("stations.removeBlacklistedPlaylist", session, stationId)
+					hasPermission("stations.blacklist", session, stationId)
 						.then(() => next())
 						.catch(next);
 				},
@@ -2563,7 +2563,7 @@ export default {
 	 * @param {string} stationId - the station id
 	 * @param {Function} cb - gets called with the result
 	 */
-	resetQueue: useHasPermission("stations.resetQueue", async function resetQueue(session, stationId, cb) {
+	resetQueue: useHasPermission("stations.queue.reset", async function resetQueue(session, stationId, cb) {
 		async.waterfall(
 			[
 				next => {

+ 12 - 12
backend/logic/actions/users.js

@@ -545,7 +545,7 @@ export default {
 	 * @param {string} userId - the user id that is going to be banned
 	 * @param {Function} cb - gets called with the result
 	 */
-	adminRemove: useHasPermission("users.adminRemove", async function adminRemove(session, userId, cb) {
+	adminRemove: useHasPermission("users.remove", async function adminRemove(session, userId, cb) {
 		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
 		const dataRequestModel = await DBModule.runJob("GET_MODEL", { modelName: "dataRequest" }, this);
 		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
@@ -1246,7 +1246,7 @@ export default {
 			[
 				next => {
 					if (session.userId === userId) return next();
-					return hasPermission("users.removeSessions", session)
+					return hasPermission("users.remove.sessions", session)
 						.then(() => next())
 						.catch(() => next("Only admins and the owner of the account can remove their sessions."));
 				},
@@ -1855,7 +1855,7 @@ export default {
 	 * @param {string} userId - the userId of the person we are trying to get the username from
 	 * @param {Function} cb - gets called with the result
 	 */
-	getUserFromId: useHasPermission("users.getUserFromId", async function getUserFromId(session, userId, cb) {
+	getUserFromId: useHasPermission("users.get", async function getUserFromId(session, userId, cb) {
 		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
 		userModel
 			.findById(userId)
@@ -1985,7 +1985,7 @@ export default {
 			[
 				next => {
 					if (updatingUserId === session.userId) return next();
-					return hasPermission("users.updateUsername", session)
+					return hasPermission("users.update", session)
 						.then(() => next())
 						.catch(() => next("Invalid permissions."));
 				},
@@ -2077,7 +2077,7 @@ export default {
 			[
 				next => {
 					if (updatingUserId === session.userId) return next();
-					return hasPermission("users.updateEmail", session)
+					return hasPermission("users.update", session)
 						.then(() => next())
 						.catch(() => next("Invalid permissions."));
 				},
@@ -2187,7 +2187,7 @@ export default {
 			[
 				next => {
 					if (updatingUserId === session.userId) return next();
-					return hasPermission("users.updateName", session)
+					return hasPermission("users.update", session)
 						.then(() => next())
 						.catch(() => next("Invalid permissions."));
 				},
@@ -2257,7 +2257,7 @@ export default {
 			[
 				next => {
 					if (updatingUserId === session.userId) return next();
-					return hasPermission("users.updateLocation", session)
+					return hasPermission("users.update", session)
 						.then(() => next())
 						.catch(() => next("Invalid permissions."));
 				},
@@ -2327,7 +2327,7 @@ export default {
 			[
 				next => {
 					if (updatingUserId === session.userId) return next();
-					return hasPermission("users.updateBio", session)
+					return hasPermission("users.update", session)
 						.then(() => next())
 						.catch(() => next("Invalid permissions."));
 				},
@@ -2391,7 +2391,7 @@ export default {
 			[
 				next => {
 					if (updatingUserId === session.userId) return next();
-					return hasPermission("users.updateAvatar", session)
+					return hasPermission("users.update", session)
 						.then(() => next())
 						.catch(() => next("Invalid permissions."));
 				},
@@ -2452,7 +2452,7 @@ export default {
 	 * @param {string} newRole - the new role
 	 * @param {Function} cb - gets called with the result
 	 */
-	updateRole: useHasPermission("users.updateRole", async function updateRole(session, updatingUserId, newRole, cb) {
+	updateRole: useHasPermission("users.update", async function updateRole(session, updatingUserId, newRole, cb) {
 		newRole = newRole.toLowerCase();
 		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
 
@@ -2984,7 +2984,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	adminRequestPasswordReset: useHasPermission(
-		"users.adminRequestPasswordReset",
+		"users.requestPasswordReset",
 		async function adminRequestPasswordReset(session, userId, cb) {
 			const code = await UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 8 }, this);
 			const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
@@ -3228,7 +3228,7 @@ export default {
 	 * @param {string} expiresAt - the time the ban expires
 	 * @param {Function} cb - gets called with the result
 	 */
-	banUserById: useHasPermission("users.banUserById", function banUserById(session, userId, reason, expiresAt, cb) {
+	banUserById: useHasPermission("users.ban", function banUserById(session, userId, reason, expiresAt, cb) {
 		async.waterfall(
 			[
 				next => {

+ 1 - 1
backend/logic/actions/utils.js

@@ -51,7 +51,7 @@ export default {
 		);
 	}),
 
-	getModule: useHasPermission("utils.getModule", function getModule(session, moduleName, cb) {
+	getModule: useHasPermission("utils.getModules", function getModule(session, moduleName, cb) {
 		async.waterfall(
 			[
 				next => {

+ 4 - 4
backend/logic/actions/youtube.js

@@ -19,7 +19,7 @@ export default {
 	 *
 	 * @returns {{status: string, data: object}}
 	 */
-	getQuotaStatus: useHasPermission("youtube.getQuotaStatus", function getQuotaStatus(session, fromDate, cb) {
+	getQuotaStatus: useHasPermission("admin.view.youtube", function getQuotaStatus(session, fromDate, cb) {
 		YouTubeModule.runJob("GET_QUOTA_STATUS", { fromDate }, this)
 			.then(response => {
 				this.log("SUCCESS", "YOUTUBE_GET_QUOTA_STATUS", `Getting quota status was successful.`);
@@ -43,7 +43,7 @@ export default {
 	 * @returns {{status: string, data: object}}
 	 */
 	getQuotaChartData: useHasPermission(
-		"youtube.getQuotaChartData",
+		"admin.view.youtube",
 		function getQuotaChartData(session, timePeriod, startDate, endDate, dataType, cb) {
 			YouTubeModule.runJob(
 				"GET_QUOTA_CHART_DATA",
@@ -75,7 +75,7 @@ export default {
 	 * @param cb
 	 */
 	getApiRequests: useHasPermission(
-		"youtube.getApiRequests",
+		"admin.view.youtube",
 		async function getApiRequests(session, page, pageSize, properties, sort, queries, operator, cb) {
 			async.waterfall(
 				[
@@ -247,7 +247,7 @@ export default {
 	 * @param cb
 	 */
 	getVideos: useHasPermission(
-		"youtube.getVideos",
+		"admin.view.youtubeVideos",
 		async function getVideos(session, page, pageSize, properties, sort, queries, operator, cb) {
 			async.waterfall(
 				[

+ 1 - 1
backend/logic/api.js

@@ -138,7 +138,7 @@ class _APIModule extends CoreClass {
 										if (playlist.createdBy === req.session.userId)
 											res.json({ status: "success", playlist });
 										else
-											hasPermission("playlists.getPlaylist", req.session.userId)
+											hasPermission("playlists.get", req.session.userId)
 												.then(() => res.json({ status: "success", playlist }))
 												.catch(() =>
 													res.json({

+ 52 - 96
backend/logic/hooks/hasPermission.js

@@ -5,130 +5,86 @@ import moduleManager from "../../index";
 
 const permissions = {};
 permissions.dj = {
-	"stations.view": true,
-	"stations.view.manage": true,
-	"stations.skip": true,
-	"stations.pause": true,
-	"stations.resume": true,
-	"stations.addToQueue": true,
-	"stations.removeFromQueue": true,
-	"stations.repositionSongInQueue": true,
-	"stations.autofillPlaylist": true,
-	"stations.removeAutofillPlaylist": true,
-	"stations.blacklistPlaylist": true,
-	"stations.removeBlacklistedPlaylist": true,
+	"stations.autofill": true,
+	"stations.blacklist": true,
 	"stations.index": true,
-	"stations.getPlaylist": true
+	"stations.playback.toggle": true,
+	"stations.queue.remove": true,
+	"stations.queue.reposition": true,
+	"stations.queue.reset": true,
+	"stations.request": true,
+	"stations.skip": true,
+	"stations.view": true,
+	"stations.view.manage": true
 };
 permissions.owner = {
 	...permissions.dj,
-	"stations.update": true,
-	"stations.remove": true
+	"stations.remove": true,
+	"stations.update": true
 };
 permissions.moderator = {
 	...permissions.owner,
-	"songs.length": true,
-	"songs.getData": true,
-	"songs.getSongFromId": true,
-	"songs.getSongsFromYoutubeIds": true,
-	"songs.create": true,
-	"songs.update": true,
-	"songs.verify": true,
-	"songs.verifyMany": true,
-	"songs.unverify": true,
-	"songs.unverifyMany": true,
-	"songs.getGenres": true,
-	"songs.editGenres": true,
-	"songs.getArtists": true,
-	"songs.editArtists": true,
-	"songs.getTags": true,
-	"songs.editTags": true,
+	"admin.view": true,
+	"admin.view.import": true,
+	"admin.view.news": true,
+	"admin.view.playlists": true,
+	"admin.view.punishments": true,
+	"admin.view.reports": true,
+	"admin.view.songs": true,
+	"admin.view.stations": true,
+	"admin.view.youtubeVideos": true,
 	"apis.searchDiscogs": true,
-	"apis.joinAdminRoom.songs": true,
-	"apis.joinAdminRoom.stations": true,
-	"apis.joinAdminRoom.reports": true,
-	"apis.joinAdminRoom.news": true,
-	"apis.joinAdminRoom.playlists": true,
-	"apis.joinAdminRoom.punishments": true,
-	"apis.joinAdminRoom.youtubeVideos": true,
-	"apis.joinAdminRoom.import": true,
-	"media.getImportJobs": true,
-	"news.getData": true,
 	"news.create": true,
 	"news.update": true,
-	"playlists.getData": true,
-	"playlists.searchOfficial": true,
-	"playlists.updatePrivacy": true,
-	"playlists.updateDisplayName": false,
-	"playlists.getPlaylist": true,
-	"playlists.repositionSong": true,
-	"playlists.addSongToPlaylist": true,
-	"playlists.addSetToPlaylist": true,
-	"playlists.removeSongFromPlaylist": true,
+	"playlists.get": true,
+	"playlists.update.displayName": false,
+	"playlists.update.privacy": true,
+	"playlists.songs.add": true,
+	"playlists.songs.remove": true,
+	"playlists.songs.reposition": true,
 	"playlists.view.others": true,
-	"punishments.getData": true,
-	"punishments.getPunishmentsForUser": true,
-	"punishments.findOne": true,
 	"punishments.banIP": true,
-	"reports.getData": true,
-	"reports.findOne": true,
-	"reports.getReportsForSong": true,
-	"reports.resolve": true,
-	"reports.toggleIssue": true,
-	"stations.getData": true,
-	"stations.resetQueue": true,
-	"stations.remove": false,
+	"punishments.get": true,
+	"reports.get": true,
+	"reports.update": true,
+	"songs.create": true,
+	"songs.get": true,
+	"songs.update": true,
+	"songs.verify": true,
+	"stations.create.official": true,
 	"stations.index": false,
 	"stations.index.other": true,
-	"stations.create.official": true,
-	"youtube.getVideos": true,
-	"youtube.requestSetAdmin": true,
-	"admin.view": true
+	"stations.remove": false,
+	"youtube.requestSetAdmin": true
 };
 permissions.admin = {
 	...permissions.moderator,
-	"songs.updateAll": true,
-	"songs.remove": true,
-	"songs.removeMany": true,
-	"apis.joinAdminRoom.users": true,
-	"apis.joinAdminRoom.statistics": true,
-	"apis.joinAdminRoom.youtube": true,
-	"dataRequests.getData": true,
+	"admin.view.statistics": true,
+	"admin.view.users": true,
+	"admin.view.youtube": true,
 	"dataRequests.resolve": true,
 	"media.recalculateAllRatings": true,
 	"media.removeImportJobs": true,
 	"news.remove": true,
+	"playlists.clearAndRefill": true,
+	"playlists.clearAndRefillAll": true,
+	"playlists.createMissing": true,
+	"playlists.deleteOrphaned": true,
 	"playlists.removeAdmin": true,
-	"playlists.deleteOrphanedStationPlaylists": true,
-	"playlists.deleteOrphanedGenrePlaylists": true,
 	"playlists.requestOrphanedPlaylistSongs": true,
-	"playlists.clearAndRefillStationPlaylist": true,
-	"playlists.clearAndRefillGenrePlaylist": true,
-	"playlists.clearAndRefillAllStationPlaylists": true,
-	"playlists.clearAndRefillAllGenrePlaylists": true,
-	"playlists.createMissingGenrePlaylists": true,
 	"reports.remove": true,
+	"songs.remove": true,
+	"songs.updateAll": true,
 	"stations.clearEveryStationQueue": true,
 	"stations.remove": true,
-	"users.getData": true,
-	"users.adminRemove": true,
-	"users.getUserFromId": true,
-	"users.updateRole": true,
-	"users.adminRequestPasswordReset": true,
+	"users.get": true,
+	"users.update": true,
+	"users.remove": true,
+	"users.remove.sessions": true,
+	"users.requestPasswordReset": true,
 	"users.resendVerifyEmail": true,
-	"users.banUserById": true,
-	"users.removeSessions": true,
-	"users.updateUsername": true,
-	"users.updateEmail": true,
-	"users.updateName": true,
-	"users.updateLocation": true,
-	"users.updateBio": true,
-	"users.updateAvatar": true,
+	"users.ban": true,
 	"utils.getModules": true,
-	"utils.getModule": true,
-	"youtube.getQuotaStatus": true,
-	"youtube.getQuotaChartData": true,
-	"youtube.getApiRequests": true,
 	"youtube.getApiRequest": true,
 	"youtube.resetStoredApiRequests": true,
 	"youtube.removeStoredApiRequest": true,

+ 4 - 6
frontend/src/components/Queue.vue

@@ -62,7 +62,7 @@ const hasPermission = permission =>
 const dragOptions = computed(() => ({
 	animation: 200,
 	group: "queue",
-	disabled: !hasPermission("stations.repositionSongInQueue"),
+	disabled: !hasPermission("stations.queue.reposition"),
 	ghostClass: "draggable-list-ghost"
 }));
 
@@ -155,20 +155,18 @@ onUpdated(() => {
 						:requested-by="true"
 						:class="{
 							'item-draggable': hasPermission(
-								'stations.repositionSongInQueue'
+								'stations.queue.reposition'
 							)
 						}"
 						:disabled-actions="[]"
 						:ref="el => (songItems[`song-item-${index}`] = el)"
 					>
 						<template
-							v-if="
-								hasPermission('stations.repositionSongInQueue')
-							"
+							v-if="hasPermission('stations.queue.reposition')"
 							#tippyActions
 						>
 							<quick-confirm
-								v-if="hasPermission('stations.removeFromQueue')"
+								v-if="hasPermission('stations.queue.remove')"
 								placement="left"
 								@confirm="removeFromQueue(element.youtubeId)"
 							>

+ 6 - 2
frontend/src/components/StationInfoBox.vue

@@ -108,7 +108,9 @@ const unfavoriteStation = () => {
 			<!-- (Admin) Pause/Resume Button -->
 			<button
 				class="button is-danger"
-				v-if="hasPermission('stations.resume') && stationPaused"
+				v-if="
+					hasPermission('stations.playback.toggle') && stationPaused
+				"
 				@click="resumeStation()"
 			>
 				<i class="material-icons icon-with-button">play_arrow</i>
@@ -117,7 +119,9 @@ const unfavoriteStation = () => {
 			<button
 				class="button is-danger"
 				@click="pauseStation()"
-				v-if="hasPermission('stations.pause') && !stationPaused"
+				v-if="
+					hasPermission('stations.playback.toggle') && !stationPaused
+				"
 			>
 				<i class="material-icons icon-with-button">pause</i>
 				<span> Pause Station </span>

+ 3 - 3
frontend/src/components/modals/EditPlaylist/Tabs/Settings.vue

@@ -28,7 +28,7 @@ const isEditable = permission =>
 		playlist.value.type === "user-disliked") &&
 		(isOwner() || hasPermission(permission))) ||
 	(playlist.value.type === "genre" &&
-		permission === "playlists.updatePrivacy" &&
+		permission === "playlists.update.privacy" &&
 		hasPermission(permission));
 
 const renamePlaylist = () => {
@@ -71,7 +71,7 @@ const updatePrivacy = () => {
 	<div class="settings-tab section">
 		<div
 			v-if="
-				isEditable('playlists.updateDisplayName') &&
+				isEditable('playlists.update.displayName') &&
 				!(
 					playlist.type === 'user-liked' ||
 					playlist.type === 'user-disliked'
@@ -101,7 +101,7 @@ const updatePrivacy = () => {
 			</div>
 		</div>
 
-		<div v-if="isEditable('playlists.updatePrivacy')">
+		<div v-if="isEditable('playlists.update.privacy')">
 			<label class="label"> Change privacy </label>
 			<div class="control is-grouped input-with-button">
 				<div class="control is-expanded select">

+ 21 - 23
frontend/src/components/modals/EditPlaylist/index.vue

@@ -73,13 +73,13 @@ const isEditable = permission =>
 		playlist.value.type === "user-disliked") &&
 		(isOwner() || hasPermission(permission))) ||
 	(playlist.value.type === "genre" &&
-		permission === "playlists.updatePrivacy" &&
+		permission === "playlists.update.privacy" &&
 		hasPermission(permission));
 
 const dragOptions = computed(() => ({
 	animation: 200,
 	group: "songs",
-	disabled: !isEditable("playlists.repositionSong"),
+	disabled: !isEditable("playlists.songs.reposition"),
 	ghostClass: "draggable-list-ghost"
 }));
 
@@ -312,15 +312,15 @@ onBeforeUnmount(() => {
 <template>
 	<modal
 		:title="
-			isEditable('playlists.updatePrivacy')
+			isEditable('playlists.update.privacy')
 				? 'Edit Playlist'
 				: 'View Playlist'
 		"
 		:class="{
 			'edit-playlist-modal': true,
-			'view-only': !isEditable('playlists.updatePrivacy')
+			'view-only': !isEditable('playlists.update.privacy')
 		}"
-		:size="isEditable('playlists.updatePrivacy') ? 'wide' : null"
+		:size="isEditable('playlists.update.privacy') ? 'wide' : null"
 		:split="true"
 	>
 		<template #body>
@@ -338,7 +338,7 @@ onBeforeUnmount(() => {
 							:class="{ selected: tab === 'settings' }"
 							:ref="el => (tabs['settings-tab'] = el)"
 							@click="showTab('settings')"
-							v-if="isEditable('playlists.updatePrivacy')"
+							v-if="isEditable('playlists.update.privacy')"
 						>
 							Settings
 						</button>
@@ -347,7 +347,7 @@ onBeforeUnmount(() => {
 							:class="{ selected: tab === 'add-songs' }"
 							:ref="el => (tabs['add-songs-tab'] = el)"
 							@click="showTab('add-songs')"
-							v-if="isEditable('playlists.addSongToPlaylist')"
+							v-if="isEditable('playlists.songs.add')"
 						>
 							Add Songs
 						</button>
@@ -358,7 +358,7 @@ onBeforeUnmount(() => {
 							}"
 							:ref="el => (tabs['import-playlists-tab'] = el)"
 							@click="showTab('import-playlists')"
-							v-if="isEditable('playlists.addSetToPlaylist')"
+							v-if="isEditable('playlists.songs.add')"
 						>
 							Import Playlists
 						</button>
@@ -366,19 +366,19 @@ onBeforeUnmount(() => {
 					<settings
 						class="tab"
 						v-show="tab === 'settings'"
-						v-if="isEditable('playlists.updatePrivacy')"
+						v-if="isEditable('playlists.update.privacy')"
 						:modal-uuid="modalUuid"
 					/>
 					<add-songs
 						class="tab"
 						v-show="tab === 'add-songs'"
-						v-if="isEditable('playlists.addSongToPlaylist')"
+						v-if="isEditable('playlists.songs.add')"
 						:modal-uuid="modalUuid"
 					/>
 					<import-playlists
 						class="tab"
 						v-show="tab === 'import-playlists'"
-						v-if="isEditable('playlists.addSetToPlaylist')"
+						v-if="isEditable('playlists.songs.add')"
 						:modal-uuid="modalUuid"
 					/>
 				</div>
@@ -386,7 +386,7 @@ onBeforeUnmount(() => {
 
 			<div class="right-section">
 				<div id="rearrange-songs-section" class="section">
-					<div v-if="isEditable('playlists.repositionSong')">
+					<div v-if="isEditable('playlists.songs.reposition')">
 						<h4 class="section-title">Rearrange Songs</h4>
 
 						<p class="section-description">
@@ -415,7 +415,7 @@ onBeforeUnmount(() => {
 										:song="element"
 										:class="{
 											'item-draggable': isEditable(
-												'playlists.repositionSong'
+												'playlists.songs.reposition'
 											)
 										}"
 										:ref="
@@ -456,7 +456,7 @@ onBeforeUnmount(() => {
 													userId ===
 														playlist.createdBy ||
 													isEditable(
-														'playlists.removeSongFromPlaylist'
+														'playlists.songs.remove'
 													)
 												"
 												placement="left"
@@ -477,7 +477,7 @@ onBeforeUnmount(() => {
 												class="material-icons"
 												v-if="
 													isEditable(
-														'playlists.repositionSong'
+														'playlists.songs.reposition'
 													) && index > 0
 												"
 												@click="
@@ -493,7 +493,7 @@ onBeforeUnmount(() => {
 											<i
 												v-if="
 													isEditable(
-														'playlists.repositionSong'
+														'playlists.songs.reposition'
 													) &&
 													playlistSongs.length - 1 !==
 														index
@@ -529,7 +529,7 @@ onBeforeUnmount(() => {
 				class="button is-default"
 				v-if="
 					isOwner() ||
-					hasPermission('playlists.getPlaylist') ||
+					hasPermission('playlists.get') ||
 					playlist.privacy === 'public'
 				"
 				@click="downloadPlaylist()"
@@ -539,9 +539,8 @@ onBeforeUnmount(() => {
 			<div class="right">
 				<quick-confirm
 					v-if="
-						hasPermission(
-							'playlists.clearAndRefillStationPlaylist'
-						) && playlist.type === 'station'
+						hasPermission('playlists.clearAndRefill') &&
+						playlist.type === 'station'
 					"
 					@confirm="clearAndRefillStationPlaylist()"
 				>
@@ -551,9 +550,8 @@ onBeforeUnmount(() => {
 				</quick-confirm>
 				<quick-confirm
 					v-if="
-						hasPermission(
-							'playlists.clearAndRefillGenrePlaylist'
-						) && playlist.type === 'genre'
+						hasPermission('playlists.clearAndRefill') &&
+						playlist.type === 'genre'
 					"
 					@confirm="clearAndRefillGenrePlaylist()"
 				>

+ 5 - 5
frontend/src/components/modals/ManageStation/index.vue

@@ -79,7 +79,7 @@ const canRequest = () =>
 	station.value.requests.enabled &&
 	(station.value.requests.access === "user" ||
 		(station.value.requests.access === "owner" &&
-			hasPermission("stations.addToQueue")));
+			hasPermission("stations.request")));
 
 const removeStation = () => {
 	socket.dispatch("stations.remove", stationId.value, res => {
@@ -151,7 +151,7 @@ onMounted(() => {
 				}
 			);
 
-			if (hasPermission("stations.getPlaylist")) {
+			if (hasPermission("stations.view")) {
 				socket.dispatch(
 					"playlists.getPlaylistForStation",
 					stationId.value,
@@ -308,7 +308,7 @@ onMounted(() => {
 		{ modalUuid: props.modalUuid }
 	);
 
-	if (hasPermission("stations.getPlaylist")) {
+	if (hasPermission("stations.view")) {
 		socket.on(
 			"event:playlist.song.added",
 			res => {
@@ -533,13 +533,13 @@ onBeforeUnmount(() => {
 		<template #footer>
 			<div class="right">
 				<quick-confirm
-					v-if="hasPermission('stations.removeFromQueue')"
+					v-if="hasPermission('stations.queue.remove')"
 					@confirm="resetQueue()"
 				>
 					<a class="button is-danger">Reset queue</a>
 				</quick-confirm>
 				<quick-confirm
-					v-if="hasPermission('stations.resetQueue')"
+					v-if="hasPermission('stations.queue.reset')"
 					@confirm="removeStation()"
 				>
 					<button class="button is-danger">Delete station</button>

+ 12 - 12
frontend/src/main.ts

@@ -174,69 +174,69 @@ const router = createRouter({
 				{
 					path: "songs",
 					component: () => import("@/pages/Admin/Songs/index.vue"),
-					meta: { permissionRequired: "apis.joinAdminRoom.songs" }
+					meta: { permissionRequired: "admin.view.songs" }
 				},
 				{
 					path: "songs/import",
 					component: () => import("@/pages/Admin/Songs/Import.vue"),
-					meta: { permissionRequired: "apis.joinAdminRoom.import" }
+					meta: { permissionRequired: "admin.view.import" }
 				},
 				{
 					path: "reports",
 					component: () => import("@/pages/Admin/Reports.vue"),
-					meta: { permissionRequired: "apis.joinAdminRoom.reports" }
+					meta: { permissionRequired: "admin.view.reports" }
 				},
 				{
 					path: "stations",
 					component: () => import("@/pages/Admin/Stations.vue"),
-					meta: { permissionRequired: "apis.joinAdminRoom.stations" }
+					meta: { permissionRequired: "admin.view.stations" }
 				},
 				{
 					path: "playlists",
 					component: () => import("@/pages/Admin/Playlists.vue"),
-					meta: { permissionRequired: "apis.joinAdminRoom.playlists" }
+					meta: { permissionRequired: "admin.view.playlists" }
 				},
 				{
 					path: "users",
 					component: () => import("@/pages/Admin/Users/index.vue"),
-					meta: { permissionRequired: "apis.joinAdminRoom.users" }
+					meta: { permissionRequired: "admin.view.users" }
 				},
 				{
 					path: "users/data-requests",
 					component: () =>
 						import("@/pages/Admin/Users/DataRequests.vue"),
-					meta: { permissionRequired: "apis.joinAdminRoom.users" }
+					meta: { permissionRequired: "admin.view.users" }
 				},
 				{
 					path: "users/punishments",
 					component: () =>
 						import("@/pages/Admin/Users/Punishments.vue"),
 					meta: {
-						permissionRequired: "apis.joinAdminRoom.punishments"
+						permissionRequired: "admin.view.punishments"
 					}
 				},
 				{
 					path: "news",
 					component: () => import("@/pages/Admin/News.vue"),
-					meta: { permissionRequired: "apis.joinAdminRoom.news" }
+					meta: { permissionRequired: "admin.view.news" }
 				},
 				{
 					path: "statistics",
 					component: () => import("@/pages/Admin/Statistics.vue"),
 					meta: {
-						permissionRequired: "apis.joinAdminRoom.statistics"
+						permissionRequired: "admin.view.statistics"
 					}
 				},
 				{
 					path: "youtube",
 					component: () => import("@/pages/Admin/YouTube/index.vue"),
-					meta: { permissionRequired: "apis.joinAdminRoom.youtube" }
+					meta: { permissionRequired: "admin.view.youtube" }
 				},
 				{
 					path: "youtube/videos",
 					component: () => import("@/pages/Admin/YouTube/Videos.vue"),
 					meta: {
-						permissionRequired: "apis.joinAdminRoom.youtubeVideos"
+						permissionRequired: "admin.view.youtubeVideos"
 					}
 				}
 			],

+ 18 - 40
frontend/src/pages/Admin/index.vue

@@ -181,7 +181,7 @@ onBeforeUnmount(() => {
 							</div>
 							<div
 								v-if="
-									hasPermission('apis.joinAdminRoom.songs') &&
+									hasPermission('admin.view.songs') &&
 									sidebarActive
 								"
 								class="sidebar-item with-children"
@@ -214,9 +214,7 @@ onBeforeUnmount(() => {
 									</router-link>
 									<router-link
 										v-if="
-											hasPermission(
-												'apis.joinAdminRoom.import'
-											)
+											hasPermission('admin.view.import')
 										"
 										class="sidebar-item-child"
 										to="/admin/songs/import"
@@ -227,7 +225,7 @@ onBeforeUnmount(() => {
 							</div>
 							<router-link
 								v-else-if="
-									hasPermission('apis.joinAdminRoom.users') &&
+									hasPermission('admin.view.users') &&
 									!sidebarActive
 								"
 								class="sidebar-item songs"
@@ -242,9 +240,7 @@ onBeforeUnmount(() => {
 								<span>Songs</span>
 							</router-link>
 							<router-link
-								v-if="
-									hasPermission('apis.joinAdminRoom.reports')
-								"
+								v-if="hasPermission('admin.view.reports')"
 								class="sidebar-item reports"
 								to="/admin/reports"
 								content="Reports"
@@ -257,9 +253,7 @@ onBeforeUnmount(() => {
 								<span>Reports</span>
 							</router-link>
 							<router-link
-								v-if="
-									hasPermission('apis.joinAdminRoom.stations')
-								"
+								v-if="hasPermission('admin.view.stations')"
 								class="sidebar-item stations"
 								to="/admin/stations"
 								content="Stations"
@@ -272,11 +266,7 @@ onBeforeUnmount(() => {
 								<span>Stations</span>
 							</router-link>
 							<router-link
-								v-if="
-									hasPermission(
-										'apis.joinAdminRoom.playlists'
-									)
-								"
+								v-if="hasPermission('admin.view.playlists')"
 								class="sidebar-item playlists"
 								to="/admin/playlists"
 								content="Playlists"
@@ -290,7 +280,7 @@ onBeforeUnmount(() => {
 							</router-link>
 							<div
 								v-if="
-									hasPermission('apis.joinAdminRoom.users') &&
+									hasPermission('admin.view.users') &&
 									sidebarActive
 								"
 								class="sidebar-item with-children"
@@ -337,7 +327,7 @@ onBeforeUnmount(() => {
 							</div>
 							<router-link
 								v-else-if="
-									hasPermission('apis.joinAdminRoom.users') &&
+									hasPermission('admin.view.users') &&
 									!sidebarActive
 								"
 								class="sidebar-item users"
@@ -352,7 +342,7 @@ onBeforeUnmount(() => {
 								<span>Users</span>
 							</router-link>
 							<router-link
-								v-if="hasPermission('apis.joinAdminRoom.news')"
+								v-if="hasPermission('admin.view.news')"
 								class="sidebar-item news"
 								to="/admin/news"
 								content="News"
@@ -365,11 +355,7 @@ onBeforeUnmount(() => {
 								<span>News</span>
 							</router-link>
 							<router-link
-								v-if="
-									hasPermission(
-										'apis.joinAdminRoom.statistics'
-									)
-								"
+								v-if="hasPermission('admin.view.statistics')"
 								class="sidebar-item statistics"
 								to="/admin/statistics"
 								content="Statistics"
@@ -383,11 +369,9 @@ onBeforeUnmount(() => {
 							</router-link>
 							<div
 								v-if="
-									(hasPermission(
-										'apis.joinAdminRoom.youtube'
-									) ||
+									(hasPermission('admin.view.youtube') ||
 										hasPermission(
-											'apis.joinAdminRoom.youtubeVideos'
+											'admin.view.youtubeVideos'
 										)) &&
 									sidebarActive
 								"
@@ -397,9 +381,7 @@ onBeforeUnmount(() => {
 								<span>
 									<router-link
 										:to="`/admin/youtube${
-											hasPermission(
-												'apis.joinAdminRoom.youtube'
-											)
+											hasPermission('admin.view.youtube')
 												? ''
 												: '/videos'
 										}`"
@@ -425,9 +407,7 @@ onBeforeUnmount(() => {
 								<div class="sidebar-item-children">
 									<router-link
 										v-if="
-											hasPermission(
-												'apis.joinAdminRoom.youtube'
-											)
+											hasPermission('admin.view.youtube')
 										"
 										class="sidebar-item-child"
 										to="/admin/youtube"
@@ -437,7 +417,7 @@ onBeforeUnmount(() => {
 									<router-link
 										v-if="
 											hasPermission(
-												'apis.joinAdminRoom.youtubeVideos'
+												'admin.view.youtubeVideos'
 											)
 										"
 										class="sidebar-item-child"
@@ -449,17 +429,15 @@ onBeforeUnmount(() => {
 							</div>
 							<router-link
 								v-else-if="
-									(hasPermission(
-										'apis.joinAdminRoom.youtube'
-									) ||
+									(hasPermission('admin.view.youtube') ||
 										hasPermission(
-											'apis.joinAdminRoom.youtubeVideos'
+											'admin.view.youtubeVideos'
 										)) &&
 									!sidebarActive
 								"
 								class="sidebar-item youtube"
 								:to="`/admin/youtube${
-									hasPermission('apis.joinAdminRoom.youtube')
+									hasPermission('admin.view.youtube')
 										? ''
 										: '/videos'
 								}`"

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

@@ -115,7 +115,7 @@ const canRequest = (station, requireLogin = true) =>
 	station.requests.enabled &&
 	(station.requests.access === "user" ||
 		(station.requests.access === "owner" &&
-			(isOwner(station) || hasPermission("stations.addToQueue"))));
+			(isOwner(station) || hasPermission("stations.request"))));
 
 const favoriteStation = stationId => {
 	socket.dispatch("stations.favoriteStation", stationId, res => {

+ 2 - 3
frontend/src/pages/Profile/index.vue

@@ -91,14 +91,13 @@ onMounted(() => {
 				<div
 					class="buttons"
 					v-if="
-						myUserId === userId ||
-						hasPermission('apis.joinAdminRoom.users')
+						myUserId === userId || hasPermission('admin.view.users')
 					"
 				>
 					<router-link
 						:to="`/admin/users?userId=${user._id}`"
 						class="button is-primary"
-						v-if="hasPermission('apis.joinAdminRoom.users')"
+						v-if="hasPermission('admin.view.users')"
 					>
 						Edit
 					</router-link>

+ 1 - 1
frontend/src/pages/Station/Sidebar/index.vue

@@ -29,7 +29,7 @@ const canRequest = (requireLogin = true) =>
 	station.value.requests.enabled &&
 	(station.value.requests.access === "user" ||
 		(station.value.requests.access === "owner" &&
-			hasPermission("stations.addToQueue")));
+			hasPermission("stations.request")));
 
 watch(
 	() => station.value.requests,

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

@@ -881,10 +881,7 @@ const join = () => {
 				}
 			});
 
-			if (
-				hasPermission("stations.pause") &&
-				hasPermission("stations.resume")
-			)
+			if (hasPermission("stations.playback.toggle"))
 				keyboardShortcuts.registerShortcut("station.pauseResume", {
 					keyCode: 32, // Spacebar
 					shift: false,
@@ -1173,11 +1170,11 @@ onMounted(async () => {
 
 	ms.setListeners(0, {
 		play: () => {
-			if (hasPermission("stations.resume")) resumeStation();
+			if (hasPermission("stations.playback.toggle")) resumeStation();
 			else resumeLocalStation();
 		},
 		pause: () => {
-			if (hasPermission("stations.pause")) pauseStation();
+			if (hasPermission("stations.playback.toggle")) pauseStation();
 			else pauseLocalStation();
 		},
 		nexttrack: () => {
@@ -2049,8 +2046,7 @@ onBeforeUnmount(() => {
 				<div>
 					<div
 						v-if="
-							hasPermission('stations.resume') ||
-							hasPermission('stations.pause') ||
+							hasPermission('stations.playback.toggle') ||
 							hasPermission('stations.skip')
 						"
 					>
@@ -2060,8 +2056,7 @@ onBeforeUnmount(() => {
 					</div>
 					<hr
 						v-if="
-							hasPermission('stations.resume') ||
-							hasPermission('stations.pause') ||
+							hasPermission('stations.playback.toggle') ||
 							hasPermission('stations.skip')
 						"
 					/>