Browse Source

refactor: Use current user model to get and set nightmode status

Owen Diffey 3 weeks ago
parent
commit
6d4dbaf8fe

+ 14 - 0
backend/src/modules/DataModule/models/User/jobs/UpdateById.ts

@@ -1,6 +1,20 @@
+import Joi from "joi";
 import UpdateByIdJob from "@/modules/DataModule/UpdateByIdJob";
 import User from "../../User";
+import isAdmin from "@/modules/DataModule/permissions/isAdmin";
+import isSelf from "@/modules/DataModule/permissions/modelPermissions/isSelf";
 
 export default class UpdateById extends UpdateByIdJob {
 	protected static _model = User;
+
+	protected static _hasModelPermission = [isAdmin, isSelf];
+
+	protected static _payloadSchema = Joi.object({
+		_id: Joi.string()
+			.pattern(/^[0-9a-fA-F]{24}$/)
+			.required(),
+		query: Joi.object({
+			nightmode: Joi.boolean().optional(),
+		}).required()
+	});
 }

+ 20 - 41
frontend/src/App.vue

@@ -3,12 +3,11 @@ import { useRouter } from "vue-router";
 import { defineAsyncComponent, ref, watch, onMounted } from "vue";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
-import { GenericResponse } from "@musare_types/actions/GenericActions";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useConfigStore } from "@/stores/config";
 import { useUserAuthStore } from "@/stores/userAuth";
-import { useUserPreferencesStore } from "@/stores/userPreferences";
 import { useModalsStore } from "@/stores/modals";
+import { useEvents } from "@/composables/useEvents";
 import aw from "@/aw";
 import keyboardShortcuts from "@/keyboardShortcuts";
 
@@ -28,7 +27,6 @@ const router = useRouter();
 const { socket } = useWebsocketsStore();
 const configStore = useConfigStore();
 const userAuthStore = useUserAuthStore();
-const userPreferencesStore = useUserPreferencesStore();
 const modalsStore = useModalsStore();
 
 const socketConnected = ref(true);
