From f5962e3cfd9eb232cb2b6a4922cafad0ea032aeb Mon Sep 17 00:00:00 2001 From: grimsi <9295182+grimsi@users.noreply.github.com> Date: Wed, 18 Sep 2024 23:35:19 +0200 Subject: [PATCH] WIP: Notifications (via email, more to come?) --- .run/UI debug.run.xml | 2 +- build.gradle.kts | 3 + .../administration/ConfigFormField.tsx | 3 +- .../administration/NotificationManagement.tsx | 75 +++++++++++++++++++ .../administration/SsoManagement.tsx | 3 +- .../frontend/components/general/Input.tsx | 4 +- src/main/frontend/routes.tsx | 2 + .../gameyfin/config/ConfigProperties.kt | 36 +++++++-- .../grimsi/gameyfin/config/ConfigService.kt | 2 +- .../notifications/NotificationEndpoint.kt | 33 ++++++++ .../notifications/dto/EmailCredentialsDto.kt | 8 ++ 11 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 src/main/frontend/components/administration/NotificationManagement.tsx create mode 100644 src/main/kotlin/de/grimsi/gameyfin/notifications/NotificationEndpoint.kt create mode 100644 src/main/kotlin/de/grimsi/gameyfin/notifications/dto/EmailCredentialsDto.kt diff --git a/.run/UI debug.run.xml b/.run/UI debug.run.xml index 1a2afd9..ccfbe55 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/build.gradle.kts b/build.gradle.kts index e804475..63857ed 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -60,6 +60,9 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server") implementation("org.springframework.security:spring-security-oauth2-jose") + // Notifications + implementation("org.springframework.boot:spring-boot-starter-mail") + // Development developmentOnly("org.springframework.boot:spring-boot-devtools") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") diff --git a/src/main/frontend/components/administration/ConfigFormField.tsx b/src/main/frontend/components/administration/ConfigFormField.tsx index 7cce99d..8a66b3f 100644 --- a/src/main/frontend/components/administration/ConfigFormField.tsx +++ b/src/main/frontend/components/administration/ConfigFormField.tsx @@ -21,7 +21,8 @@ export default function ConfigFormField({configElement, ...props}: any) { ); case "String": return ( - + ); case "Float": return ( diff --git a/src/main/frontend/components/administration/NotificationManagement.tsx b/src/main/frontend/components/administration/NotificationManagement.tsx new file mode 100644 index 0000000..76e8487 --- /dev/null +++ b/src/main/frontend/components/administration/NotificationManagement.tsx @@ -0,0 +1,75 @@ +import React from "react"; +import withConfigPage from "Frontend/components/administration/withConfigPage"; +import * as Yup from 'yup'; +import ConfigFormField from "Frontend/components/administration/ConfigFormField"; +import Section from "Frontend/components/general/Section"; +import {Button, Input, Select, SelectItem} from "@nextui-org/react"; +import {NotificationEndpoint} from "Frontend/generated/endpoints"; +import EmailCredentialsDto from "Frontend/generated/de/grimsi/gameyfin/notifications/dto/EmailCredentialsDto"; +import {toast} from "sonner"; + +function NotificationManagementLayout({getConfig, formik}: any) { + + async function testMailSettings() { + const credentials: EmailCredentialsDto = { + host: formik.values.notifications.email.host, + port: formik.values.notifications.email.port, + username: formik.values.notifications.email.username, + password: formik.values.notifications.email.password + } + + const areCredentialsValid = await NotificationEndpoint.verifyEmailCredentials(credentials); + + if (areCredentialsValid) { + toast.success("Credentials are valid") + } else { + toast.error("Credentials are invalid") + } + } + + return ( +
+
+
+ + +
+
+
+ + + + + +
+
+
+ {/* TODO: Evaluate need and options if need is given */} + + + +
+
+
+
+
+ ); +} + +const validationSchema = Yup.object({}); + +export const NotificationManagement = withConfigPage(NotificationManagementLayout, "Notifications", "notifications", validationSchema); \ No newline at end of file diff --git a/src/main/frontend/components/administration/SsoManagement.tsx b/src/main/frontend/components/administration/SsoManagement.tsx index a343e74..4df5384 100644 --- a/src/main/frontend/components/administration/SsoManagement.tsx +++ b/src/main/frontend/components/administration/SsoManagement.tsx @@ -58,6 +58,7 @@ function SsoManagementLayout({getConfig, formik, setSaveMessage}: any) {
Auto-populate + className="h-14 mt-2"> Auto-populate
diff --git a/src/main/frontend/components/general/Input.tsx b/src/main/frontend/components/general/Input.tsx index df5a6bd..b2b8b96 100644 --- a/src/main/frontend/components/general/Input.tsx +++ b/src/main/frontend/components/general/Input.tsx @@ -9,7 +9,7 @@ const Input = ({label, ...props}) => { const [field, meta] = useField(props); return ( -
+
{ label={label} isInvalid={meta.touched && !!meta.error} /> -
+
{meta.touched && meta.error && ( )} diff --git a/src/main/frontend/routes.tsx b/src/main/frontend/routes.tsx index d241efd..95c53b9 100644 --- a/src/main/frontend/routes.tsx +++ b/src/main/frontend/routes.tsx @@ -12,6 +12,7 @@ import ProfileManagement from "Frontend/components/administration/ProfileManagem import {SsoManagement} from "Frontend/components/administration/SsoManagement"; import {AdministrationView} from "Frontend/views/AdministrationView"; import {ProfileView} from "Frontend/views/ProfileView"; +import {NotificationManagement} from "Frontend/components/administration/NotificationManagement"; export const routes = protectRoutes([ { @@ -40,6 +41,7 @@ export const routes = protectRoutes([ {path: 'libraries', element: }, {path: 'users', element: }, {path: 'sso', element: }, + {path: 'notifications', element: } ] } ] diff --git a/src/main/kotlin/de/grimsi/gameyfin/config/ConfigProperties.kt b/src/main/kotlin/de/grimsi/gameyfin/config/ConfigProperties.kt index d3932b9..2e4866a 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/config/ConfigProperties.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/config/ConfigProperties.kt @@ -127,17 +127,37 @@ sealed class ConfigProperties( ) /** Notifications */ - data object NotificationsEmailHost : - ConfigProperties(String::class, "notifications.email.host", "URL of the email server") + data object NotificationsEnabled : ConfigProperties( + Boolean::class, + "notifications.enabled", + "Enable notifications", + false + ) - data object NotificationsEmailPort : - ConfigProperties(String::class, "notifications.email.port", "Port of the email server") + data object NotificationsEmailHost : ConfigProperties( + String::class, + "notifications.email.host", + "URL of the email server" + ) - data object NotificationsEmailUsername : - ConfigProperties(String::class, "notifications.email.username", "Username for the email account") + data object NotificationsEmailPort : ConfigProperties( + Int::class, + "notifications.email.port", + "Port of the email server", + 587 + ) - data object NotificationsEmailPassword : - ConfigProperties(String::class, "notifications.email.password", "Password for the email account") + data object NotificationsEmailUsername : ConfigProperties( + String::class, + "notifications.email.username", + "Username for the email account" + ) + + data object NotificationsEmailPassword : ConfigProperties( + String::class, + "notifications.email.password", + "Password for the email account" + ) } enum class MatchUsersBy { diff --git a/src/main/kotlin/de/grimsi/gameyfin/config/ConfigService.kt b/src/main/kotlin/de/grimsi/gameyfin/config/ConfigService.kt index 28eb8d3..0cc3346 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/config/ConfigService.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/config/ConfigService.kt @@ -119,7 +119,7 @@ class ConfigService( * @throws IllegalArgumentException if the value can't be cast to the type defined for the config property */ fun set(key: String, value: T) { - log.info { "Set config value '$key' to '$value'" } + log.info { "Set config value '$key'" } val configKey = findConfigProperty(key) diff --git a/src/main/kotlin/de/grimsi/gameyfin/notifications/NotificationEndpoint.kt b/src/main/kotlin/de/grimsi/gameyfin/notifications/NotificationEndpoint.kt new file mode 100644 index 0000000..83c71ce --- /dev/null +++ b/src/main/kotlin/de/grimsi/gameyfin/notifications/NotificationEndpoint.kt @@ -0,0 +1,33 @@ +package de.grimsi.gameyfin.notifications + +import com.vaadin.hilla.Endpoint +import de.grimsi.gameyfin.meta.Roles +import de.grimsi.gameyfin.notifications.dto.EmailCredentialsDto +import jakarta.annotation.security.RolesAllowed +import jakarta.mail.MessagingException +import jakarta.mail.Session +import java.util.* + +@Endpoint +@RolesAllowed(Roles.Names.SUPERADMIN, Roles.Names.ADMIN) +class NotificationEndpoint { + + fun verifyEmailCredentials(credentials: EmailCredentialsDto): Boolean { + val properties = Properties() + properties["mail.smtp.auth"] = "true" + properties["mail.smtp.starttls.enable"] = "true" + properties["mail.smtp.host"] = credentials.host + properties["mail.smtp.port"] = credentials.port + + val session = Session.getInstance(properties, null) + + try { + val transport = session.getTransport("smtp") + transport.connect(credentials.host, credentials.port, credentials.username, credentials.password) + transport.close() + return true + } catch (ex: MessagingException) { + return false + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/de/grimsi/gameyfin/notifications/dto/EmailCredentialsDto.kt b/src/main/kotlin/de/grimsi/gameyfin/notifications/dto/EmailCredentialsDto.kt new file mode 100644 index 0000000..cf177d0 --- /dev/null +++ b/src/main/kotlin/de/grimsi/gameyfin/notifications/dto/EmailCredentialsDto.kt @@ -0,0 +1,8 @@ +package de.grimsi.gameyfin.notifications.dto + +data class EmailCredentialsDto( + val host: String, + val port: Int, + val username: String, + val password: String? +) \ No newline at end of file