Skip to content
Snippets Groups Projects
Commit ae8df7e2 authored by Aaron Dötsch's avatar Aaron Dötsch
Browse files

Add news notification type and admin interface

parent 7f95231d
No related branches found
No related tags found
No related merge requests found
......@@ -8,4 +8,5 @@ export const NotificationType = {
USE_VOUCHER: {key: 1<<4, name: "use-voucher"} as Type<"use-voucher">,
SEND_TRANSFER: {key: 1<<5, name: "send-transfer"} as Type<"send-transfer">,
RECEIVE_TRANSFER: {key: 1<<6, name: "receive-transfer"} as Type<"receive-transfer">,
NEWS: {key: 1<<7, name: "news"} as Type<"news">,
};
export type PossibleNotificationType = "buy" | "refund" | "deposit" | "withdraw" | "use-voucher" | "send-transfer" | "receive-transfer";
export type PossibleNotificationType = "buy" | "refund" | "deposit" | "withdraw" | "use-voucher" | "send-transfer" | "receive-transfer" | "news";
export type Item = {name: string, code: string, price: number, premium: number|undefined};
export type User = {name: string, id: number}
......@@ -11,6 +11,7 @@ export type WithdrawNotificationData = {amount: number, balanceBefore: number, b
export type UseVoucherNotificationData = {voucher: {code: string, value: number}, balanceBefore: number, balanceAfter: number};
export type SendTransferNotificationData = {amount: number, receiver: User, balanceBefore: number, balanceAfter: number};
export type ReceiveTransferNotificationData = {amount: number, sender: User, balanceBefore: number, balanceAfter: number};
export type NewsNotificationData = {title: string, textVersion: string, htmlVersion: string};
export type NotificationData<T extends PossibleNotificationType> =
T extends "buy" ? BuyNotificationData :
T extends "refund" ? RefundNotificationData :
......@@ -19,6 +20,7 @@ export type NotificationData<T extends PossibleNotificationType> =
T extends "use-voucher" ? UseVoucherNotificationData :
T extends "send-transfer" ? SendTransferNotificationData :
T extends "receive-transfer" ? ReceiveTransferNotificationData :
T extends "news" ? NewsNotificationData :
never;
export type HandlerReturnSuccess = {status: "success"};
......
......@@ -23,6 +23,9 @@ export function getMessageTitle<T extends PossibleNotificationType>(type: Notifi
case "receive-transfer": {
return "Überweisung erhalten";
}
case "news": {
return "Neu bei der Getränkekasse";
}
default: {
console.error(`Unknown notification type`, type);
return `Unknown notification type ${type}`;
......@@ -70,6 +73,9 @@ Dein neuer Kontostand beträgt ${(balanceAfter/100).toFixed(2)}€.`;
return `Du hast ${(amount/100).toFixed(2)}€ von ${sender.name} erhalten.
Dein neuer Kontostand beträgt ${(balanceAfter/100).toFixed(2)}€.`;
}
case "news": {
return (data as NotificationData<"news">).textVersion;
}
default: {
console.error(`Unknown notification type`, type);
return `Unknown notification type ${type}`;
......@@ -118,6 +124,9 @@ export function formatMessageToHtml<T extends PossibleNotificationType>(type: No
return `<p>Du hast ${(amount / 100).toFixed(2)}€ von ${escapeHtml(sender.name)} erhalten.</p>
<p>Dein neuer Kontostand beträgt ${(balanceAfter / 100).toFixed(2)}€.</p>`;
}
case "news": {
return (data as NotificationData<"news">).htmlVersion;
}
default: {
console.error(`Unknown notification type`, type);
return `Unknown notification type ${escapeHtml(JSON.stringify(type))}`;
......
......@@ -2,8 +2,11 @@ import { db } from "$lib/server/database";
import { ChannelType } from "./channelTypes";
import type { HandlerReturnError, HandlerReturnIgnored, HandlerReturnType, NotificationChannel, NotificationData, NotificationType, PossibleNotificationType } from "../../notifications/types";
export async function sendNotification<T extends PossibleNotificationType>(user: { id: number, notificationChannels: NotificationChannel[] | undefined } | number, type: NotificationType<T>, data: NotificationData<T>): Promise<HandlerReturnType[]>{
export async function sendNotification<T extends PossibleNotificationType>(user: { id: number, notificationChannels: NotificationChannel[] | undefined } | number | null, type: NotificationType<T>, data: NotificationData<T>): Promise<HandlerReturnType[]>{
let notificationChannels: NotificationChannel[];
if(user === null){
notificationChannels = await getNotificationChannels(type.key);
}else{
let userId: number;
if(typeof user === "number"){
userId = user;
......@@ -14,6 +17,7 @@ export async function sendNotification<T extends PossibleNotificationType>(user:
if(!notificationChannels){
notificationChannels = await getNotificationChannels(userId, type.key);
}
}
return Promise.all(notificationChannels.map(channel => {
const channelType = Object.values(ChannelType).find(type => type.key === channel.channelType);
if(!channelType){
......@@ -34,9 +38,17 @@ export async function sendNotification<T extends PossibleNotificationType>(user:
}));
};
async function getNotificationChannels(userId: number, notificationType: number): Promise<NotificationChannel[]> {
const allChannels = await db.notificationChannel.findMany({
function getNotificationChannels(userId: number, notificationType: number): Promise<NotificationChannel[]>;
function getNotificationChannels(notificationType: number): Promise<NotificationChannel[]>
async function getNotificationChannels(userId: number, notificationType?: number): Promise<NotificationChannel[]> {
let allChannels;
if(notificationType === undefined){
notificationType = userId;
allChannels = await db.notificationChannel.findMany();
}else{
allChannels = await db.notificationChannel.findMany({
where: { userId }
});
}
return allChannels.filter(channel => channel.notificationTypes & notificationType);
}
......@@ -2,6 +2,8 @@ import { error, type Actions } from "@sveltejs/kit";
import { getUsers } from "../api/users";
import { sendMail } from "$lib/server/mail";
import { format } from "./formatter";
import { sendNotification } from "$lib/server/notifications/handler";
import { NotificationType } from "$lib/notifications/notificationTypes";
export async function load(){
const users = await getUsers();
......@@ -13,6 +15,8 @@ export const actions: Actions = {
const data = await event.request.formData();
const subject = data.get("subject");
const message = data.get("content");
const type = data.get("type");
if(type === "mail"){
const users = data.get("users");
if(typeof subject !== "string" || typeof message !== "string" || typeof users !== "string") throw error(400, "Invalid request");
const userIDs = users.split(",").map(id => parseInt(id));
......@@ -32,5 +36,13 @@ export const actions: Actions = {
}
}
return result;
}else if(type === "notification"){
if(typeof subject !== "string" || typeof message !== "string") throw error(400, "Invalid request");
const notification = format(subject, message);
sendNotification(null, NotificationType.NEWS, notification);
return { status: "success" };
}else{
throw error(400, "Invalid request");
}
}
};
......@@ -7,15 +7,17 @@
export let data: {users: User[]};
let subject = "", content = "";
let type: "mail"|"notification" = "mail";
let minBalance, maxBalance;
$: filter = (user: User) => {
if(type === "notification") return false;
if(minBalance != null && user.balance < minBalance) return false;
if(maxBalance != null && user.balance > maxBalance) return false;
return true;
};
$: matchedUsers = data.users.filter(filter);
$: exampleUser = matchedUsers[Math.floor(Math.random()*matchedUsers.length)];
$: preview = exampleUser ? format(subject, content, exampleUser) : null;
$: preview = format(subject, content, exampleUser);
</script>
<style>
......@@ -32,12 +34,21 @@
<a href="/admin">Zurück</a>
<h2>Typ</h2>
<select bind:value={type}>
<option value="mail">Mail</option>
<option value="notification">Benachrichtigung</option>
</select>
{#if type === "mail"}
<h2>Empfänger</h2>
<label>Minimales Guthaben: <input type="number" bind:value={minBalance} /></label><br>
<label>Maximales Guthaben: <input type="number" bind:value={maxBalance} /></label><br>
{/if}
<h2>Mail</h2>
<form method="post" action="?/send" use:enhance={({form, data, cancel})=>{
<form method="post" action="?/send" use:enhance={({formElement, formData, cancel})=>{
if(type === "mail"){
return async ({result})=>{
if(result.type === "success"){
const { rejected, accepted, error } = result.data;
......@@ -53,22 +64,37 @@
}
}
};
}else{
return async ({result})=>{
if(result.type === "success"){
addMessage(MessageType.SUCCESS, "Benachrichtigung erfolgreich versendet");
}else{
addMessage(MessageType.ERROR, "Benachrichtigung konnte nicht versendet werden");
console.error(result);
}
};
}
}}>
<input type="text" name="subject" placeholder="Betreff" bind:value={subject} required /><br>
<textarea name="content" placeholder="Inhalt" bind:value={content} required></textarea><br>
<input type="hidden" name="type" value={type} />
{#if type === "mail"}
<input type="hidden" name="users" value={matchedUsers.map(u=>u.id).join(",")} />
{/if}
<button type="submit">Senden</button>
</form>
<h2>Vorschau</h2>
{#if preview}
{#if type==="mail" && !matchedUsers.length}
<p class="preview">Die Mail wird an keinen Nutzer gesendet</p>
{:else}
<div class="preview">
{#if type === "mail"}
<p><b>An: </b>{matchedUsers.map(u=>u.email).join(", ")}</p>
<h3>{preview.subject}</h3>
{/if}
<h3>{type === "mail" ? preview.subject : preview.title}</h3>
<div>
{@html preview.html}
{@html type === "mail" ? preview.html : preview.htmlVersion}
</div>
</div>
{:else}
<p class="preview">Die Mail wird an keinen Nutzer gesendet</p>
{/if}
......@@ -3,6 +3,7 @@ import MarkdownItHighlight from "markdown-it-highlightjs";
import hljs from "highlight.js";
import hljsCSS from "highlight.js/styles/github.css?inline";
import type { User } from "@prisma/client";
import type { NewsNotificationData } from "$lib/notifications/types";
const md = new MarkdownIt({
breaks: true,
......@@ -16,6 +17,7 @@ const md = new MarkdownIt({
hljs
});
md.core.ruler.push("replacePlaceholders", state=>{
if(!state.env.user) return;
for(const token of state.tokens){
handleToken(token, state.env.user);
}
......@@ -40,7 +42,10 @@ type Mail = {
html: string
};
export function format(subject: string, content: string, user: User): Mail {
export function format(subject: string, content: string, user: User): Mail;
export function format(subject: string, content: string): NewsNotificationData;
export function format(subject: string, content: string, user?: User) {
if(user){
const html = md.render(content, {user});
const css = html.includes("hljs") ? `<style>${hljsCSS}</style>` : "";
return {
......@@ -49,6 +54,15 @@ export function format(subject: string, content: string, user: User): Mail {
text: replacePlaceholders(content, user),
html: css + html
};
}else{
const html = md.render(content);
const css = html.includes("hljs") ? `<style>${hljsCSS}</style>` : "";
return {
title: subject,
textVersion: content,
htmlVersion: css + html
};
}
}
function replacePlaceholders(content: string, user: User): string {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment