Implemented test notification sending

This commit is contained in:
grimsi
2024-09-24 16:51:03 +02:00
parent c0a790bda4
commit 9d8ae28b7a
3 changed files with 110 additions and 12 deletions
@@ -14,17 +14,21 @@ import {
ModalFooter,
ModalHeader,
Textarea,
Tooltip,
useDisclosure
} from "@nextui-org/react";
import {MessageTemplateEndpoint, NotificationEndpoint} from "Frontend/generated/endpoints";
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 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) {
const {isOpen, onOpen, onOpenChange} = useDisclosure();
const editorModal = useDisclosure();
const testNotificationModal = useDisclosure();
const [availableTemplates, setAvailableTemplates] = useState<MessageTemplateDto[]>([]);
const [selectedTemplate, setSelectedTemplate] = useState<MessageTemplateDto | null>(null);
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);
let templateContent = await MessageTemplateEndpoint.read(template.key, TemplateType.MJML);
@@ -66,13 +70,26 @@ function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
}
setTemplateContent(templateContent);
onOpen();
editorModal.onOpen();
}
function openTestNotification(template: MessageTemplateDto) {
setSelectedTemplate(template);
testNotificationModal.onOpen();
}
async function saveTemplate(template: MessageTemplateDto) {
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 (
<div className="flex flex-col">
<div className="flex flex-row">
@@ -102,12 +119,22 @@ function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
<div className="flex flex-col gap-4">
{availableTemplates.map((template: MessageTemplateDto) =>
<Card className="flex flex-row items-center gap-2 p-4" key={template.key}>
<Button isIconOnly
size="sm"
onPress={() => openModal(template)}
>
<Pencil/>
</Button>
<Tooltip content="Edit template">
<Button isIconOnly
size="sm"
onPress={() => openEditor(template)}
>
<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>
</Card>
)}
@@ -117,7 +144,7 @@ function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
</div>
</div>
<Modal isOpen={isOpen} onOpenChange={onOpenChange} size="5xl">
<Modal isOpen={editorModal.isOpen} onOpenChange={editorModal.onOpenChange} size="5xl">
<ModalContent>
{(onClose) => (
<>
@@ -190,6 +217,45 @@ function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
)}
</ModalContent>
</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>
);
}
@@ -19,4 +19,8 @@ class NotificationEndpoint(
fun verifyCredentials(provider: String, credentials: Map<String, Any>): Boolean {
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.templates.MessageTemplateService
import de.grimsi.gameyfin.notifications.templates.MessageTemplates
import de.grimsi.gameyfin.users.UserService
import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.context.ApplicationContext
import org.springframework.context.event.EventListener
import org.springframework.scheduling.annotation.Async
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 java.util.*
@@ -17,7 +20,8 @@ import java.util.*
@Service
class NotificationService(
private val applicationContext: ApplicationContext,
private val templateService: MessageTemplateService
private val templateService: MessageTemplateService,
private val userService: UserService
) {
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
@EventListener(PasswordResetRequestEvent::class)
fun onPasswordResetRequest(event: PasswordResetRequestEvent) {