diff --git a/app/src/main/frontend/components/general/input/ComboButton.tsx b/app/src/main/frontend/components/general/input/ComboButton.tsx index b23817f..7210166 100644 --- a/app/src/main/frontend/components/general/input/ComboButton.tsx +++ b/app/src/main/frontend/components/general/input/ComboButton.tsx @@ -9,7 +9,7 @@ import { SharedSelection } from "@heroui/react"; import {CaretDown} from "@phosphor-icons/react"; -import {UserPreferenceService} from "Frontend/util/user-preference-service"; +import {useUserPreferenceService} from "Frontend/util/user-preference-service"; export interface ComboButtonOption { label: string; @@ -27,11 +27,12 @@ export interface ComboButtonProps { export default function ComboButton({options, preferredOptionKey, description}: ComboButtonProps) { const [selectedOption, setSelectedOption] = useState(new Set([Object.keys(options)[0]])); const selectedOptionValue = Array.from(selectedOption)[0]; + const userPreferenceService = useUserPreferenceService(); useEffect(() => { if (!preferredOptionKey) return; - UserPreferenceService.get(preferredOptionKey).then((key) => { + userPreferenceService.get(preferredOptionKey).then((key) => { if (key && options[key]) { setSelectedOption(new Set([key])); } else { @@ -44,7 +45,7 @@ export default function ComboButton({options, preferredOptionKey, description}: if (!keys.currentKey) return; if (preferredOptionKey) { - await UserPreferenceService.set(preferredOptionKey, keys.currentKey); + await userPreferenceService.set(preferredOptionKey, keys.currentKey); } setSelectedOption(new Set([keys.currentKey])); diff --git a/app/src/main/frontend/components/theming/ThemeSelector.tsx b/app/src/main/frontend/components/theming/ThemeSelector.tsx index 6d2aaed..315d64f 100644 --- a/app/src/main/frontend/components/theming/ThemeSelector.tsx +++ b/app/src/main/frontend/components/theming/ThemeSelector.tsx @@ -5,13 +5,14 @@ import {themes} from "Frontend/theming/themes"; import {Theme} from "Frontend/theming/theme"; import ThemePreview from "Frontend/components/theming/ThemePreview"; import {toTitleCase} from "Frontend/util/utils"; -import {UserPreferenceService} from "Frontend/util/user-preference-service"; +import {useUserPreferenceService} from "Frontend/util/user-preference-service"; export function ThemeSelector() { const {theme, setTheme} = useTheme(); const [selectedTheme, setSelectedTheme] = useState(theme?.substring(0, theme?.lastIndexOf("-"))); const [selectedMode, setSelectedMode] = useState(); + const userPreferenceService = useUserPreferenceService(); useEffect(() => { if (!selectedMode) @@ -24,7 +25,7 @@ export function ThemeSelector() { if (selectedMode instanceof Set) { let theme = `${selectedTheme}-${selectedMode.values().next().value}`; setTheme(theme); - UserPreferenceService.set("preferred-theme", theme).catch(console.error); + userPreferenceService.set("preferred-theme", theme).catch(console.error); } } diff --git a/app/src/main/frontend/util/user-preference-service.ts b/app/src/main/frontend/util/user-preference-service.ts index 0d6e6c5..191f160 100644 --- a/app/src/main/frontend/util/user-preference-service.ts +++ b/app/src/main/frontend/util/user-preference-service.ts @@ -1,39 +1,45 @@ import {UserPreferencesEndpoint} from "Frontend/generated/endpoints"; +import {useAuth} from "Frontend/util/auth"; -export class UserPreferenceService { - static LOCAL_STORAGE_PREFIX = "gameyfin."; +export function useUserPreferenceService() { + const LOCAL_STORAGE_PREFIX = "gameyfin."; + const auth = useAuth(); + + async function sync(): Promise { + if (auth.state.user === undefined) return; - static async sync(): Promise { let keys = Object.keys(localStorage); for (let key of keys) { - if (!key.startsWith(`${this.LOCAL_STORAGE_PREFIX}`)) { + if (!key.startsWith(LOCAL_STORAGE_PREFIX)) { continue; } - let value = await UserPreferencesEndpoint.get(key.replace(this.LOCAL_STORAGE_PREFIX, "")); + let value = await UserPreferencesEndpoint.get(key.replace(LOCAL_STORAGE_PREFIX, "")); if (value) { localStorage.setItem(key, value); } } } - static async get(key: string): Promise { - let localPreference = localStorage.getItem(`${this.LOCAL_STORAGE_PREFIX}${key}`); - + async function get(key: string): Promise { + let localPreference = localStorage.getItem(`${LOCAL_STORAGE_PREFIX}${key}`); if (localPreference) { return localPreference; } else { + if (auth.state.user === undefined) return undefined; let syncedPreference = await UserPreferencesEndpoint.get(key); if (syncedPreference) { - localStorage.setItem(`${this.LOCAL_STORAGE_PREFIX}${key}`, syncedPreference); + localStorage.setItem(`${LOCAL_STORAGE_PREFIX}${key}`, syncedPreference); return syncedPreference; } } - return undefined; } - static async set(key: string, value: string) { + async function set(key: string, value: string): Promise { + localStorage.setItem(`${LOCAL_STORAGE_PREFIX}${key}`, value); + if (auth.state.user === undefined) return; await UserPreferencesEndpoint.set(key, value); - localStorage.setItem(`${this.LOCAL_STORAGE_PREFIX}${key}`, value); } + + return {sync, get, set}; } \ No newline at end of file diff --git a/app/src/main/frontend/views/MainLayout.tsx b/app/src/main/frontend/views/MainLayout.tsx index 080bb16..9f56f43 100644 --- a/app/src/main/frontend/views/MainLayout.tsx +++ b/app/src/main/frontend/views/MainLayout.tsx @@ -9,7 +9,7 @@ import {useAuth} from "Frontend/util/auth"; import {ArrowLeft, DiceSix, Heart, House, ListMagnifyingGlass, SignIn} from "@phosphor-icons/react"; import Confetti, {ConfettiProps} from "react-confetti-boom"; import {useTheme} from "next-themes"; -import {UserPreferenceService} from "Frontend/util/user-preference-service"; +import {useUserPreferenceService} from "Frontend/util/user-preference-service"; import SearchBar from "Frontend/components/general/SearchBar"; import {useSnapshot} from "valtio/react"; import {gameState} from "Frontend/state/GameState"; @@ -20,6 +20,7 @@ export default function MainLayout() { const navigate = useNavigate(); const location = useLocation(); const auth = useAuth(); + const userPreferenceService = useUserPreferenceService(); const routeMetadata = useRouteMetadata(); const {setTheme} = useTheme(); const isSearchPage = location.pathname.startsWith("/search"); @@ -30,11 +31,13 @@ export default function MainLayout() { useEffect(() => { let newTitle = `Gameyfin - ${routeMetadata?.title}`; window.addEventListener('popstate', () => document.title = newTitle); + }, []); - UserPreferenceService.sync() + useEffect(() => { + userPreferenceService.sync() .then(() => loadUserTheme().catch(console.error)) .catch(console.error); - }, []); + }, [auth.state.user]); const confettiProps: ConfettiProps = { mode: 'boom', @@ -47,7 +50,7 @@ export default function MainLayout() { } async function loadUserTheme() { - let syncedTheme = await UserPreferenceService.get("preferred-theme") + let syncedTheme = await userPreferenceService.get("preferred-theme") if (syncedTheme !== undefined) { setTheme(syncedTheme); } diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt index 3cd9ebf..36b04e9 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt @@ -45,6 +45,7 @@ class SecurityConfig( .requestMatchers("/accept-invitation").permitAll() .requestMatchers("/public/**").permitAll() .requestMatchers("/images/**").permitAll() + .requestMatchers("/favicon.ico").permitAll() // Dynamic public access for certain endpoints auth.requestMatchers("/").access(DynamicPublicAccessAuthorizationManager(config)) diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryEndpoint.kt index 0e3476b..25d9087 100644 --- a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryEndpoint.kt +++ b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryEndpoint.kt @@ -26,8 +26,7 @@ class LibraryEndpoint( } fun getAll() = libraryService.getAll() - - + fun subscribeToScanProgressEvents(): Flux> { val user = userService.getCurrentUser() return if (user.isAdmin()) LibraryService.subscribeToScanProgressEvents() diff --git a/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt index f208966..1fc3774 100644 --- a/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt +++ b/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt @@ -1,5 +1,6 @@ package org.gameyfin.app.users +import com.vaadin.flow.server.auth.AnonymousAllowed import com.vaadin.hilla.Endpoint import jakarta.annotation.security.PermitAll import jakarta.annotation.security.RolesAllowed @@ -16,8 +17,10 @@ class UserEndpoint( private val userService: UserService, private val roleService: RoleService ) { - @PermitAll - fun getUserInfo(): UserInfoDto { + @AnonymousAllowed + fun getUserInfo(): UserInfoDto? { + val auth = SecurityContextHolder.getContext().authentication + if (!auth.isAuthenticated || auth.principal == "anonymousUser") return null return userService.getUserInfo() }