mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 00:30:02 +00:00
Implement notification templates
Refactor notification providers to be more expandable in the future Minor layout changes
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Production build" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<option name="executionName" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="externalSystemIdString" value="GRADLE" />
|
||||||
|
<option name="scriptParameters" value="-P hilla.productionMode=true" />
|
||||||
|
<option name="taskDescriptions">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="taskNames">
|
||||||
|
<list>
|
||||||
|
<option value="build" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<RunAsTest>false</RunAsTest>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -1,24 +1,38 @@
|
|||||||
import React from "react";
|
import React, {useState} from "react";
|
||||||
import withConfigPage from "Frontend/components/administration/withConfigPage";
|
import withConfigPage from "Frontend/components/administration/withConfigPage";
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import ConfigFormField from "Frontend/components/administration/ConfigFormField";
|
import ConfigFormField from "Frontend/components/administration/ConfigFormField";
|
||||||
import Section from "Frontend/components/general/Section";
|
import Section from "Frontend/components/general/Section";
|
||||||
import {Button, Input, Select, SelectItem} from "@nextui-org/react";
|
import {
|
||||||
import {NotificationEndpoint} from "Frontend/generated/endpoints";
|
Button,
|
||||||
import EmailCredentialsDto from "Frontend/generated/de/grimsi/gameyfin/notifications/dto/EmailCredentialsDto";
|
Card,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
Textarea,
|
||||||
|
useDisclosure
|
||||||
|
} from "@nextui-org/react";
|
||||||
|
import {ConfigEndpoint, NotificationEndpoint} from "Frontend/generated/endpoints";
|
||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
|
import ConfigEntryDto from "Frontend/generated/de/grimsi/gameyfin/config/dto/ConfigEntryDto";
|
||||||
|
import {Pencil} from "@phosphor-icons/react";
|
||||||
|
|
||||||
function NotificationManagementLayout({getConfig, formik}: any) {
|
function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
|
||||||
|
|
||||||
async function testMailSettings() {
|
const {isOpen, onOpen, onOpenChange} = useDisclosure();
|
||||||
const credentials: EmailCredentialsDto = {
|
const [selectedTemplate, setSelectedTemplate] = useState<ConfigEntryDto | null>(null);
|
||||||
host: formik.values.notifications.email.host,
|
|
||||||
port: formik.values.notifications.email.port,
|
async function verifyCredentials(provider: string) {
|
||||||
username: formik.values.notifications.email.username,
|
const credentials: Record<string, any> = {
|
||||||
password: formik.values.notifications.email.password
|
host: formik.values.notifications.providers.email.host,
|
||||||
|
port: formik.values.notifications.providers.email.port,
|
||||||
|
username: formik.values.notifications.providers.email.username,
|
||||||
|
password: formik.values.notifications.providers.email.password
|
||||||
}
|
}
|
||||||
|
|
||||||
const areCredentialsValid = await NotificationEndpoint.verifyEmailCredentials(credentials);
|
const areCredentialsValid = await NotificationEndpoint.verifyCredentials(provider, credentials);
|
||||||
|
|
||||||
if (areCredentialsValid) {
|
if (areCredentialsValid) {
|
||||||
toast.success("Credentials are valid")
|
toast.success("Credentials are valid")
|
||||||
@@ -27,6 +41,19 @@ function NotificationManagementLayout({getConfig, formik}: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openModal(template: ConfigEntryDto) {
|
||||||
|
let templateContent = await ConfigEndpoint.get(template.key);
|
||||||
|
setSelectedTemplate({
|
||||||
|
...template,
|
||||||
|
value: templateContent
|
||||||
|
});
|
||||||
|
onOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveTemplate(template: ConfigEntryDto) {
|
||||||
|
await ConfigEndpoint.set(template.key, template.value);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
@@ -36,36 +63,83 @@ function NotificationManagementLayout({getConfig, formik}: any) {
|
|||||||
<div className="flex flex-row gap-8">
|
<div className="flex flex-row gap-8">
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1">
|
||||||
<Section title="E-Mail"/>
|
<Section title="E-Mail"/>
|
||||||
<ConfigFormField configElement={getConfig("notifications.email.host")}
|
<ConfigFormField configElement={getConfig("notifications.providers.email.host")}
|
||||||
isDisabled={!formik.values.notifications.enabled}/>
|
isDisabled={!formik.values.notifications.enabled}/>
|
||||||
<ConfigFormField configElement={getConfig("notifications.email.port")}
|
<ConfigFormField configElement={getConfig("notifications.providers.email.port")}
|
||||||
isDisabled={!formik.values.notifications.enabled}/>
|
isDisabled={!formik.values.notifications.enabled}/>
|
||||||
<ConfigFormField configElement={getConfig("notifications.email.username")}
|
<ConfigFormField configElement={getConfig("notifications.providers.email.username")}
|
||||||
isDisabled={!formik.values.notifications.enabled}/>
|
isDisabled={!formik.values.notifications.enabled}/>
|
||||||
<ConfigFormField configElement={getConfig("notifications.email.password")}
|
<ConfigFormField configElement={getConfig("notifications.providers.email.password")}
|
||||||
type="password"
|
type="password"
|
||||||
isDisabled={!formik.values.notifications.enabled}/>
|
isDisabled={!formik.values.notifications.enabled}/>
|
||||||
<Button onPress={testMailSettings}
|
<Button onPress={() => verifyCredentials("email")}
|
||||||
isDisabled={!(
|
isDisabled={!(
|
||||||
formik.values.notifications.enabled &&
|
formik.values.notifications.enabled &&
|
||||||
formik.values.notifications.email.host &&
|
formik.values.notifications.providers.email.host &&
|
||||||
formik.values.notifications.email.port &&
|
formik.values.notifications.providers.email.port &&
|
||||||
formik.values.notifications.email.username)}>Test</Button>
|
formik.values.notifications.providers.email.username)}>Test</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1">
|
||||||
<Section title="Push"/>
|
<Section title="Message Templates"/>
|
||||||
{/* TODO: Evaluate need and options if need is given */}
|
<div className="flex flex-col gap-4">
|
||||||
<Select className="mt-2 mb-10"
|
{getConfigs("notifications.templates").map((template: ConfigEntryDto) =>
|
||||||
label="Push notification provider" defaultSelectedKeys={["pushbullet"]}
|
<Card className="flex flex-row items-center gap-2 p-4">
|
||||||
isDisabled>
|
<Button isIconOnly
|
||||||
<SelectItem key="pushbullet">Pushbullet</SelectItem>
|
size="sm"
|
||||||
</Select>
|
onPress={() => openModal(template)}
|
||||||
<Input className="mt-2 mb-10" label="Access Token" type="password" isDisabled/>
|
>
|
||||||
<Input className="mt-2 mb-10" label="Channel tag" type="text" isDisabled/>
|
<Pencil/>
|
||||||
|
</Button>
|
||||||
|
<p className="text-lg">{template.description}</p>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange} size="5xl">
|
||||||
|
<ModalContent>
|
||||||
|
{(onClose) => (
|
||||||
|
<>
|
||||||
|
<ModalHeader
|
||||||
|
className="flex flex-col gap-1">Edit {selectedTemplate?.description.toLowerCase()}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Textarea
|
||||||
|
size="lg"
|
||||||
|
autoFocus
|
||||||
|
disableAutosize
|
||||||
|
value={selectedTemplate?.value}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (selectedTemplate?.key) setSelectedTemplate({
|
||||||
|
...selectedTemplate,
|
||||||
|
value: e.target.value
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
classNames={{
|
||||||
|
input: "resize-y min-h-[500px]"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="danger" variant="light" onPress={onClose}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button color="primary" onPress={async () => {
|
||||||
|
if (selectedTemplate) {
|
||||||
|
await saveTemplate(selectedTemplate);
|
||||||
|
toast.success("Template saved")
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ function SsoManagementLayout({getConfig, formik, setSaveMessage}: any) {
|
|||||||
<Button
|
<Button
|
||||||
isDisabled={isAutoPopulateDisabled()}
|
isDisabled={isAutoPopulateDisabled()}
|
||||||
onPress={autoPopulate}
|
onPress={autoPopulate}
|
||||||
className="h-14 mt-2"><MagicWand className="min-w-5"/> Auto-populate</Button>
|
className="h-14"><MagicWand className="min-w-5"/> Auto-populate</Button>
|
||||||
</div>
|
</div>
|
||||||
<ConfigFormField configElement={getConfig("sso.oidc.authorize-url")}
|
<ConfigFormField configElement={getConfig("sso.oidc.authorize-url")}
|
||||||
isDisabled={!formik.values.sso.oidc.enabled}/>
|
isDisabled={!formik.values.sso.oidc.enabled}/>
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ export default function withConfigPage(WrappedComponent: React.ComponentType<any
|
|||||||
return configDtos.find((configDto: ConfigEntryDto) => configDto.key === key);
|
return configDtos.find((configDto: ConfigEntryDto) => configDto.key === key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getConfigs(prefix: string) {
|
||||||
|
return configDtos.filter((configDto: ConfigEntryDto) => configDto.key?.startsWith(prefix));
|
||||||
|
}
|
||||||
|
|
||||||
function toNestedConfig(configArray: ConfigEntryDto[]): NestedConfig {
|
function toNestedConfig(configArray: ConfigEntryDto[]): NestedConfig {
|
||||||
const nestedConfig: NestedConfig = {};
|
const nestedConfig: NestedConfig = {};
|
||||||
|
|
||||||
@@ -143,7 +147,10 @@ export default function withConfigPage(WrappedComponent: React.ComponentType<any
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<WrappedComponent {...props} getConfig={getConfig} formik={formik}
|
<WrappedComponent {...props}
|
||||||
|
getConfig={getConfig}
|
||||||
|
getConfigs={getConfigs}
|
||||||
|
formik={formik}
|
||||||
setSaveMessage={setSaveMessage}/>
|
setSaveMessage={setSaveMessage}/>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const CheckboxInput = ({label, ...props}) => {
|
|||||||
const [field] = useField(props);
|
const [field] = useField(props);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row flex-1 items-center gap-2 my-2">
|
<div className="flex flex-row flex-1 items-center gap-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
{...field}
|
{...field}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const Input = ({label, ...props}) => {
|
|||||||
const [field, meta] = useField(props);
|
const [field, meta] = useField(props);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-1 items-start gap-2 my-2">
|
<div className="flex flex-col flex-1 items-start gap-2">
|
||||||
<NextUiInput
|
<NextUiInput
|
||||||
{...props}
|
{...props}
|
||||||
{...field}
|
{...field}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const SelectInput = ({label, values, ...props}) => {
|
|||||||
const [field] = useField(props);
|
const [field] = useField(props);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row flex-1 justify-center gap-2 my-2">
|
<div className="flex flex-row flex-1 justify-center gap-2">
|
||||||
<Select
|
<Select
|
||||||
{...field}
|
{...field}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -137,29 +137,59 @@ sealed class ConfigProperties<T : Serializable>(
|
|||||||
|
|
||||||
data object NotificationsEmailHost : ConfigProperties<String>(
|
data object NotificationsEmailHost : ConfigProperties<String>(
|
||||||
String::class,
|
String::class,
|
||||||
"notifications.email.host",
|
"notifications.providers.email.host",
|
||||||
"URL of the email server"
|
"URL of the email server"
|
||||||
)
|
)
|
||||||
|
|
||||||
data object NotificationsEmailPort : ConfigProperties<Int>(
|
data object NotificationsEmailPort : ConfigProperties<Int>(
|
||||||
Int::class,
|
Int::class,
|
||||||
"notifications.email.port",
|
"notifications.providers.email.port",
|
||||||
"Port of the email server",
|
"Port of the email server",
|
||||||
587
|
587
|
||||||
)
|
)
|
||||||
|
|
||||||
data object NotificationsEmailUsername : ConfigProperties<String>(
|
data object NotificationsEmailUsername : ConfigProperties<String>(
|
||||||
String::class,
|
String::class,
|
||||||
"notifications.email.username",
|
"notifications.providers.email.username",
|
||||||
"Username for the email account"
|
"Username for the email account"
|
||||||
)
|
)
|
||||||
|
|
||||||
data object NotificationsEmailPassword : ConfigProperties<String>(
|
data object NotificationsEmailPassword : ConfigProperties<String>(
|
||||||
String::class,
|
String::class,
|
||||||
"notifications.email.password",
|
"notifications.providers.email.password",
|
||||||
"Password for the email account"
|
"Password for the email account"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data object NotificationsTemplateNewUser : ConfigProperties<String>(
|
||||||
|
String::class,
|
||||||
|
"notifications.templates.new-user",
|
||||||
|
"Template for new user notifications"
|
||||||
|
)
|
||||||
|
|
||||||
|
data object NotificationsTemplateNewInvite : ConfigProperties<String>(
|
||||||
|
String::class,
|
||||||
|
"notifications.templates.new-invite",
|
||||||
|
"Template for new user notifications"
|
||||||
|
)
|
||||||
|
|
||||||
|
data object NotificationsTemplateNewPasswordRequest : ConfigProperties<String>(
|
||||||
|
String::class,
|
||||||
|
"notifications.templates.new-password-request",
|
||||||
|
"Template for new password request notifications"
|
||||||
|
)
|
||||||
|
|
||||||
|
data object NotificationsTemplateNewGame : ConfigProperties<String>(
|
||||||
|
String::class,
|
||||||
|
"notifications.templates.new-game",
|
||||||
|
"Template for new game notifications"
|
||||||
|
)
|
||||||
|
|
||||||
|
data object NotificationsTemplateNewGameRequest : ConfigProperties<String>(
|
||||||
|
String::class,
|
||||||
|
"notifications.templates.new-game-request",
|
||||||
|
"Template for new game request notifications"
|
||||||
|
)
|
||||||
|
|
||||||
/** Logs */
|
/** Logs */
|
||||||
data object LogsFolder : ConfigProperties<String>(
|
data object LogsFolder : ConfigProperties<String>(
|
||||||
String::class,
|
String::class,
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package de.grimsi.gameyfin.config.entities
|
package de.grimsi.gameyfin.config.entities
|
||||||
|
|
||||||
import jakarta.persistence.Column
|
import jakarta.persistence.*
|
||||||
import jakarta.persistence.Entity
|
|
||||||
import jakarta.persistence.Id
|
|
||||||
import jakarta.persistence.Table
|
|
||||||
import jakarta.validation.constraints.NotNull
|
import jakarta.validation.constraints.NotNull
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@@ -15,6 +12,7 @@ class ConfigEntry(
|
|||||||
val key: String,
|
val key: String,
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@Lob
|
||||||
@Column(name = "`value`")
|
@Column(name = "`value`")
|
||||||
var value: String
|
var value: String
|
||||||
)
|
)
|
||||||
@@ -2,32 +2,15 @@ package de.grimsi.gameyfin.notifications
|
|||||||
|
|
||||||
import com.vaadin.hilla.Endpoint
|
import com.vaadin.hilla.Endpoint
|
||||||
import de.grimsi.gameyfin.core.Roles
|
import de.grimsi.gameyfin.core.Roles
|
||||||
import de.grimsi.gameyfin.notifications.dto.EmailCredentialsDto
|
|
||||||
import jakarta.annotation.security.RolesAllowed
|
import jakarta.annotation.security.RolesAllowed
|
||||||
import jakarta.mail.MessagingException
|
|
||||||
import jakarta.mail.Session
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@Endpoint
|
@Endpoint
|
||||||
@RolesAllowed(Roles.Names.SUPERADMIN, Roles.Names.ADMIN)
|
@RolesAllowed(Roles.Names.SUPERADMIN, Roles.Names.ADMIN)
|
||||||
class NotificationEndpoint {
|
class NotificationEndpoint(
|
||||||
|
private val notificationService: NotificationService
|
||||||
|
) {
|
||||||
|
|
||||||
fun verifyEmailCredentials(credentials: EmailCredentialsDto): Boolean {
|
fun verifyCredentials(provider: String, credentials: Map<String, Any>): Boolean {
|
||||||
val properties = Properties()
|
return notificationService.testCredentials(provider, credentials)
|
||||||
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)
|
|
||||||
|
|
||||||
try {
|
|
||||||
val transport = session.getTransport("smtp")
|
|
||||||
transport.connect(credentials.host, credentials.port, credentials.username, credentials.password)
|
|
||||||
transport.close()
|
|
||||||
return true
|
|
||||||
} catch (_: MessagingException) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package de.grimsi.gameyfin.notifications
|
||||||
|
|
||||||
|
import de.grimsi.gameyfin.notifications.providers.AbstractNotificationProvider
|
||||||
|
import org.springframework.context.ApplicationContext
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class NotificationService(
|
||||||
|
private val applicationContext: ApplicationContext
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val providers: List<AbstractNotificationProvider>
|
||||||
|
get() = applicationContext.getBeansOfType(AbstractNotificationProvider::class.java).values.toList()
|
||||||
|
|
||||||
|
fun testCredentials(provider: String, credentials: Map<String, Any>): Boolean {
|
||||||
|
val notificationProvider = providers.find { it.providerKey == provider }
|
||||||
|
val credentialsProperties = Properties().apply { putAll(credentials) }
|
||||||
|
return notificationProvider?.testCredentials(credentialsProperties)
|
||||||
|
?: throw IllegalArgumentException("Provider $provider not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package de.grimsi.gameyfin.notifications.dto
|
|
||||||
|
|
||||||
data class EmailCredentialsDto(
|
|
||||||
val host: String,
|
|
||||||
val port: Int,
|
|
||||||
val username: String,
|
|
||||||
val password: String?
|
|
||||||
)
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package de.grimsi.gameyfin.notifications.events
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationEvent
|
||||||
|
|
||||||
|
class UserInvitationEvent(source: Any) : ApplicationEvent(source)
|
||||||
|
|
||||||
|
class UserRegistrationEvent(source: Any) : ApplicationEvent(source)
|
||||||
|
|
||||||
|
class PasswordResetRequestEvent(source: Any) : ApplicationEvent(source)
|
||||||
|
|
||||||
|
class GameRequestEvent(source: Any) : ApplicationEvent(source)
|
||||||
|
|
||||||
|
class GameRequestApprovalEvent(source: Any) : ApplicationEvent(source)
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package de.grimsi.gameyfin.notifications.providers
|
||||||
|
|
||||||
|
import de.grimsi.gameyfin.config.ConfigService
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
abstract class AbstractNotificationProvider(
|
||||||
|
val providerKey: String,
|
||||||
|
private val config: ConfigService
|
||||||
|
) {
|
||||||
|
private val configKey = String.format("notifications.providers.%s.enabled", providerKey)
|
||||||
|
|
||||||
|
fun isEnabled(): Boolean {
|
||||||
|
return config.get(configKey).toBoolean()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun testCredentials(credentials: Properties): Boolean
|
||||||
|
|
||||||
|
abstract fun sendNotification(recipient: String, title: String, message: String)
|
||||||
|
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package de.grimsi.gameyfin.notifications.providers
|
||||||
|
|
||||||
|
import de.grimsi.gameyfin.config.ConfigService
|
||||||
|
import jakarta.mail.MessagingException
|
||||||
|
import jakarta.mail.Session
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class EmailNotificationProvider(
|
||||||
|
configService: ConfigService
|
||||||
|
) : AbstractNotificationProvider("email", configService) {
|
||||||
|
|
||||||
|
override fun testCredentials(credentials: Properties): Boolean {
|
||||||
|
try {
|
||||||
|
val sessionProperties = Properties()
|
||||||
|
sessionProperties["mail.smtp.auth"] = true
|
||||||
|
sessionProperties["mail.smtp.starttls.enable"] = true
|
||||||
|
sessionProperties["mail.smtp.host"] = credentials["host"] as String
|
||||||
|
sessionProperties["mail.smtp.port"] = credentials["port"] as Int
|
||||||
|
|
||||||
|
val session = Session.getInstance(sessionProperties)
|
||||||
|
|
||||||
|
val transport = session.getTransport("smtp")
|
||||||
|
transport.connect(
|
||||||
|
credentials["host"] as String,
|
||||||
|
credentials["port"] as Int,
|
||||||
|
credentials["username"] as String,
|
||||||
|
credentials["password"] as String
|
||||||
|
)
|
||||||
|
transport.close()
|
||||||
|
return true
|
||||||
|
} catch (_: MessagingException) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sendNotification(recipient: String, title: String, message: String) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user