Procházet zdrojové kódy

refactor: Improved modal, station and homepage user permission change handling

Owen Diffey před 1 rokem
rodič
revize
8749166fc5

+ 10 - 10
backend/logic/stations.js

@@ -87,14 +87,14 @@ class _StationsModule extends CoreClass {
 				userModel.findOne({ _id: userId }, (err, user) => {
 					if (!err && user) {
 						const { _id, name, username, avatar } = user;
-						const data = { user: { _id, name, username, avatar } };
-						WSModule.runJob("EMIT_TO_ROOM", {
-							room: `station.${stationId}`,
-							args: ["event:station.djs.added", { data }]
+						const data = { data: { user: { _id, name, username, avatar }, stationId } };
+						WSModule.runJob("EMIT_TO_ROOMS", {
+							rooms: [`station.${stationId}`, "home"],
+							args: ["event:station.djs.added", data]
 						});
 						WSModule.runJob("EMIT_TO_ROOM", {
 							room: `manage-station.${stationId}`,
-							args: ["event:manageStation.djs.added", { data: { ...data, stationId } }]
+							args: ["event:manageStation.djs.added", data]
 						});
 					}
 				});
@@ -107,14 +107,14 @@ class _StationsModule extends CoreClass {
 				userModel.findOne({ _id: userId }, (err, user) => {
 					if (!err && user) {
 						const { _id, name, username, avatar } = user;
-						const data = { user: { _id, name, username, avatar } };
-						WSModule.runJob("EMIT_TO_ROOM", {
-							room: `station.${stationId}`,
-							args: ["event:station.djs.removed", { data }]
+						const data = { data: { user: { _id, name, username, avatar }, stationId } };
+						WSModule.runJob("EMIT_TO_ROOMS", {
+							rooms: [`station.${stationId}`, "home"],
+							args: ["event:station.djs.removed", data]
 						});
 						WSModule.runJob("EMIT_TO_ROOM", {
 							room: `manage-station.${stationId}`,
-							args: ["event:manageStation.djs.removed", { data: { ...data, stationId } }]
+							args: ["event:manageStation.djs.removed", data]
 						});
 					}
 				});

+ 1 - 1
frontend/src/App.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { useRoute, useRouter } from "vue-router";
+import { useRouter } from "vue-router";
 import { defineAsyncComponent, ref, computed, watch, onMounted } from "vue";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";

+ 6 - 0
frontend/src/components/modals/EditSong/index.vue

@@ -1206,6 +1206,12 @@ watch(youtubeId, (_youtubeId, _oldYoutubeId) => {
 	if (_oldYoutubeId) unloadSong(_oldYoutubeId);
 	if (_youtubeId) loadSong(_youtubeId);
 });
+watch(
+	() => hasPermission("songs.update"),
+	value => {
+		if (!value) modalsStore.closeCurrentModal();
+	}
+);
 
 onMounted(async () => {
 	activityWatchVideoDataInterval.value = setInterval(() => {

+ 6 - 0
frontend/src/components/modals/EditUser.vue

@@ -158,6 +158,12 @@ const removeSessions = () => {
 
 // When the userId changes, run init. There can be a delay between the modal opening and the required data (userId) being available
 watch(userId, () => init());
+watch(
+	() => hasPermission("users.get") && hasPermission("users.update"),
+	value => {
+		if (!value) closeCurrentModal();
+	}
+);
 
 onMounted(() => {
 	ws.onConnect(init);

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

@@ -107,10 +107,12 @@ const resetQueue = () => {
 
 const findTabOrClose = () => {
 	if (hasPermission("stations.update")) return showTab("settings");
-	if (hasPermission("stations.request")) return showTab("request");
+	if (canRequest()) return showTab("request");
 	if (hasPermission("stations.autofill")) return showTab("autofill");
 	if (hasPermission("stations.blacklist")) return showTab("blacklist");
-	return closeCurrentModal();
+	if (!(sector.value === "home" && hasPermission("stations.view")))
+		return closeCurrentModal();
+	return null;
 };
 
 watch(
@@ -145,7 +147,7 @@ onMounted(() => {
 
 			await updatePermissions();
 
-			if (!hasPermission("stations.update")) showTab("request");
+			findTabOrClose();
 
 			const currentSong = res.data.station.currentSong
 				? res.data.station.currentSong

+ 14 - 1
frontend/src/components/modals/ViewReport.vue

@@ -1,5 +1,11 @@
 <script setup lang="ts">
-import { defineAsyncComponent, ref, onMounted, onBeforeUnmount } from "vue";
+import {
+	defineAsyncComponent,
+	ref,
+	watch,
+	onMounted,
+	onBeforeUnmount
+} from "vue";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
@@ -126,6 +132,13 @@ const openSong = () => {
 	});
 };
 
+watch(
+	() => hasPermission("reports.get"),
+	value => {
+		if (!value) closeCurrentModal();
+	}
+);
+
 onMounted(() => {
 	ws.onConnect(init);
 });

+ 1 - 3
frontend/src/main.ts

@@ -273,9 +273,7 @@ router.beforeEach((to, from, next) => {
 		const { query } = to;
 		delete query.toast;
 		next({ ...to, query });
-	}
-
-	if (
+	} else if (
 		to.meta.loginRequired ||
 		to.meta.permissionRequired ||
 		to.meta.guestsOnly

+ 68 - 10
frontend/src/pages/Home.vue

@@ -4,6 +4,7 @@ import {
 	defineAsyncComponent,
 	ref,
 	computed,
+	watch,
 	onMounted,
 	onBeforeUnmount
 } from "vue";
@@ -49,6 +50,9 @@ const orderOfFavoriteStations = ref([]);
 const handledLoginRegisterRedirect = ref(false);
 
 const isOwner = station => loggedIn.value && station.owner === userId.value;
+const isDj = station =>
+	loggedIn.value && !!station.djs.find(dj => dj === userId.value);
+const isOwnerOrDj = station => isOwner(station) || isDj(station);
 
 const isPlaying = station => typeof station.currentSong.title !== "undefined";
 
@@ -64,6 +68,7 @@ const filteredStations = computed(() => {
 		.sort(
 			(a, b) =>
 				Number(isOwner(b)) - Number(isOwner(a)) ||
+				Number(isDj(b)) - Number(isDj(a)) ||
 				Number(isPlaying(b)) - Number(isPlaying(a)) ||
 				a.paused - b.paused ||
 				privacyOrder.indexOf(a.privacy) -
@@ -84,15 +89,13 @@ const favoriteStations = computed(() =>
 
 const { openModal } = useModalsStore();
 
-const init = () => {
+const fetchStations = () => {
 	socket.dispatch(
 		"stations.index",
 		route.query.adminFilter === undefined,
 		res => {
-			stations.value = [];
-
 			if (res.status === "success") {
-				res.data.stations.forEach(station => {
+				stations.value = res.data.stations.map(station => {
 					const modifiableStation = station;
 
 					if (!modifiableStation.currentSong)
@@ -106,15 +109,19 @@ const init = () => {
 					)
 						modifiableStation.currentSong.ytThumbnail = `https://img.youtube.com/vi/${station.currentSong.youtubeId}/mqdefault.jpg`;
 
-					stations.value.push(modifiableStation);
+					return modifiableStation;
 				});
 
 				orderOfFavoriteStations.value = res.data.favorited;
 			}
 		}
 	);
+};
 
+const init = () => {
 	socket.dispatch("apis.joinRoom", "home");
+
+	fetchStations();
 };
 
 const canRequest = (station, requireLogin = true) =>
@@ -124,7 +131,7 @@ const canRequest = (station, requireLogin = true) =>
 	station.requests.enabled &&
 	(station.requests.access === "user" ||
 		(station.requests.access === "owner" &&
-			(isOwner(station) || hasPermission("stations.request"))));
+			(isOwnerOrDj(station) || hasPermission("stations.request"))));
 
 const favoriteStation = stationId => {
 	socket.dispatch("stations.favoriteStation", stationId, res => {
@@ -151,6 +158,19 @@ const changeFavoriteOrder = ({ moved }) => {
 	);
 };
 
+watch(
+	() => hasPermission("stations.index.other"),
+	value => {
+		if (!value && route.query.adminFilter === null)
+			router.push({
+				query: {
+					...route.query,
+					adminFilter: undefined
+				}
+			});
+	}
+);
+
 onMounted(async () => {
 	siteSettings.value = await lofig.get("siteSettings");
 
@@ -298,6 +318,18 @@ onMounted(async () => {
 		orderOfFavoriteStations.value = res.data.order;
 	});
 
+	socket.on("event:station.djs.added", res => {
+		if (res.data.user._id === userId.value) fetchStations();
+	});
+
+	socket.on("event:station.djs.removed", res => {
+		if (res.data.user._id === userId.value) fetchStations();
+	});
+
+	socket.on("keep.event:user.role.updated", () => {
+		fetchStations();
+	});
+
 	// ctrl + alt + f
 	keyboardShortcuts.registerShortcut("home.toggleAdminFilter", {
 		keyCode: 70,
@@ -397,7 +429,8 @@ onBeforeUnmount(() => {
 							:class="{
 								'station-card': true,
 								isPrivate: element.privacy === 'private',
-								isMine: isOwner(element)
+								isMine: isOwner(element),
+								isDj: isDj(element)
 							}"
 							:style="
 								'--primary-color: var(--' + element.theme + ')'
@@ -409,7 +442,7 @@ onBeforeUnmount(() => {
 										<div class="icon-container">
 											<div
 												v-if="
-													isOwner(element) ||
+													isOwnerOrDj(element) ||
 													hasPermission(
 														'stations.view.manage'
 													)
@@ -526,6 +559,17 @@ onBeforeUnmount(() => {
 												v-tippy="{ theme: 'info' }"
 												>home</i
 											>
+											<i
+												v-if="
+													element.type ===
+														'community' &&
+													isDj(element)
+												"
+												class="djIcon material-icons"
+												content="You are a DJ in this station."
+												v-tippy="{ theme: 'info' }"
+												>shield</i
+											>
 											<i
 												v-if="
 													element.privacy ===
@@ -668,7 +712,8 @@ onBeforeUnmount(() => {
 					class="station-card"
 					:class="{
 						isPrivate: station.privacy === 'private',
-						isMine: isOwner(station)
+						isMine: isOwner(station),
+						isDj: isDj(station)
 					}"
 					:style="'--primary-color: var(--' + station.theme + ')'"
 				>
@@ -678,7 +723,7 @@ onBeforeUnmount(() => {
 								<div class="icon-container">
 									<div
 										v-if="
-											isOwner(station) ||
+											isOwnerOrDj(station) ||
 											hasPermission(
 												'stations.view.manage'
 											)
@@ -779,6 +824,16 @@ onBeforeUnmount(() => {
 										v-tippy="{ theme: 'info' }"
 										>home</i
 									>
+									<i
+										v-if="
+											station.type === 'community' &&
+											isDj(station)
+										"
+										class="djIcon material-icons"
+										content="You are a DJ in this station."
+										v-tippy="{ theme: 'info' }"
+										>shield</i
+									>
 									<i
 										v-if="station.privacy === 'private'"
 										class="privateIcon material-icons"
@@ -1222,6 +1277,9 @@ html {
 					.homeIcon {
 						color: var(--light-purple);
 					}
+					.djIcon {
+						color: var(--primary-color);
+					}
 				}
 
 				.hostedBy {

+ 15 - 34
frontend/src/pages/Station/Sidebar/Users.vue

@@ -39,20 +39,17 @@ const { socket } = useWebsocketsStore();
 
 const { station, users, userCount } = storeToRefs(stationStore);
 
+const isOwner = userId => station.value.owner === userId;
+const isDj = userId => !!station.value.djs.find(dj => dj._id === userId);
+
 const sortedUsers = computed(() =>
 	users.value && users.value.loggedIn
 		? users.value.loggedIn
 				.slice()
 				.sort(
 					(a, b) =>
-						Number(station.value.owner === b._id) -
-							Number(station.value.owner === a._id) ||
-						Number(
-							!station.value.djs.find(dj => dj._id === a._id)
-						) -
-							Number(
-								!station.value.djs.find(dj => dj._id === b._id)
-							)
+						Number(isOwner(b._id)) - Number(isOwner(a._id)) ||
+						Number(!isOwner(a._id)) - Number(!isOwner(b._id))
 				)
 		: []
 );
@@ -224,18 +221,14 @@ onMounted(async () => {
 								{{ user.name || user.username }}
 
 								<span
-									v-if="user._id === station.owner"
+									v-if="isOwner(user._id)"
 									class="material-icons user-rank"
 									content="Station Owner"
 									v-tippy="{ theme: 'info' }"
 									>local_police</span
 								>
 								<span
-									v-else-if="
-										station.djs.find(
-											dj => dj._id === user._id
-										)
-									"
+									v-else-if="isDj(user._id)"
 									class="material-icons user-rank"
 									content="Station DJ"
 									v-tippy="{ theme: 'info' }"
@@ -246,10 +239,8 @@ onMounted(async () => {
 									v-if="
 										hasPermission('stations.djs.add') &&
 										station.type === 'community' &&
-										!station.djs.find(
-											dj => dj._id === user._id
-										) &&
-										station.owner !== user._id
+										!isDj(user._id) &&
+										!isOwner(user._id)
 									"
 									class="button is-primary material-icons"
 									@click.prevent="addDj(user._id)"
@@ -262,9 +253,7 @@ onMounted(async () => {
 									v-else-if="
 										hasPermission('stations.djs.remove') &&
 										station.type === 'community' &&
-										station.djs.find(
-											dj => dj._id === user._id
-										)
+										isDj(user._id)
 									"
 									class="button is-danger material-icons"
 									@click.prevent="removeDj(user._id)"
@@ -374,18 +363,14 @@ onMounted(async () => {
 								{{ user.name || user.username }}
 
 								<span
-									v-if="user._id === station.owner"
+									v-if="isOwner(user._id)"
 									class="material-icons user-rank"
 									content="Station Owner"
 									v-tippy="{ theme: 'info' }"
 									>local_police</span
 								>
 								<span
-									v-else-if="
-										station.djs.find(
-											dj => dj._id === user._id
-										)
-									"
+									v-else-if="isDj(user._id)"
 									class="material-icons user-rank"
 									content="Station DJ"
 									v-tippy="{ theme: 'info' }"
@@ -396,10 +381,8 @@ onMounted(async () => {
 									v-if="
 										hasPermission('stations.djs.add') &&
 										station.type === 'community' &&
-										!station.djs.find(
-											dj => dj._id === user._id
-										) &&
-										station.owner !== user._id
+										!isDj(user._id) &&
+										!isOwner(user._id)
 									"
 									class="button is-primary material-icons"
 									@click.prevent="addDj(user._id)"
@@ -412,9 +395,7 @@ onMounted(async () => {
 									v-else-if="
 										hasPermission('stations.djs.remove') &&
 										station.type === 'community' &&
-										station.djs.find(
-											dj => dj._id === user._id
-										)
+										isDj(user._id)
 									"
 									class="button is-danger material-icons"
 									@click.prevent="removeDj(user._id)"