diff --git a/.run/UI debug.run.xml b/.run/UI debug.run.xml index 0a58cbb..b656b83 100644 --- a/.run/UI debug.run.xml +++ b/.run/UI debug.run.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/frontend/components/ProfileMenu.tsx b/frontend/components/ProfileMenu.tsx index d20ef06..55028c7 100644 --- a/frontend/components/ProfileMenu.tsx +++ b/frontend/components/ProfileMenu.tsx @@ -1,11 +1,9 @@ import {useAuth} from "Frontend/util/auth"; -import {useNavigate} from "react-router-dom"; import {GearFine, Question, SignOut, User} from "@phosphor-icons/react"; import {Avatar, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger} from "@nextui-org/react"; export default function ProfileMenu() { const {state, logout} = useAuth(); - const navigate = useNavigate(); const profileMenuItems = [ { @@ -17,7 +15,7 @@ export default function ProfileMenu() { label: "Administration", icon: , onClick: () => alert("Administration"), - showIf: state.user?.authorities?.some(a => a?.includes("ADMIN")) + showIf: state.user?.roles?.some(a => a?.includes("ADMIN")) }, { label: "Help", @@ -35,7 +33,15 @@ export default function ProfileMenu() { return ( - + {/* @ts-ignore */} @@ -48,7 +54,7 @@ export default function ProfileMenu() { startContent={
{icon}
} /* @ts-ignore */ color={color ? color : ""} - className={`text-${color}`} + className={`text-${color} hover:bg-primary/20`} > {label} : null diff --git a/frontend/components/theming/ThemePreview.tsx b/frontend/components/theming/ThemePreview.tsx index 353143f..c904e8f 100644 --- a/frontend/components/theming/ThemePreview.tsx +++ b/frontend/components/theming/ThemePreview.tsx @@ -1,6 +1,5 @@ import {Theme} from "Frontend/theming/theme"; -import {Card, Tooltip} from "@nextui-org/react"; -import GameyfinLogo from "Frontend/components/theming/GameyfinLogo"; +import {Tooltip} from "@nextui-org/react"; export default function ThemePreview({theme, mode, isSelected}: { theme: Theme, @@ -11,26 +10,9 @@ export default function ThemePreview({theme, mode, isSelected}: { {theme.name?.replace("-", " ")}

} placement="bottom">
); -} - -/* - - - - - - - - - - - - - - -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/frontend/components/wizard/Wizard.tsx b/frontend/components/wizard/Wizard.tsx index 1b799bb..7d6894c 100644 --- a/frontend/components/wizard/Wizard.tsx +++ b/frontend/components/wizard/Wizard.tsx @@ -7,7 +7,7 @@ import {Step, Stepper} from "@material-tailwind/react"; const Wizard = ({children, initialValues, onSubmit}: { children: ReactNode, initialValues: any, - onSubmit: (values: any, bag: FormikHelpers | FormikBag) => Promise + onSubmit: (values: any, bag: FormikHelpers | FormikBag) => Promise }) => { const [stepNumber, setStepNumber] = useState(0); const steps = React.Children.toArray(children); diff --git a/frontend/views/MainLayout.tsx b/frontend/views/MainLayout.tsx index 32d5060..13013bf 100644 --- a/frontend/views/MainLayout.tsx +++ b/frontend/views/MainLayout.tsx @@ -2,7 +2,8 @@ import {useRouteMetadata} from 'Frontend/util/routing.js'; import {useEffect} from 'react'; import ProfileMenu from "Frontend/components/ProfileMenu"; import {Outlet} from "react-router-dom"; -import {Card} from "@nextui-org/react"; +import {Navbar, NavbarBrand, NavbarContent, NavbarItem} from "@nextui-org/react"; +import GameyfinLogo from "Frontend/components/theming/GameyfinLogo"; export default function MainLayout() { const currentTitle = `Gameyfin - ${useRouteMetadata()?.title}` ?? 'Gameyfin'; @@ -12,16 +13,16 @@ export default function MainLayout() { return ( <> - -
- Gameyfin - -
-
+ + + + + + + + + + diff --git a/frontend/views/SetupView.tsx b/frontend/views/SetupView.tsx index 84a642f..8af28bc 100644 --- a/frontend/views/SetupView.tsx +++ b/frontend/views/SetupView.tsx @@ -9,8 +9,7 @@ import {themes} from "Frontend/theming/themes"; import {Card, Switch} from "@nextui-org/react"; import {useTheme} from "next-themes"; import {Theme} from "Frontend/theming/theme"; - -const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); +import {UserEndpoint} from "Frontend/generated/endpoints"; function WelcomeStep() { return ( @@ -95,6 +94,11 @@ function UserStep() { name="username" type="text" /> + (
- sleep(300).then(() => alert(JSON.stringify(values, null, 2))) - } + initialValues={{username: '', email: '', password: '', passwordRepeat: ''}} + onSubmit={(values: any) => UserEndpoint.registerInitialSuperAdmin({ + username: values.username, + password: values.password, + email: values.email + } + ).then(() => alert("Successfully registered!"))} > }> @@ -144,6 +151,9 @@ const SetupView = () => ( password: Yup.string() .min(8, 'Password must be at least 8 characters long') .required('Required'), + email: Yup.string() + .email() + .required('Required'), passwordRepeat: Yup.string() .equals([Yup.ref('password')], 'Passwords do not match') .required('Required') diff --git a/frontend/views/TestView.tsx b/frontend/views/TestView.tsx index bafa2e0..bf8c1fb 100644 --- a/frontend/views/TestView.tsx +++ b/frontend/views/TestView.tsx @@ -2,7 +2,7 @@ import {Link} from "react-router-dom"; export default function TestView() { return ( -
+
Setup
); diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/RoleService.kt b/src/main/kotlin/de/grimsi/gameyfin/users/RoleService.kt index 683c910..0e78c5e 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/users/RoleService.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/users/RoleService.kt @@ -1,6 +1,7 @@ package de.grimsi.gameyfin.users import de.grimsi.gameyfin.config.Roles +import de.grimsi.gameyfin.users.entities.Role import de.grimsi.gameyfin.users.persistence.RoleRepository import jakarta.transaction.Transactional import org.springframework.stereotype.Service @@ -18,4 +19,12 @@ class RoleService( val r = roleRepository.findByRolename(role.roleName) ?: return 0 return r.users.size } + + fun toRoles(roles: Collection): List { + return roles.mapNotNull { r -> roleRepository.findByRolename(r) } + } + + fun toRole(role: String): Role { + return roleRepository.findByRolename(role) ?: throw RuntimeException("Role $role does not exist") + } } \ No newline at end of file diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/UserEndpoint.kt b/src/main/kotlin/de/grimsi/gameyfin/users/UserEndpoint.kt index 3ebc9b5..659760e 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/users/UserEndpoint.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/users/UserEndpoint.kt @@ -1,37 +1,51 @@ package de.grimsi.gameyfin.users +import de.grimsi.gameyfin.config.Roles import de.grimsi.gameyfin.users.dto.UserInfo import de.grimsi.gameyfin.users.dto.UserRegistration import de.grimsi.gameyfin.users.entities.User import dev.hilla.Endpoint import jakarta.annotation.security.PermitAll +import org.springframework.http.HttpStatus import org.springframework.security.core.Authentication import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.web.server.ResponseStatusException @Endpoint class UserEndpoint( - private val userService: UserService + private val userService: UserService, + private val roleService: RoleService, ) { @PermitAll fun getUserInfo(): UserInfo { val auth: Authentication = SecurityContextHolder.getContext().authentication val authorities: List = auth.authorities.map { g: GrantedAuthority -> g.authority } - return UserInfo(auth.name, authorities) + return UserInfo(username = auth.name, roles = authorities) } @PermitAll - fun registerUser(registration: UserRegistration): Boolean { + fun registerUser(registration: UserRegistration): UserInfo { + val user: User = registerUser(registration, listOf(Roles.USER)) + return userService.toUserInfo(user) + } + + @PermitAll + fun registerInitialSuperAdmin(registration: UserRegistration): UserInfo { + if (roleService.getUserCountForRole(Roles.SUPERADMIN) > 0) throw ResponseStatusException(HttpStatus.UNAUTHORIZED) + val superAdmin: User = registerUser(registration, listOf(Roles.SUPERADMIN)) + return userService.toUserInfo(superAdmin) + } + + private fun registerUser(registration: UserRegistration, roles: List): User { val user = User( username = registration.username, password = registration.password, email = registration.email, - roles = userService.toRoles(registration.roles) + roles = roles.map { r -> roleService.toRole(r.roleName) } ) - userService.registerUser(user) - - return true + return userService.registerUser(user) } } \ No newline at end of file diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt b/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt index 842a043..6f43716 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt @@ -1,8 +1,8 @@ package de.grimsi.gameyfin.users +import de.grimsi.gameyfin.users.dto.UserInfo import de.grimsi.gameyfin.users.entities.Role import de.grimsi.gameyfin.users.entities.User -import de.grimsi.gameyfin.users.persistence.RoleRepository import de.grimsi.gameyfin.users.persistence.UserRepository import jakarta.transaction.Transactional import org.springframework.security.core.GrantedAuthority @@ -18,7 +18,6 @@ import org.springframework.stereotype.Service @Transactional class UserService( private val userRepository: UserRepository, - private val roleRepository: RoleRepository, private val passwordEncoder: PasswordEncoder ) : UserDetailsService { @@ -42,8 +41,12 @@ class UserService( return userRepository.save(user) } - fun toRoles(roles: Collection): List { - return roles.mapNotNull { r -> roleRepository.findByRolename(r) } + fun toUserInfo(user: User): UserInfo { + return UserInfo( + username = user.username, + email = user.email, + roles = user.roles.map { r -> r.rolename } + ) } private fun toAuthorities(roles: Collection): List { diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/dto/UserInfo.kt b/src/main/kotlin/de/grimsi/gameyfin/users/dto/UserInfo.kt index 261b23f..55212f8 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/users/dto/UserInfo.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/users/dto/UserInfo.kt @@ -1,6 +1,7 @@ package de.grimsi.gameyfin.users.dto data class UserInfo( - val name: String, - val authorities: List + val username: String, + val email: String? = null, + val roles: List ) \ No newline at end of file diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/dto/UserRegistration.kt b/src/main/kotlin/de/grimsi/gameyfin/users/dto/UserRegistration.kt index 929939c..cc7ccfa 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/users/dto/UserRegistration.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/users/dto/UserRegistration.kt @@ -3,6 +3,5 @@ package de.grimsi.gameyfin.users.dto data class UserRegistration( val username: String, val password: String, - val email: String, - val roles: List + val email: String ) \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 7bd9411..2d84231 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -8,7 +8,7 @@ server: tracking-modes: cookie spring: - # Workaround for https://github.dev/hilla/issues/842 + # Workaround for https://github.com/vaadin/hilla/issues/842 devtools.restart.additional-exclude: dev/hilla/openapi.json jpa: defer-datasource-initialization: true