diff --git a/src/routes/admin/api/[slug]/+server.js b/src/routes/admin/api/[slug]/+server.js index 7825b588ec87741a5fe4d50b96acb1b692d561ba..31c3242267070bb3b6b0086fc0cefe6477db6584 100644 --- a/src/routes/admin/api/[slug]/+server.js +++ b/src/routes/admin/api/[slug]/+server.js @@ -1,6 +1,7 @@ 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){ diff --git a/src/routes/admin/api/notificationChannels.js b/src/routes/admin/api/notificationChannels.js new file mode 100644 index 0000000000000000000000000000000000000000..c91aee1fe095a8f81e8b77de3fd168366cae687e --- /dev/null +++ b/src/routes/admin/api/notificationChannels.js @@ -0,0 +1,29 @@ +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 + } + }); +} diff --git a/src/routes/admin/api/users.js b/src/routes/admin/api/users.js index ced7bab77de93fdc6f677aee6d419afd9c849d11..d8e27f6e2e19b6106406383d8ca90bed96198a07 100644 --- a/src/routes/admin/api/users.js +++ b/src/routes/admin/api/users.js @@ -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 }); } diff --git a/src/routes/admin/user/[id]/+page.server.js b/src/routes/admin/user/[id]/+page.server.js index 280c70122cfdab584eb1650857c3fdaaa0f3e690..c7fb2b4d025f7b7465c56d42e0160bfff13f49fe 100644 --- a/src/routes/admin/user/[id]/+page.server.js +++ b/src/routes/admin/user/[id]/+page.server.js @@ -1,10 +1,12 @@ 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 }; } } diff --git a/src/routes/admin/user/[id]/+page.svelte b/src/routes/admin/user/[id]/+page.svelte index 6d01637263d21e4014f4433b846fbf11aa77c29d..869bd2c38e2ba50816d147e839eeeb6dfde3baf8 100644 --- a/src/routes/admin/user/[id]/+page.svelte +++ b/src/routes/admin/user/[id]/+page.svelte @@ -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 /> diff --git a/src/routes/admin/user/[id]/NotificationChannelItem.svelte b/src/routes/admin/user/[id]/NotificationChannelItem.svelte new file mode 100644 index 0000000000000000000000000000000000000000..6b9dc20fec9629870dcdf613fbdc070d0afd8f69 --- /dev/null +++ b/src/routes/admin/user/[id]/NotificationChannelItem.svelte @@ -0,0 +1,29 @@ +<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¬ificationTypes).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} diff --git a/src/routes/admin/user/[id]/NotificationChannelList.svelte b/src/routes/admin/user/[id]/NotificationChannelList.svelte new file mode 100644 index 0000000000000000000000000000000000000000..cce8af454d0cab5eab7a554bed375a78eac4d3f3 --- /dev/null +++ b/src/routes/admin/user/[id]/NotificationChannelList.svelte @@ -0,0 +1,89 @@ +<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>