Update vaadin

WIP: User management
This commit is contained in:
grimsi
2024-09-13 14:28:40 +02:00
parent 0fe91a1980
commit d2f720a6ed
10 changed files with 96 additions and 10 deletions
+3
View File
@@ -1,9 +1,12 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="GameyfinApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true"> <configuration default="false" name="GameyfinApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
<option name="ACTIVE_PROFILES" value="dev" /> <option name="ACTIVE_PROFILES" value="dev" />
<option name="ALTERNATIVE_JRE_PATH" value="BUNDLED" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<module name="gameyfin.main" /> <module name="gameyfin.main" />
<option name="SHORTEN_COMMAND_LINE" value="ARGS_FILE" /> <option name="SHORTEN_COMMAND_LINE" value="ARGS_FILE" />
<option name="SPRING_BOOT_MAIN_CLASS" value="de.grimsi.gameyfin.GameyfinApplication" /> <option name="SPRING_BOOT_MAIN_CLASS" value="de.grimsi.gameyfin.GameyfinApplication" />
<option name="VM_PARAMETERS" value="-XX:+AllowEnhancedClassRedefinition -XX:HotswapAgent=fatjar" />
<extension name="coverage"> <extension name="coverage">
<pattern> <pattern>
<option name="PATTERN" value="de.grimsi.gameyfin.*" /> <option name="PATTERN" value="de.grimsi.gameyfin.*" />
+2 -2
View File
@@ -18,7 +18,7 @@ group = "de.grimsi"
version = "2.0.0-SNAPSHOT" version = "2.0.0-SNAPSHOT"
description = "gameyfin" description = "gameyfin"
java.sourceCompatibility = JavaVersion.VERSION_22 java.sourceCompatibility = JavaVersion.VERSION_21
configurations { configurations {
compileOnly { compileOnly {
@@ -76,7 +76,7 @@ tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile> {
compilerOptions { compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
jvmTarget.set(JvmTarget.JVM_22) jvmTarget.set(JvmTarget.JVM_21)
progressiveMode.set(true) progressiveMode.set(true)
freeCompilerArgs.add("-Xjsr305=strict") freeCompilerArgs.add("-Xjsr305=strict")
} }
@@ -1,24 +1,39 @@
import React from "react"; import React, {useEffect, useState} from "react";
import ConfigFormField from "Frontend/components/administration/ConfigFormField"; import ConfigFormField from "Frontend/components/administration/ConfigFormField";
import withConfigPage from "Frontend/components/administration/withConfigPage"; import withConfigPage from "Frontend/components/administration/withConfigPage";
import Section from "Frontend/components/general/Section"; import Section from "Frontend/components/general/Section";
import * as Yup from "yup"; import * as Yup from "yup";
import {UserEndpoint} from "Frontend/generated/endpoints";
import UserInfoDto from "Frontend/generated/de/grimsi/gameyfin/users/dto/UserInfoDto";
import {UserCard} from "Frontend/components/general/UserCard";
function UserManagementLayout({getConfig, formik}: any) { function UserManagementLayout({getConfig, formik}: any) {
const [users, setUsers] = useState<UserInfoDto[]>([]);
useEffect(() => {
UserEndpoint.getAllUsers().then(
(response) => setUsers(response as UserInfoDto[])
);
}, []);
return ( return (
<div className="flex flex-col flex-grow"> <div className="flex flex-col flex-grow">
<Section title="Users"/>
{/* TODO */}
<Section title="Sign-Ups"/> <Section title="Sign-Ups"/>
<div className="flex flex-row"> <div className="flex flex-row">
<ConfigFormField configElement={getConfig("users.sign-ups.allow")}/> <ConfigFormField configElement={getConfig("users.sign-ups.allow")}/>
<ConfigFormField configElement={getConfig("users.sign-ups.confirm")} <ConfigFormField configElement={getConfig("users.sign-ups.confirm")}
isDisabled={!formik.values.users["sign-ups"].allow}/> isDisabled={!formik.values.users["sign-ups"].allow}/>
</div> </div>
<Section title="Users"/>
<div className="grid grid-flow-col grid-cols-4 gap-4">
{users.map((user) => <UserCard user={user} key={user.username}/>)}
</div>
</div> </div>
); )
;
} }
const validationSchema = Yup.object({ const validationSchema = Yup.object({
@@ -0,0 +1,21 @@
import UserInfoDto from "Frontend/generated/de/grimsi/gameyfin/users/dto/UserInfoDto";
import {Avatar, Card, Chip} from "@nextui-org/react";
import {roleToColor, roleToRoleName} from "Frontend/util/utils";
export function UserCard({user}: { user: UserInfoDto }) {
return (
<Card className="flex flex-row flex-grow items-center gap-4 p-2">
<Avatar classNames={{
base: "gradient-primary size-20",
icon: "text-background/80"
}}></Avatar>
<div className="flex flex-col gap-1">
<p className="font-semibold">{user.username}</p>
<p className="text-sm">{user.email}</p>
{user.roles?.map((role) =>
<Chip key={role} size="sm" radius="sm"
className={`text-xs bg-${roleToColor(role!)}-500`}>{roleToRoleName(role!)}</Chip>)}
</div>
</Card>
)
}
+18
View File
@@ -17,4 +17,22 @@ export function rand(min: number, max: number) {
const minCeiled = Math.ceil(min); const minCeiled = Math.ceil(min);
const maxFloored = Math.floor(max); const maxFloored = Math.floor(max);
return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled);
}
export function roleToRoleName(role: string) {
role = role.replace("ROLE_", "").toLowerCase();
return role.charAt(0).toUpperCase() + role.slice(1);
}
export function roleToColor(role: string) {
switch (role) {
case "ROLE_SUPERADMIN":
return "red";
case "ROLE_ADMIN":
return "orange";
case "ROLE_USER":
return "blue";
default:
return "gray";
}
} }
@@ -3,6 +3,7 @@ package de.grimsi.gameyfin.config
import de.grimsi.gameyfin.config.dto.ConfigEntryDto import de.grimsi.gameyfin.config.dto.ConfigEntryDto
import de.grimsi.gameyfin.config.entities.ConfigEntry import de.grimsi.gameyfin.config.entities.ConfigEntry
import de.grimsi.gameyfin.config.persistence.ConfigRepository import de.grimsi.gameyfin.config.persistence.ConfigRepository
import io.github.oshai.kotlinlogging.KotlinLogging
import jakarta.transaction.Transactional import jakarta.transaction.Transactional
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.io.Serializable import java.io.Serializable
@@ -12,6 +13,7 @@ import java.io.Serializable
class ConfigService( class ConfigService(
private val appConfigRepository: ConfigRepository private val appConfigRepository: ConfigRepository
) { ) {
private val log = KotlinLogging.logger {}
/** /**
* Get all known config values. * Get all known config values.
@@ -20,6 +22,9 @@ class ConfigService(
* @return A map of all config values * @return A map of all config values
*/ */
fun getAllConfigValues(prefix: String?): List<ConfigEntryDto> { fun getAllConfigValues(prefix: String?): List<ConfigEntryDto> {
log.info { "Getting all config values for prefix '$prefix'" }
var configProperties = ConfigProperties::class.sealedSubclasses.flatMap { subclass -> var configProperties = ConfigProperties::class.sealedSubclasses.flatMap { subclass ->
subclass.objectInstance?.let { listOf(it) } ?: listOf() subclass.objectInstance?.let { listOf(it) } ?: listOf()
} }
@@ -49,6 +54,9 @@ class ConfigService(
* @throws IllegalArgumentException if no value is set and no default value exists * @throws IllegalArgumentException if no value is set and no default value exists
*/ */
fun <T : Serializable> getConfigValue(configProperty: ConfigProperties<T>): T { fun <T : Serializable> getConfigValue(configProperty: ConfigProperties<T>): T {
log.info { "Getting config value '${configProperty.key}'" }
val appConfig = appConfigRepository.findById(configProperty.key).orElse(null) val appConfig = appConfigRepository.findById(configProperty.key).orElse(null)
return if (appConfig != null) { return if (appConfig != null) {
getValue(appConfig.value, configProperty) getValue(appConfig.value, configProperty)
@@ -66,6 +74,9 @@ class ConfigService(
* @throws IllegalArgumentException if no value is set and no default value exists * @throws IllegalArgumentException if no value is set and no default value exists
*/ */
fun getConfigValue(key: String): String { fun getConfigValue(key: String): String {
log.info { "Getting config value '$key'" }
val configProperty = findConfigProperty(key) val configProperty = findConfigProperty(key)
val appConfig = appConfigRepository.findById(configProperty.key).orElse(null) val appConfig = appConfigRepository.findById(configProperty.key).orElse(null)
@@ -86,6 +97,9 @@ class ConfigService(
* @throws IllegalArgumentException if the value can't be cast to the type defined for the config property * @throws IllegalArgumentException if the value can't be cast to the type defined for the config property
*/ */
fun <T : Serializable> setConfigValue(key: String, value: T) { fun <T : Serializable> setConfigValue(key: String, value: T) {
log.info { "Set config value '$key' to '$value'" }
val configKey = findConfigProperty(key) val configKey = findConfigProperty(key)
// Check if the value can be cast to the type defined for the config property // Check if the value can be cast to the type defined for the config property
@@ -109,6 +123,9 @@ class ConfigService(
* @param key: Key of the config property * @param key: Key of the config property
*/ */
fun resetConfigValue(key: String) { fun resetConfigValue(key: String) {
log.info { "Reset config value '$key'" }
val configKey = findConfigProperty(key) val configKey = findConfigProperty(key)
if (configKey.default == null) { if (configKey.default == null) {
@@ -129,6 +146,9 @@ class ConfigService(
* @param key: Key of the config property * @param key: Key of the config property
*/ */
fun deleteConfig(key: String) { fun deleteConfig(key: String) {
log.info { "Delete config value '$key'" }
val configKey = findConfigProperty(key) val configKey = findConfigProperty(key)
appConfigRepository.deleteById(configKey.key) appConfigRepository.deleteById(configKey.key)
} }
@@ -1,6 +1,5 @@
package de.grimsi.gameyfin.setup package de.grimsi.gameyfin.meta
import de.grimsi.gameyfin.meta.Roles
import de.grimsi.gameyfin.users.UserService import de.grimsi.gameyfin.users.UserService
import de.grimsi.gameyfin.users.entities.Role import de.grimsi.gameyfin.users.entities.Role
import de.grimsi.gameyfin.users.entities.User import de.grimsi.gameyfin.users.entities.User
@@ -22,6 +22,12 @@ class UserEndpoint(
return userService.getUserInfo(auth.name) return userService.getUserInfo(auth.name)
} }
@PermitAll
@RolesAllowed(Roles.Names.SUPERADMIN, Roles.Names.ADMIN)
fun getAllUsers(): List<UserInfoDto> {
return userService.getAllUsers()
}
@PermitAll @PermitAll
fun registerUser(registration: UserRegistrationDto): UserInfoDto { fun registerUser(registration: UserRegistrationDto): UserInfoDto {
val user: User = registerUser(registration, listOf(Roles.USER)) val user: User = registerUser(registration, listOf(Roles.USER))
@@ -35,7 +41,7 @@ class UserEndpoint(
} }
@RolesAllowed(Roles.Names.SUPERADMIN, Roles.Names.ADMIN) @RolesAllowed(Roles.Names.SUPERADMIN, Roles.Names.ADMIN)
fun updateUser(username: String, updates: UserUpdateDto) { fun updateUserByName(username: String, updates: UserUpdateDto) {
userService.updateUser(username, updates) userService.updateUser(username, updates)
} }
@@ -41,6 +41,10 @@ class UserService(
fun existsByUsername(username: String): Boolean = userRepository.findByUsername(username) != null fun existsByUsername(username: String): Boolean = userRepository.findByUsername(username) != null
fun getAllUsers(): List<UserInfoDto> {
return userRepository.findAll().map { u -> toUserInfo(u) }
}
fun getUserInfo(username: String): UserInfoDto { fun getUserInfo(username: String): UserInfoDto {
val user = userByUsername(username) val user = userByUsername(username)
return toUserInfo(user) return toUserInfo(user)