mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 16:20:04 +00:00
Implemented test notification sending
This commit is contained in:
@@ -14,17 +14,21 @@ import {
|
|||||||
ModalFooter,
|
ModalFooter,
|
||||||
ModalHeader,
|
ModalHeader,
|
||||||
Textarea,
|
Textarea,
|
||||||
|
Tooltip,
|
||||||
useDisclosure
|
useDisclosure
|
||||||
} from "@nextui-org/react";
|
} from "@nextui-org/react";
|
||||||
import {MessageTemplateEndpoint, NotificationEndpoint} from "Frontend/generated/endpoints";
|
import {MessageTemplateEndpoint, NotificationEndpoint} from "Frontend/generated/endpoints";
|
||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
import {Pencil} from "@phosphor-icons/react";
|
import {PaperPlaneRight, Pencil} from "@phosphor-icons/react";
|
||||||
import MessageTemplateDto from "Frontend/generated/de/grimsi/gameyfin/notifications/templates/MessageTemplateDto";
|
import MessageTemplateDto from "Frontend/generated/de/grimsi/gameyfin/notifications/templates/MessageTemplateDto";
|
||||||
import TemplateType from "Frontend/generated/de/grimsi/gameyfin/notifications/templates/TemplateType";
|
import TemplateType from "Frontend/generated/de/grimsi/gameyfin/notifications/templates/TemplateType";
|
||||||
|
import {Form, Formik} from "formik";
|
||||||
|
import Input from "Frontend/components/general/Input";
|
||||||
|
|
||||||
function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
|
function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
|
||||||
|
|
||||||
const {isOpen, onOpen, onOpenChange} = useDisclosure();
|
const editorModal = useDisclosure();
|
||||||
|
const testNotificationModal = useDisclosure();
|
||||||
const [availableTemplates, setAvailableTemplates] = useState<MessageTemplateDto[]>([]);
|
const [availableTemplates, setAvailableTemplates] = useState<MessageTemplateDto[]>([]);
|
||||||
const [selectedTemplate, setSelectedTemplate] = useState<MessageTemplateDto | null>(null);
|
const [selectedTemplate, setSelectedTemplate] = useState<MessageTemplateDto | null>(null);
|
||||||
const [templateContent, setTemplateContent] = useState<string>("");
|
const [templateContent, setTemplateContent] = useState<string>("");
|
||||||
@@ -53,7 +57,7 @@ function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openModal(template: MessageTemplateDto) {
|
async function openEditor(template: MessageTemplateDto) {
|
||||||
setSelectedTemplate(template);
|
setSelectedTemplate(template);
|
||||||
|
|
||||||
let templateContent = await MessageTemplateEndpoint.read(template.key, TemplateType.MJML);
|
let templateContent = await MessageTemplateEndpoint.read(template.key, TemplateType.MJML);
|
||||||
@@ -66,13 +70,26 @@ function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTemplateContent(templateContent);
|
setTemplateContent(templateContent);
|
||||||
onOpen();
|
editorModal.onOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openTestNotification(template: MessageTemplateDto) {
|
||||||
|
setSelectedTemplate(template);
|
||||||
|
testNotificationModal.onOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveTemplate(template: MessageTemplateDto) {
|
async function saveTemplate(template: MessageTemplateDto) {
|
||||||
await MessageTemplateEndpoint.save(template.key, TemplateType.MJML, templateContent);
|
await MessageTemplateEndpoint.save(template.key, TemplateType.MJML, templateContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateValidationSchema(placeholders: string[]) {
|
||||||
|
const shape: { [key: string]: Yup.StringSchema } = {};
|
||||||
|
placeholders.forEach(placeholder => {
|
||||||
|
shape[placeholder] = Yup.string().required(`Placeholder ${placeholder} is required`);
|
||||||
|
});
|
||||||
|
return Yup.object().shape(shape);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
@@ -102,12 +119,22 @@ function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
|
|||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{availableTemplates.map((template: MessageTemplateDto) =>
|
{availableTemplates.map((template: MessageTemplateDto) =>
|
||||||
<Card className="flex flex-row items-center gap-2 p-4" key={template.key}>
|
<Card className="flex flex-row items-center gap-2 p-4" key={template.key}>
|
||||||
<Button isIconOnly
|
<Tooltip content="Edit template">
|
||||||
size="sm"
|
<Button isIconOnly
|
||||||
onPress={() => openModal(template)}
|
size="sm"
|
||||||
>
|
onPress={() => openEditor(template)}
|
||||||
<Pencil/>
|
>
|
||||||
</Button>
|
<Pencil/>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Send test notification">
|
||||||
|
<Button isIconOnly
|
||||||
|
size="sm"
|
||||||
|
onPress={() => openTestNotification(template)}
|
||||||
|
>
|
||||||
|
<PaperPlaneRight/>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
<p className="text-lg">{template.description}</p>
|
<p className="text-lg">{template.description}</p>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
@@ -117,7 +144,7 @@ function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange} size="5xl">
|
<Modal isOpen={editorModal.isOpen} onOpenChange={editorModal.onOpenChange} size="5xl">
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
{(onClose) => (
|
{(onClose) => (
|
||||||
<>
|
<>
|
||||||
@@ -190,6 +217,45 @@ function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
|
|||||||
)}
|
)}
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Modal isOpen={testNotificationModal.isOpen} onOpenChange={testNotificationModal.onOpenChange} size="3xl">
|
||||||
|
<ModalContent>
|
||||||
|
{(onClose) => (
|
||||||
|
<>
|
||||||
|
<Formik
|
||||||
|
initialValues={{}}
|
||||||
|
onSubmit={async (values) => {
|
||||||
|
await NotificationEndpoint.sendTestNotification(selectedTemplate?.key, values);
|
||||||
|
toast.success("Test notification to you has been sent");
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
validationSchema={generateValidationSchema(selectedTemplate?.availablePlaceholders as string[])}
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<ModalHeader className="flex flex-col gap-1">
|
||||||
|
Send {selectedTemplate?.name} Test Message
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<p className="text-ls font-semibold mb-4">Fill the placeholders of the
|
||||||
|
template</p>
|
||||||
|
{selectedTemplate?.availablePlaceholders?.map((placeholder) =>
|
||||||
|
<Input key={placeholder} label={placeholder} name={placeholder}/>
|
||||||
|
)}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="danger" variant="light" onPress={onClose}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button color="primary" type="submit">
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,4 +19,8 @@ class NotificationEndpoint(
|
|||||||
fun verifyCredentials(provider: String, credentials: Map<String, Any>): Boolean {
|
fun verifyCredentials(provider: String, credentials: Map<String, Any>): Boolean {
|
||||||
return notificationService.testCredentials(provider, credentials)
|
return notificationService.testCredentials(provider, credentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun sendTestNotification(templateKey: String, placeholders: Map<String, String>): Boolean {
|
||||||
|
return notificationService.sendTestNotification(templateKey, placeholders)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,12 +4,15 @@ import de.grimsi.gameyfin.core.events.PasswordResetRequestEvent
|
|||||||
import de.grimsi.gameyfin.notifications.providers.AbstractNotificationProvider
|
import de.grimsi.gameyfin.notifications.providers.AbstractNotificationProvider
|
||||||
import de.grimsi.gameyfin.notifications.templates.MessageTemplateService
|
import de.grimsi.gameyfin.notifications.templates.MessageTemplateService
|
||||||
import de.grimsi.gameyfin.notifications.templates.MessageTemplates
|
import de.grimsi.gameyfin.notifications.templates.MessageTemplates
|
||||||
|
import de.grimsi.gameyfin.users.UserService
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import org.springframework.context.ApplicationContext
|
import org.springframework.context.ApplicationContext
|
||||||
import org.springframework.context.event.EventListener
|
import org.springframework.context.event.EventListener
|
||||||
import org.springframework.scheduling.annotation.Async
|
import org.springframework.scheduling.annotation.Async
|
||||||
import org.springframework.scheduling.annotation.EnableAsync
|
import org.springframework.scheduling.annotation.EnableAsync
|
||||||
|
import org.springframework.security.core.Authentication
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -17,7 +20,8 @@ import java.util.*
|
|||||||
@Service
|
@Service
|
||||||
class NotificationService(
|
class NotificationService(
|
||||||
private val applicationContext: ApplicationContext,
|
private val applicationContext: ApplicationContext,
|
||||||
private val templateService: MessageTemplateService
|
private val templateService: MessageTemplateService,
|
||||||
|
private val userService: UserService
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val log: KLogger = KotlinLogging.logger {}
|
val log: KLogger = KotlinLogging.logger {}
|
||||||
@@ -47,6 +51,30 @@ class NotificationService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a test notification.
|
||||||
|
* Recipient is always the current user to prevent misuse.
|
||||||
|
*/
|
||||||
|
fun sendTestNotification(templateKey: String, placeholders: Map<String, String>): Boolean {
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
log.error { "No notification provider available, can't send test message" }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val auth: Authentication = SecurityContextHolder.getContext().authentication
|
||||||
|
val user = userService.getByUsername(auth.name) ?: throw IllegalStateException("User not found")
|
||||||
|
val template = templateService.getMessageTemplate(templateKey)
|
||||||
|
sendNotification(user.email, "[Gameyfin] Test Notification", template, placeholders)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error(e) { "Failed to send test notification" }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
@Async
|
@Async
|
||||||
@EventListener(PasswordResetRequestEvent::class)
|
@EventListener(PasswordResetRequestEvent::class)
|
||||||
fun onPasswordResetRequest(event: PasswordResetRequestEvent) {
|
fun onPasswordResetRequest(event: PasswordResetRequestEvent) {
|
||||||
|
|||||||
Reference in New Issue
Block a user