@@ -40,27 +38,10 @@ const broadcastChannel = ref({
 const disconnectedMessage = ref();
 
 const { christmas } = storeToRefs(configStore);
-const { currentUser, loggedIn, banned } = storeToRefs(userAuthStore);
-const { nightmode, activityWatch } = storeToRefs(userPreferencesStore);
-const {
-	changeNightmode,
-} = userPreferencesStore;
+const { currentUser, loggedIn, banned, nightmode } = storeToRefs(userAuthStore);
 const { activeModals } = storeToRefs(modalsStore);
 const { openModal, closeCurrentModal } = modalsStore;
-
-const toggleNightMode = () => {
-	if (loggedIn.value) {
-		socket.dispatch(
-			"users.updatePreferences",
-			{ nightmode: !nightmode.value },
-			(res: GenericResponse) => {
-				if (res.status !== "success") new Toast(res.message);
-			}
-		);
-	} else {
-		broadcastChannel.value.nightmode.postMessage(!nightmode.value);
-	}
-};
+const { onReady } = useEvents();
 
 const enableNightmode = () => {
 	document.getElementsByTagName("html")[0].classList.add("night-mode");
@@ -85,23 +66,23 @@ watch(socketConnected, connected => {
 watch(banned, () => {
 	disconnectedMessage.value.hide();
 });
-watch(nightmode, enabled => {
-	if (enabled) enableNightmode();
+watch(nightmode, value => {
+	if (value) enableNightmode();
 	else disableNightmode();
+
+	localStorage.setItem("nightmode", value.toString());
 });
-watch(activityWatch, enabled => {
-	if (enabled) aw.enable();
-	else aw.disable();
-});
+watch(
+	() => currentUser.value?.activityWatch,
+	enabled => {
+		if (enabled) aw.enable();
+		else aw.disable();
+	}
+);
 watch(christmas, enabled => {
 	if (enabled) enableChristmasMode();
 	else disableChristmasMode();
 });
-watch(currentUser, user => {
-	if (!user) return;
-
-	changeNightmode(user.nightmode);
-});
 
 onMounted(async () => {
 	document.getElementsByTagName("html")[0].style.cssText =
@@ -110,7 +91,7 @@ onMounted(async () => {
 	window
 		.matchMedia("(prefers-color-scheme: dark)")
 		.addEventListener("change", e => {
-			if (e.matches === !nightmode.value) changeNightmode(true);
+			if (e.matches === !nightmode.value) nightmode.value = true;
 		});
 
 	disconnectedMessage.value = new Toast({
@@ -121,7 +102,7 @@ onMounted(async () => {
 
 	disconnectedMessage.value.hide();
 
-	socket.onConnect(() => {
+	await onReady(async () => {
 		socketConnected.value = true;
 
 		document.getElementsByTagName("html")[0].style.cssText =
@@ -142,7 +123,7 @@ onMounted(async () => {
 				`${configStore.cookie}.nightmode`
 			);
 			broadcastChannel.value.nightmode.onmessage = res => {
-				changeNightmode(!!res.data);
+				nightmode.value = !!res.data;
 			};
 		}
 
@@ -170,7 +151,7 @@ onMounted(async () => {
 			keyCode: 78,
 			ctrl: true,
 			alt: true,
-			handler: () => toggleNightMode()
+			handler: userAuthStore.toggleNightmode
 		});
 
 		keyboardShortcuts.registerShortcut("closeModal", {
@@ -194,7 +175,7 @@ onMounted(async () => {
 				localStorage.removeItem("github_redirect");
 			}
 		});
-	}, true);
+	});
 
 	socket.onDisconnect(() => {
 		socketConnected.value = false;
@@ -204,9 +185,7 @@ onMounted(async () => {
 		window.location.reload()
 	);
 
-	if (localStorage.getItem("nightmode") === "true") {
-		changeNightmode(true);
-	} else changeNightmode(false);
+	nightmode.value = localStorage.getItem("nightmode") === "true";
 });
 </script>
 

+ 8 - 45
frontend/src/components/MainHeader.vue

@@ -1,11 +1,8 @@
 <script setup lang="ts">
-import { defineAsyncComponent, ref, onMounted, watch, nextTick } from "vue";
-import Toast from "toasters";
+import { defineAsyncComponent, ref, onMounted, nextTick } from "vue";
 import { storeToRefs } from "pinia";
-import { useWebsocketsStore } from "@/stores/websockets";
 import { useConfigStore } from "@/stores/config";
 import { useUserAuthStore } from "@/stores/userAuth";
-import { useUserPreferencesStore } from "@/stores/userPreferences";
 import { useModalsStore } from "@/stores/modals";
 
 const ChristmasLights = defineAsyncComponent(
@@ -20,57 +17,25 @@ defineProps({
 
 const userAuthStore = useUserAuthStore();
 
-const localNightmode = ref(false);
 const isMobile = ref(false);
 const windowWidth = ref(0);
-const broadcastChannel = ref();
-
-const { socket } = useWebsocketsStore();
 
 const configStore = useConfigStore();
-const { cookie, sitename, registrationDisabled, christmas } =
-	storeToRefs(configStore);
+const { sitename, registrationDisabled, christmas } = storeToRefs(configStore);
 
-const { loggedIn, currentUser } = storeToRefs(userAuthStore);
-const { logout, hasPermission } = userAuthStore;
-const userPreferencesStore = useUserPreferencesStore();
-const { nightmode } = storeToRefs(userPreferencesStore);
+const { loggedIn, currentUser, nightmode } = storeToRefs(userAuthStore);
+const { logout, hasPermission, toggleNightmode } = userAuthStore;
 
 const { openModal } = useModalsStore();
 
-const toggleNightmode = toggle => {
-	localNightmode.value =
-		toggle === undefined ? !localNightmode.value : toggle;
-
-	if (loggedIn.value) {
-		socket.dispatch(
-			"users.updatePreferences",
-			{ nightmode: localNightmode.value },
-			res => {
-				if (res.status !== "success") new Toast(res.message);
-			}
-		);
-	} else {
-		broadcastChannel.value.postMessage(localNightmode.value);
-	}
-};
-
 const onResize = () => {
 	windowWidth.value = window.innerWidth;
 };
 
-watch(nightmode, value => {
-	localNightmode.value = value;
-});
-
 onMounted(async () => {
-	localNightmode.value = nightmode.value;
-
 	await nextTick();
 	onResize();
 	window.addEventListener("resize", onResize);
-
-	broadcastChannel.value = new BroadcastChannel(`${cookie.value}.nightmode`);
 });
 </script>
 
@@ -107,20 +72,18 @@ onMounted(async () => {
 			<div
 				class="nav-item"
 				id="nightmode-toggle"
-				@click="toggleNightmode(!localNightmode)"
+				@click="toggleNightmode"
 			>
 				<span
 					:class="{
 						'material-icons': true,
 						'night-mode-toggle': true,
-						'night-mode-on': localNightmode
+						'night-mode-on': nightmode
 					}"
-					:content="`${
-						localNightmode ? 'Disable' : 'Enable'
-					} Nightmode`"
+					:content="`${nightmode ? 'Disable' : 'Enable'} Nightmode`"
 					v-tippy
 				>
-					{{ localNightmode ? "dark_mode" : "light_mode" }}
+					{{ nightmode ? "dark_mode" : "light_mode" }}
 				</span>
 				<span class="night-mode-label">Toggle Nightmode</span>
 			</div>

+ 2 - 2
frontend/src/pages/Station/index.vue

@@ -132,8 +132,8 @@ const { activeModals } = storeToRefs(modalsStore);
 // TODO fix this if it still has some use, as this is no longer accurate
 // const video = computed(() => store.state.modals.editSong);
 
-const { loggedIn, currentUser } = storeToRefs(userAuthStore);
-const { nightmode, autoSkipDisliked } = storeToRefs(userPreferencesStore);
+const { loggedIn, currentUser, nightmode } = storeToRefs(userAuthStore);
+const { autoSkipDisliked } = storeToRefs(userPreferencesStore);
 const {
 	station,
 	currentSong,

+ 44 - 5
frontend/src/stores/userAuth.ts

@@ -1,5 +1,6 @@
 import { defineStore } from "pinia";
-import { computed, ref } from "vue";
+import { computed, ref, watch } from "vue";
+import Toast from "toasters";
 import validation from "@/validation";
 import { useWebsocketStore } from "@/stores/websocket";
 import { useConfigStore } from "@/stores/config";
@@ -31,6 +32,7 @@ export const useUserAuthStore = defineStore("userAuth", () => {
 	const gotData = ref(false);
 	const gotPermissions = ref(false);
 	const permissions = ref<Record<string, boolean>>({});
+	const nightmode = ref(false);
 
 	const loggedIn = computed(() => !!currentUser.value);
 
@@ -119,9 +121,11 @@ export const useUserAuthStore = defineStore("userAuth", () => {
 			data.SID
 		}; expires=${date.toUTCString()}; ${domain}${secure}path=/`;
 
-		const bc = new BroadcastChannel(`${configStore.cookie}.user_login`);
-		bc.postMessage(true);
-		bc.close();
+		const loginBroadcastChannel = new BroadcastChannel(
+			`${configStore.cookie}.user_login`
+		);
+		loginBroadcastChannel.postMessage(true);
+		loginBroadcastChannel.close();
 	};
 
 	const logout = async () => {
@@ -232,6 +236,39 @@ export const useUserAuthStore = defineStore("userAuth", () => {
 		}; expires=${date.toUTCString()}; ${domain}${secure}path=/`;
 	};
 
+	const toggleNightmode = async () => {
+		if (loggedIn.value) {
+			try {
+				await websocketStore.runJob(`data.users.updateById`, {
+					_id: currentUser.value._id,
+					query: {
+						nightmode: !nightmode.value
+					}
+				});
+			} catch (error) {
+				new Toast(error.message);
+			}
+		} else {
+			nightmode.value = !nightmode.value;
+
+			const nightmodeBroadcastChannel = new BroadcastChannel(
+				`${configStore.cookie}.nightmode`
+			);
+			nightmodeBroadcastChannel.postMessage(nightmode.value);
+			nightmodeBroadcastChannel.close();
+		}
+	};
+
+	watch(
+		currentUser,
+		user => {
+			if (!user) return;
+
+			nightmode.value = user.nightmode;
+		},
+		{ deep: true }
+	);
+
 	return {
 		userIdMap,
 		userIdRequested,
@@ -242,6 +279,7 @@ export const useUserAuthStore = defineStore("userAuth", () => {
 		gotData,
 		gotPermissions,
 		permissions,
+		nightmode,
 		loggedIn,
 		register,
 		login,
@@ -254,6 +292,7 @@ export const useUserAuthStore = defineStore("userAuth", () => {
 		banUser,
 		hasPermission,
 		updatePermissions,
-		resetCookieExpiration
+		resetCookieExpiration,
+		toggleNightmode
 	};
 });

+ 6 - 6
frontend/src/stores/websocket.ts

@@ -1,4 +1,4 @@
-import { defineStore } from "pinia";
+import { defineStore, storeToRefs } from "pinia";
 import { ref } from "vue";
 import { generateUuid } from "@common/utils/generateUuid";
 import { forEachIn } from "@common/utils/forEachIn";
@@ -187,14 +187,14 @@ export const useWebsocketStore = defineStore("websocket", () => {
 
 		ready.value = true;
 
-		userAuthStore.currentUser = data.user
+		const { currentUser, gotData, loggedIn } = storeToRefs(userAuthStore);
+
+		currentUser.value = data.user
 			? await modelStore.registerModel(data.user)
 			: null;
-		userAuthStore.gotData = true;
+		gotData.value = true;
 
-		if (userAuthStore.loggedIn) {
-			userAuthStore.resetCookieExpiration();
-		}
+		if (loggedIn.value) userAuthStore.resetCookieExpiration();
 
 		if (configStore.experimental.media_session) ms.initialize();
 		else ms.uninitialize();