mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 08:15:48 +00:00
Refine error handling in UI
Implement SystemEndpoint
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Generate Hilla sources" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<option name="executionName" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="externalSystemIdString" value="GRADLE" />
|
||||||
|
<option name="scriptParameters" value="" />
|
||||||
|
<option name="taskDescriptions">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="taskNames">
|
||||||
|
<list>
|
||||||
|
<option value="hillaGenerate" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<RunAsTest>false</RunAsTest>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
+18
-11
@@ -1,12 +1,13 @@
|
|||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("org.springframework.boot") version "3.2.2"
|
val kotlinVersion = "2.0.0"
|
||||||
id("io.spring.dependency-management") version "1.1.4"
|
id("org.springframework.boot") version "3.3.0"
|
||||||
|
id("io.spring.dependency-management") version "1.1.5"
|
||||||
id("dev.hilla") version "2.5.6"
|
id("dev.hilla") version "2.5.6"
|
||||||
kotlin("jvm") version "1.9.22"
|
kotlin("jvm") version kotlinVersion
|
||||||
kotlin("plugin.spring") version "1.9.22"
|
kotlin("plugin.spring") version kotlinVersion
|
||||||
kotlin("plugin.jpa") version "1.9.22"
|
kotlin("plugin.jpa") version kotlinVersion
|
||||||
java
|
java
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ group = "de.grimsi"
|
|||||||
version = "2.0.0-SNAPSHOT"
|
version = "2.0.0-SNAPSHOT"
|
||||||
description = "gameyfin"
|
description = "gameyfin"
|
||||||
|
|
||||||
java.sourceCompatibility = JavaVersion.VERSION_21
|
java.sourceCompatibility = JavaVersion.VERSION_22
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
compileOnly {
|
compileOnly {
|
||||||
@@ -34,6 +35,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extra["hillaVersion"] = "2.5.6"
|
extra["hillaVersion"] = "2.5.6"
|
||||||
|
val springCloudVersion by extra("2023.0.2")
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Spring Boot & Kotlin
|
// Spring Boot & Kotlin
|
||||||
@@ -54,6 +56,7 @@ dependencies {
|
|||||||
// Persistence
|
// Persistence
|
||||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
implementation("com.github.paulcwarren:spring-content-fs-boot-starter:3.0.7")
|
implementation("com.github.paulcwarren:spring-content-fs-boot-starter:3.0.7")
|
||||||
|
implementation("org.springframework.cloud:spring-cloud-starter")
|
||||||
|
|
||||||
// Development
|
// Development
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||||
@@ -67,13 +70,17 @@ dependencies {
|
|||||||
dependencyManagement {
|
dependencyManagement {
|
||||||
imports {
|
imports {
|
||||||
mavenBom("dev.hilla:hilla-bom:${property("hillaVersion")}")
|
mavenBom("dev.hilla:hilla-bom:${property("hillaVersion")}")
|
||||||
|
mavenBom("org.springframework.cloud:spring-cloud-dependencies:$springCloudVersion")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<KotlinCompile> {
|
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile> {
|
||||||
kotlinOptions {
|
compilerOptions {
|
||||||
freeCompilerArgs += "-Xjsr305=strict"
|
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
|
||||||
jvmTarget = "21"
|
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
|
||||||
|
jvmTarget.set(JvmTarget.JVM_22)
|
||||||
|
progressiveMode.set(true)
|
||||||
|
freeCompilerArgs.add("-Xjsr305=strict")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import {themeNames} from "Frontend/theming/themes";
|
|||||||
import {AuthProvider} from "Frontend/util/auth";
|
import {AuthProvider} from "Frontend/util/auth";
|
||||||
import {IconContext} from "@phosphor-icons/react";
|
import {IconContext} from "@phosphor-icons/react";
|
||||||
import {Toaster} from "Frontend/@/components/ui/sonner";
|
import {Toaster} from "Frontend/@/components/ui/sonner";
|
||||||
|
import client from "Frontend/generated/connect-client.default";
|
||||||
|
import {ErrorHandlingMiddleware} from "Frontend/util/middleware";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
client.middlewares.push(ErrorHandlingMiddleware);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NextUIProvider className="size-full" navigate={navigate}>
|
<NextUIProvider className="size-full" navigate={navigate}>
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import {Middleware, MiddlewareContext, MiddlewareNext} from '@hilla/frontend';
|
||||||
|
import {toast} from "sonner";
|
||||||
|
import {getReasonPhrase} from "http-status-codes";
|
||||||
|
|
||||||
|
export const ErrorHandlingMiddleware: Middleware = async function(
|
||||||
|
context: MiddlewareContext,
|
||||||
|
next: MiddlewareNext
|
||||||
|
) {
|
||||||
|
const {endpoint, method} = context;
|
||||||
|
|
||||||
|
let response: Response = await next(context);
|
||||||
|
if(!response.ok) {
|
||||||
|
//Ignore calls to UserEndpoint.getUserInfo since they are managed by Hilla and called on initial load
|
||||||
|
if(endpoint == "UserEndpoint" && method == "getUserInfo") return response;
|
||||||
|
|
||||||
|
toast.error(`${getReasonPhrase(response.status)}`, {description: `${endpoint}.${method}`})
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
@@ -100,7 +100,7 @@ function SetupView() {
|
|||||||
password: values.password,
|
password: values.password,
|
||||||
email: values.email
|
email: values.email
|
||||||
});
|
});
|
||||||
toast("Setup finished", {description: "Have fun with Gameyfin!"});
|
toast.success("Setup finished", {description: "Have fun with Gameyfin!"});
|
||||||
navigate('/login');
|
navigate('/login');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert("An error occurred while completing the setup. Please try again.")
|
alert("An error occurred while completing the setup. Please try again.")
|
||||||
|
|||||||
@@ -1,20 +1,40 @@
|
|||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button} from "@nextui-org/react";
|
import {Button} from "@nextui-org/react";
|
||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
|
import {SystemEndpoint} from "Frontend/generated/endpoints";
|
||||||
|
|
||||||
export default function TestView() {
|
export default function TestView() {
|
||||||
return (
|
return (
|
||||||
<div className="flex grow justify-center mt-12">
|
<div className="grow justify-center mt-12">
|
||||||
<div className="flex flex-col items-center gap-6">
|
<div className="flex flex-col items-center gap-6">
|
||||||
<Link to="/setup">Setup</Link>
|
<Link to="/setup">Setup</Link>
|
||||||
<Button onPress={
|
<div className="flex flex-row gap-4">
|
||||||
() => toast("Setup finished", {
|
<Button onPress={
|
||||||
description: "Have fun with Gameyfin!",
|
() => toast("Normal", {
|
||||||
action: {
|
description: "Description",
|
||||||
label: "OK",
|
action: {
|
||||||
onClick: () => console.log("Ok"),
|
label: "OK",
|
||||||
}
|
onClick: () => {},
|
||||||
})}>Toast</Button>
|
}
|
||||||
|
})}>Toast (Normal)</Button>
|
||||||
|
<Button onPress={
|
||||||
|
() => toast.success("Success", {
|
||||||
|
description: "Description",
|
||||||
|
action: {
|
||||||
|
label: "OK",
|
||||||
|
onClick: () => {},
|
||||||
|
}
|
||||||
|
})}>Toast (Success)</Button>
|
||||||
|
<Button onPress={
|
||||||
|
() => toast.error("Error", {
|
||||||
|
description: "Description",
|
||||||
|
action: {
|
||||||
|
label: "OK",
|
||||||
|
onClick: () => {},
|
||||||
|
}
|
||||||
|
})}>Toast (Error)</Button>
|
||||||
|
</div>
|
||||||
|
<Button onPress={() => SystemEndpoint.restart()}>Restart</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Generated
+7
@@ -36,6 +36,7 @@
|
|||||||
"construct-style-sheets-polyfill": "3.1.0",
|
"construct-style-sheets-polyfill": "3.1.0",
|
||||||
"formik": "^2.4.5",
|
"formik": "^2.4.5",
|
||||||
"framer-motion": "^11.1.7",
|
"framer-motion": "^11.1.7",
|
||||||
|
"http-status-codes": "^2.3.0",
|
||||||
"lit": "3.1.0",
|
"lit": "3.1.0",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@@ -9493,6 +9494,12 @@
|
|||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/http-status-codes": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/idb": {
|
"node_modules/idb": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
|
||||||
|
|||||||
+4
-2
@@ -31,6 +31,7 @@
|
|||||||
"construct-style-sheets-polyfill": "3.1.0",
|
"construct-style-sheets-polyfill": "3.1.0",
|
||||||
"formik": "^2.4.5",
|
"formik": "^2.4.5",
|
||||||
"framer-motion": "^11.1.7",
|
"framer-motion": "^11.1.7",
|
||||||
|
"http-status-codes": "^2.3.0",
|
||||||
"lit": "3.1.0",
|
"lit": "3.1.0",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@@ -102,7 +103,8 @@
|
|||||||
"@nextui-org/react": "$@nextui-org/react",
|
"@nextui-org/react": "$@nextui-org/react",
|
||||||
"framer-motion": "$framer-motion",
|
"framer-motion": "$framer-motion",
|
||||||
"@material-tailwind/react": "$@material-tailwind/react",
|
"@material-tailwind/react": "$@material-tailwind/react",
|
||||||
"sonner": "$sonner"
|
"sonner": "$sonner",
|
||||||
|
"http-status-codes": "$http-status-codes"
|
||||||
},
|
},
|
||||||
"vaadin": {
|
"vaadin": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -146,6 +148,6 @@
|
|||||||
"workbox-core": "7.0.0",
|
"workbox-core": "7.0.0",
|
||||||
"workbox-precaching": "7.0.0"
|
"workbox-precaching": "7.0.0"
|
||||||
},
|
},
|
||||||
"hash": "3305a1ae01d771a26115b08f2597b5fbb020e6535692fd453407cba700f727ea"
|
"hash": "e078b3ecf381b7be4b804c8e2cd928faa9accb3412cfb55cfb649f9633cd1d41"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
package de.grimsi.gameyfin.config
|
package de.grimsi.gameyfin.config
|
||||||
|
|
||||||
enum class Roles(val roleName: String) {
|
enum class Roles(val roleName: String) {
|
||||||
SUPERADMIN("ROLE_SUPERADMIN"),
|
SUPERADMIN(Names.SUPERADMIN),
|
||||||
ADMIN("ROLE_ADMIN"),
|
ADMIN(Names.ADMIN),
|
||||||
USER("ROLE_USER")
|
USER(Names.USER);
|
||||||
|
|
||||||
|
// necessary for the ability to use the Roles class in the @RolesAllowed annotation
|
||||||
|
class Names {
|
||||||
|
companion object {
|
||||||
|
const val SUPERADMIN = "ROLE_SUPERADMIN"
|
||||||
|
const val ADMIN = "ROLE_ADMIN"
|
||||||
|
const val USER = "ROLE_USER"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -29,13 +29,16 @@ class SetupDataLoader(
|
|||||||
log.info { "We will now set up some data..." }
|
log.info { "We will now set up some data..." }
|
||||||
|
|
||||||
setupRoles()
|
setupRoles()
|
||||||
//setupUsers()
|
setupUsers()
|
||||||
|
|
||||||
log.info { "Setup completed..." }
|
log.info { "Setup completed..." }
|
||||||
log.info { "Visit http://${InetAddress.getLocalHost().hostName}:${env.getProperty("server.port")}/setup to complete the setup" }
|
log.info { "Visit http://${InetAddress.getLocalHost().hostName}:${env.getProperty("server.port")}/setup to complete the setup" }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setupUsers() {
|
fun setupUsers() {
|
||||||
|
|
||||||
|
log.info { "Setting up users..." }
|
||||||
|
|
||||||
val superadmin = User(
|
val superadmin = User(
|
||||||
username = "admin",
|
username = "admin",
|
||||||
password = "admin"
|
password = "admin"
|
||||||
@@ -49,6 +52,8 @@ class SetupDataLoader(
|
|||||||
)
|
)
|
||||||
|
|
||||||
userService.registerUser(user, Roles.USER)
|
userService.registerUser(user, Roles.USER)
|
||||||
|
|
||||||
|
log.info { "User setup completed." }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setupRoles() {
|
fun setupRoles() {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package de.grimsi.gameyfin.system
|
||||||
|
|
||||||
|
import de.grimsi.gameyfin.config.Roles
|
||||||
|
import dev.hilla.Endpoint
|
||||||
|
import jakarta.annotation.security.RolesAllowed
|
||||||
|
|
||||||
|
@Endpoint
|
||||||
|
class SystemEndpoint(
|
||||||
|
private val systemService: SystemService
|
||||||
|
) {
|
||||||
|
|
||||||
|
@RolesAllowed(Roles.Names.SUPERADMIN, Roles.Names.ADMIN)
|
||||||
|
fun restart() {
|
||||||
|
systemService.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package de.grimsi.gameyfin.system
|
||||||
|
|
||||||
|
import org.springframework.cloud.context.restart.RestartEndpoint
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class SystemService(
|
||||||
|
private val restartEndpoint: RestartEndpoint,
|
||||||
|
) {
|
||||||
|
fun restart() {
|
||||||
|
restartEndpoint.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,8 +12,7 @@ import org.springframework.security.core.context.SecurityContextHolder
|
|||||||
|
|
||||||
@Endpoint
|
@Endpoint
|
||||||
class UserEndpoint(
|
class UserEndpoint(
|
||||||
private val userService: UserService,
|
private val userService: UserService
|
||||||
private val roleService: RoleService,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@PermitAll
|
@PermitAll
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import jakarta.validation.constraints.NotNull
|
|||||||
@Entity
|
@Entity
|
||||||
class Role(
|
class Role(
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@Column(unique = true)
|
||||||
var rolename: String,
|
var rolename: String,
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
|
|||||||
@@ -8,23 +8,27 @@ import jakarta.validation.constraints.NotNull
|
|||||||
@Entity
|
@Entity
|
||||||
@Table(name = "users")
|
@Table(name = "users")
|
||||||
class User(
|
class User(
|
||||||
@field:NotNull
|
@NotNull
|
||||||
|
@Column(unique = true)
|
||||||
var username: String,
|
var username: String,
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
var id: Long? = null,
|
var id: Long? = null,
|
||||||
|
|
||||||
@field:NotNull
|
@NotNull
|
||||||
var password: String,
|
var password: String,
|
||||||
|
|
||||||
@field:Nullable
|
@Nullable
|
||||||
|
@Column(unique = true)
|
||||||
var email: String? = null,
|
var email: String? = null,
|
||||||
|
|
||||||
|
var email_confirmed: Boolean = false,
|
||||||
|
|
||||||
var enabled: Boolean = true,
|
var enabled: Boolean = true,
|
||||||
|
|
||||||
@Embedded
|
@Embedded
|
||||||
@field:Nullable
|
@Nullable
|
||||||
var avatar: Avatar? = null,
|
var avatar: Avatar? = null,
|
||||||
|
|
||||||
@ManyToMany(fetch = FetchType.EAGER)
|
@ManyToMany(fetch = FetchType.EAGER)
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ server:
|
|||||||
session:
|
session:
|
||||||
tracking-modes: cookie
|
tracking-modes: cookie
|
||||||
|
|
||||||
|
management:
|
||||||
|
endpoints.web.exposure.include: '*'
|
||||||
|
endpoint:
|
||||||
|
pause:
|
||||||
|
enabled: false
|
||||||
|
restart:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
# Workaround for https://github.com/vaadin/hilla/issues/842
|
# Workaround for https://github.com/vaadin/hilla/issues/842
|
||||||
devtools.restart.additional-exclude: dev/hilla/openapi.json
|
devtools.restart.additional-exclude: dev/hilla/openapi.json
|
||||||
|
|||||||
Reference in New Issue
Block a user