mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-17 08:15:44 +00:00
Update vaadin
WIP: User management
This commit is contained in:
@@ -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
@@ -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,3 +18,21 @@ export function rand(min: number, max: number) {
|
|||||||
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
-2
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user