diff --git a/src/routes/admin/reports/+page.svelte b/src/routes/admin/reports/+page.svelte index 37a0537..e2ba02b 100644 --- a/src/routes/admin/reports/+page.svelte +++ b/src/routes/admin/reports/+page.svelte @@ -363,6 +363,7 @@ if (!e.detail.draft) $reportCount += 1; currentPageReports = [e.detail, ...currentPageReports]; activeReport = currentPageReports[0]; + newReportModal.close(); }} /> </dialog> diff --git a/src/routes/admin/users/+page.svelte b/src/routes/admin/users/+page.svelte index 19743e2..ff9d630 100644 --- a/src/routes/admin/users/+page.svelte +++ b/src/routes/admin/users/+page.svelte @@ -1,6 +1,6 @@ <script lang="ts"> import type { PageData } from './$types'; - import { Check, NoSymbol, PencilSquare, Trash } from 'svelte-heros-v2'; + import { Check, NoSymbol, PencilSquare, Plus, Trash } from 'svelte-heros-v2'; import Input from '$lib/components/Input/Input.svelte'; import Select from '$lib/components/Input/Select.svelte'; import { env } from '$env/dynamic/public'; @@ -10,6 +10,7 @@ import HeaderBar from './HeaderBar.svelte'; import SortableTr from '$lib/components/Table/SortableTr.svelte'; import SortableTh from '$lib/components/Table/SortableTh.svelte'; + import NewUserModal from './NewUserModal.svelte'; export let data: PageData; @@ -19,6 +20,7 @@ let userPage = 0; let userFilter = { name: null, playertype: null }; let userTableContainerElement: HTMLDivElement; + let newUserModal: HTMLDialogElement; function fetchPageUsers(page: number) { if (!browser) return; @@ -208,6 +210,16 @@ </tr> {/each} {/await} + <tr> + <td colspan="100"> + <div class="flex justify-center items-center"> + <button class="btn btn-sm" on:click={() => newUserModal.show()}> + <Plus /> + <span>Neuer Spieler</span> + </button> + </div> + </td> + </tr> {/key} </tbody> </table> @@ -227,3 +239,12 @@ </div> </div> </div> + +<dialog class="modal" bind:this={newUserModal}> + <NewUserModal + on:submit={(e) => { + currentPageUsers = [...currentPageUsers, e.detail]; + newUserModal.close(); + }} + /> +</dialog> diff --git a/src/routes/admin/users/+server.ts b/src/routes/admin/users/+server.ts index 863d354..6f996de 100644 --- a/src/routes/admin/users/+server.ts +++ b/src/routes/admin/users/+server.ts @@ -1,8 +1,9 @@ import { getSession } from '$lib/server/session'; import { Permissions } from '$lib/permissions'; -import type { RequestHandler } from '@sveltejs/kit'; +import { error, type RequestHandler } from '@sveltejs/kit'; import { User } from '$lib/server/database'; import { type Attributes, Op } from 'sequelize'; +import { ApiError, getJavaUuid, getNoAuthUuid, UserNotFoundError } from '$lib/server/minecraft'; export const POST = (async ({ request, cookies }) => { if (getSession(cookies, { permissions: [Permissions.UserRead] }) == null) { @@ -88,6 +89,90 @@ export const PATCH = (async ({ request, cookies }) => { return new Response(); }) satisfies RequestHandler; +export const PUT = (async ({ request, cookies }) => { + if (getSession(cookies, { permissions: [Permissions.UserWrite] }) == null) { + return new Response(null, { + status: 401 + }); + } + + const data: { + firstname: string; + lastname: string; + + birthday: string; + telephone: string; + + username: string; + playertype: string; + } = await request.json(); + + if ( + data.firstname == null || + data.lastname == null || + data.birthday == null || + data.username == null || + data.playertype == null + ) { + return new Response(null, { status: 400 }); + } + + let uuid: string | null; + try { + switch (data.playertype) { + case 'java': + uuid = await getJavaUuid(data.username); + break; + case 'bedrock': + uuid = null; + // uuid = await getBedrockUuid(username); + break; + case 'noauth': + uuid = getNoAuthUuid(data.username); + break; + default: + throw new Error(`invalid player type (${data.playertype})`); + } + } catch (e) { + if (e instanceof UserNotFoundError) { + throw error(400, `Der Spielername ${data.username} existiert nicht`); + } else if (e instanceof ApiError) { + console.error((e as Error).message); + uuid = null; + } else { + console.error((e as Error).message); + throw error(500); + } + } + + if (uuid && (await User.findOne({ where: { uuid: uuid } }))) { + throw error(400, 'Dieser Minecraft-Account wurde bereits registriert'); + } else if ( + await User.findOne({ + where: { + firstname: data.firstname, + lastname: data.lastname, + birthday: new Date(data.birthday).toUTCString() + } + }) + ) { + throw error(400, 'Ein Nutzer mit demselben Namen und Geburtstag wurde bereits registriert'); + } + + await User.create({ + firstname: data.firstname, + lastname: data.lastname, + birthday: new Date(data.birthday).toUTCString(), + telephone: data.telephone, + username: data.username, + playertype: data.playertype, + password: null, + uuid: uuid + }); + + return new Response(); +}) satisfies RequestHandler; + export const DELETE = (async ({ request, cookies }) => { if (getSession(cookies, { permissions: [Permissions.UserWrite] }) == null) { return new Response(null, { diff --git a/src/routes/admin/users/NewUserModal.svelte b/src/routes/admin/users/NewUserModal.svelte new file mode 100644 index 0000000..2aa7a53 --- /dev/null +++ b/src/routes/admin/users/NewUserModal.svelte @@ -0,0 +1,114 @@ +<script lang="ts"> + import Input from '$lib/components/Input/Input.svelte'; + import { env } from '$env/dynamic/public'; + import Select from '$lib/components/Input/Select.svelte'; + import { errorMessage } from '$lib/stores'; + import { createEventDispatcher } from 'svelte'; + + const dispatch = createEventDispatcher(); + + let firstname: string; + let lastname: string; + let birthday: string; + let phone: string; + let username: string; + let playertype = 'java'; + + async function newUser() { + const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/users`, { + method: 'PUT', + body: JSON.stringify({ + firstname: firstname, + lastname: lastname, + birthday: birthday, + telephone: phone, + username: username, + playertype: playertype + }) + }); + if (response.ok) { + dispatch('submit', { + firstname: firstname, + lastname: lastname, + birthday: birthday, + telephone: phone, + username: username, + playertype: playertype + }); + globalCloseForm.submit(); + } else { + $errorMessage = (await response.json()).message; + } + } + + let globalCloseForm: HTMLFormElement; + + let reportForm: HTMLFormElement; + let confirmDialog: HTMLDialogElement; +</script> + +<form method="dialog" class="modal-box" bind:this={reportForm}> + <button + class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" + on:click|preventDefault={() => globalCloseForm.submit()}>✕</button + > + <h3 class="font-roboto text-xl">Neuer Spieler</h3> + <div class="grid grid-cols-2 gap-4"> + <Input type="text" required bind:value={firstname}> + <span slot="label">Vorname</span> + </Input> + <Input type="text" required bind:value={lastname}> + <span slot="label">Nachname</span> + </Input> + <Input type="date" required bind:value={birthday}> + <span slot="label">Geburtstag</span> + </Input> + <Input type="tel" bind:value={phone}> + <span slot="label">Telefonnummer</span> + </Input> + <Input type="text" required bind:value={username}> + <span slot="label">Minecraft-Spielername</span> + </Input> + <Select required label="Edition" bind:value={playertype}> + <option value="java">Java Edition</option> + <option value="bedrock">Bedrock Edition</option> + </Select> + </div> + <div class="flex flex-row space-x-2 mt-6"> + <Input + type="submit" + value="Hinzufügen" + on:click={(e) => { + if (reportForm.checkValidity()) { + e.detail.preventDefault(); + confirmDialog.show(); + } + }} + /> + <Input + type="submit" + value="Abbrechen" + on:click={(e) => { + e.detail.preventDefault(); + globalCloseForm.submit(); + }} + /> + </div> +</form> +<form method="dialog" class="modal-backdrop bg-[rgba(0,0,0,.3)]" bind:this={globalCloseForm}> + <button>close</button> +</form> + +<dialog class="modal" bind:this={confirmDialog}> + <form method="dialog" class="modal-box"> + <button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button> + <h3 class="font-roboto text-xl mb-2">Spieler hinzufügen?</h3> + <div class="flex flex-row space-x-2 mt-6"> + <Input type="submit" value="Hinzufügen" on:click={newUser} /> + <Input type="submit" value="Abbrechen" /> + </div> + </form> + <form method="dialog" class="modal-backdrop bg-[rgba(0,0,0,.3)]"> + <button>close</button> + </form> +</dialog>