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 (
<>
-
-
-

-
-
-
+
+
+
+
+
+
+
+
+
+
>
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