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

Add admin interface for managing notifications

parent 72194611
No related branches found
No related tags found
No related merge requests found
import { restockArticle, createArticle } from "../articles";
import { createUser } from "../users";
import { deleteItemTransaction } from "../transactions";
import { deleteNotificationChannel } from "../notificationChannels";
export const POST = async ({request, params}) => {
switch(params.slug){
......@@ -74,6 +75,23 @@ export const DELETE = async ({request, params}) => {
const transaction = await deleteItemTransaction(data.id);
return new Response(JSON.stringify({success: true, transaction}), {status: 200});
}catch(error){
console.error(error);
return new Response(JSON.stringify({error: "Internal server error"}), {status: 500});
}
}catch(e){
return new Response(JSON.stringify({message: "Invalid data"}), {headers: {'content-type': 'application/json', 'status': 400}});
}
}
case "notificationchannel": {
try {
const data = await request.json();
if(!data.id) return new Response(JSON.stringify({message: "Missing required fields"}), {status: 400});
if(!Number.isInteger(data.id) || data.id <= 0) return new Response(JSON.stringify({message: "Invalid data"}), {status: 400});
try{
const channel = await deleteNotificationChannel(data.id);
return new Response(JSON.stringify({success: true, channel}), {status: 200});
}catch(error){
console.error(error);
return new Response(JSON.stringify({error: "Internal server error"}), {status: 500});
}
}catch(e){
......
import { db } from "$lib/server/database";
export async function createNotificationChannel(userId, channelType, notificationTypes, recipient){
return db.notificationChannel.create({
data: {
userId,
channelType,
notificationTypes,
recipient
}
});
}
export async function deleteNotificationChannel(id){
return db.notificationChannel.delete({
where: { id }
});
}
export async function updateNotificationChannel(id, channelType, notificationTypes, recipient){
return db.notificationChannel.update({
where: { id },
data: {
channelType,
notificationTypes,
recipient
}
});
}
......@@ -21,10 +21,13 @@ export async function getUsers(){
return db.user.findMany();
}
export async function getUser(id, includeCards=false){
export async function getUser(id, includeCards=false, includeNotificationChannels=false){
let include = includeCards || includeNotificationChannels ? {} : undefined;
if(includeCards) include.cards = true;
if(includeNotificationChannels) include.notificationChannels = true;
return db.user.findUnique({
where: { id },
include: includeCards ? { cards: true } : undefined
include
});
}
......
import { addUserCard, getUser, updateCard } from "../../api/users";
import { fail } from "@sveltejs/kit";
import { createTransaction } from "../../api/transactions";
import { ChannelType } from "$lib/notifications/channelTypes";
import { createNotificationChannel, updateNotificationChannel } from "../../api/notificationChannels";
export const load = async ({ params }) => {
const { id } = params;
const user = await getUser(parseInt(id), true);
const user = await getUser(parseInt(id), true, true);
return { user };
}
......@@ -39,5 +41,30 @@ export const actions = {
delete user.createdAt;
delete user.updatedAt;
return { success: !!user, user };
},
createNotificationChannel: async event => {
const userId = parseInt(event.params.id);
const data = await event.request.formData();
const channelType = parseInt(data.get('channelType'));
const notificationTypes = parseInt(data.get('notificationTypes'));
const recipient = data.get('recipient');
if(!Object.values(ChannelType).some(type => type.key === channelType)) return fail(400, { error: "Invalid form data" });
if(isNaN(notificationTypes) || !Number.isInteger(notificationTypes) || notificationTypes < 0) return fail(400, { error: "Invalid form data" });
if(typeof recipient !== "string" || !recipient.length) return fail(400, { error: "Invalid form data" });
const channel = await createNotificationChannel(userId, channelType, notificationTypes, recipient);
return { success: !!channel, channel };
},
updateNotificationChannel: async event => {
const data = await event.request.formData();
const id = parseInt(data.get('id'));
const channelType = parseInt(data.get('channelType'));
const notificationTypes = parseInt(data.get('notificationTypes'));
const recipient = data.get('recipient');
if(Number.isNaN(id) || !Number.isInteger(id) || id < 0) return fail(400, { error: "Invalid form data" });
if(!Object.values(ChannelType).some(type => type.key === channelType)) return fail(400, { error: "Invalid form data" });
if(isNaN(notificationTypes) || !Number.isInteger(notificationTypes) || notificationTypes < 0) return fail(400, { error: "Invalid form data" });
if(typeof recipient !== "string" || !recipient.length) return fail(400, { error: "Invalid form data" });
const channel = await updateNotificationChannel(id, channelType, notificationTypes, recipient);
return { success: !!channel, channel };
}
}
......@@ -2,10 +2,12 @@
import CardList from "./CardList.svelte";
import { enhance } from "$app/forms";
import { addMessage, MessageType } from "$lib/messages";
import NotificationChannelList from "./NotificationChannelList.svelte";
export let data;
let cards = data.user.cards;
let balance = data.user.balance;
let notificationChannels = data.user.notificationChannels;
</script>
<a href="/admin/user">Zurück</a>
......@@ -36,3 +38,6 @@ import { addMessage, MessageType } from "$lib/messages";
<label title="positiv: Nutzer zahl Geld ein, negativ: Nutzer hebt Geld ab">Betrag: <input name="amount" value="0" /></label>
<input type="submit" value="Speichern" />
</form>
<h2>Benachrichtigungen</h2>
<NotificationChannelList bind:notificationChannels />
<script>
import { ChannelType } from "$lib/notifications/channelTypes";
import { NotificationType } from "$lib/notifications/notificationTypes";
import BitfieldSelector from "../../../../components/BitfieldSelector.svelte";
export let id, channelType, notificationTypes, recipient, edit, deleteChannel;
</script>
{#if edit}
<tr>
<td>
<select name="channelType" form="notif-form{id}" required>
{#each Object.entries(ChannelType) as [name, {key}]}
<option value={key} selected={key==channelType}>{name}</option>
{/each}
</select>
</td>
<td><BitfieldSelector value={notificationTypes} fields={Object.values(NotificationType).map(type=>({name: type.name, value: type.key}))} formName="notif-form{id}" formFieldName="notificationTypes" /></td>
<td><input type="text" name="recipient" form="notif-form{id}" value={recipient} /></td>
<td><button type="submit" form="notif-form{id}">Speichern</button><button on:click={()=>edit=false}>Abbrechen</button></td>
</tr>
{:else}
<tr>
<td>{Object.entries(ChannelType).find(([,type])=>type.key==channelType)?.[0]}</td>
<td><span title={Object.values(NotificationType).filter(t=>t.key&notificationTypes).map(t=>t.name).join(", ")}>{notificationTypes}</span></td>
<td>{recipient}</td>
<td><button type="button" on:click={() => edit = true}>Bearbeiten</button><button on:click={deleteChannel}>Löschen</button></td>
</tr>
{/if}
<script>
import { enhance } from "$app/forms";
import { MessageType, addMessage } from "$lib/messages";
import { ChannelType } from "$lib/notifications/channelTypes";
import { NotificationType } from "$lib/notifications/notificationTypes";
import BitfieldSelector from "../../../../components/BitfieldSelector.svelte";
import NotificationChannelItem from "./NotificationChannelItem.svelte";
/** @type {import("$lib/notifications/types").NotificationChannel[]} */
export let notificationChannels;
let edit = notificationChannels.map(() => false);
function deleteChannel(id){
fetch("/admin/api/notificationchannel", {
method: "DELETE",
body: JSON.stringify({id}),
headers: {
"Content-Type": "application/json"
}
}).then(res => {
if(res.ok){
notificationChannels = notificationChannels.filter(channel=>channel.id!==id);
edit = edit.filter((_,i)=>i!==id);
addMessage(MessageType.SUCCESS, "Benachrichtigungskanal wurde gelöscht");
}else{
addMessage(MessageType.ERROR, "Benachrichtigungskanal konnte nicht gelöscht werden");
console.error(res);
}
}).catch(err=>{
addMessage(MessageType.ERROR, "Benachrichtigungskanal konnte nicht gelöscht werden");
console.error(err);
});
}
</script>
<table>
<tr>
<th>Typ</th>
<th>Events</th>
<th>Empfänger</th>
<th></th>
</tr>
{#each notificationChannels as channel, i}
<NotificationChannelItem {...channel} bind:edit={edit[i]} deleteChannel={()=>deleteChannel(channel.id)} />
{/each}
<tr>
<td>
<select name="channelType" form="notif-form-new" required>
{#each Object.entries(ChannelType) as [name, {key}]}
<option value={key}>{name}</option>
{/each}
</select>
</td>
<td><BitfieldSelector value={0} fields={Object.values(NotificationType).map(type=>({name: type.name, value: type.key}))} formName="notif-form-new" formFieldName="notificationTypes" /></td>
<td><input type="text" name="recipient" form="notif-form-new" /></td>
<td><button type="submit" form="notif-form-new">Erstellen</button></td>
</tr>
</table>
{#each notificationChannels as channel, i}
<form method="post" action="?/updateNotificationChannel" id="notif-form{channel.id}" use:enhance={({form, data, cancel})=>{
return async ({result})=>{
if(result.type === "success"){
notificationChannels[i] = result.data.channel;
edit[i] = false;
form.reset();
addMessage(MessageType.SUCCESS, "Benachrichtigungskanal wurde bearbeitet");
}else{
addMessage(MessageType.ERROR, "Benachrichtigungskanal konnte nicht bearbeitet werden");
console.error(result);
}
};
}}>
<input type="hidden" name="id" value={channel.id} />
</form>
{/each}
<form method="post" action="?/createNotificationChannel" id="notif-form-new" use:enhance={({form, data, cancel})=>{
return async ({result})=>{
if(result.type === "success"){
edit = [...edit, false];
notificationChannels = [...notificationChannels, result.data.channel];
form.reset();
addMessage(MessageType.SUCCESS, "Benachrichtigungskanal wurde erstellt");
}else{
addMessage(MessageType.ERROR, "Benachrichtigungskanal konnte nicht erstellt werden");
console.error(result);
}
};
}}></form>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment