add admin tools page
All checks were successful
delpoy / build-and-deploy (push) Successful in 40s

This commit is contained in:
bytedream 2024-12-03 14:04:15 +01:00
parent 9e282cf61b
commit 8cb1e8bec5
11 changed files with 190 additions and 3 deletions

View File

@ -4,6 +4,7 @@ export class Permissions {
static readonly Reports = 2 << 2;
static readonly Feedback = 2 << 3;
static readonly Settings = 2 << 4;
static readonly Tools = 2 << 5;
readonly value: number;
@ -31,7 +32,8 @@ export class Permissions {
Permissions.Users,
Permissions.Reports,
Permissions.Feedback,
Permissions.Settings
Permissions.Settings,
Permissions.Tools
];
}
@ -51,6 +53,9 @@ export class Permissions {
settings(): boolean {
return (this.value & Permissions.Reports) != 0;
}
tools(): boolean {
return (this.value & Permissions.Tools) != 0;
}
asArray(): number[] {
const array = [];

View File

@ -21,6 +21,7 @@ export const load: LayoutServerLoad = async ({ route, cookies }) => {
: null,
adminCount: session?.permissions.admin() ? await Admin.count() : null,
settingsRead: session?.permissions.settings(),
toolsRead: session?.permissions.tools(),
self: session
? JSON.parse(JSON.stringify(await Admin.findOne({ where: { id: session.userId } })))
: null

View File

@ -8,7 +8,8 @@
Flag,
UserGroup,
Users,
BookOpen
BookOpen,
WrenchScrewdriver
} from 'svelte-heros-v2';
import { buttonTriggeredRequest } from '$lib/components/utils';
import { goto } from '$app/navigation';
@ -69,6 +70,13 @@
name: 'Website Einstellungen',
badge: null,
enabled: data.settingsRead
},
{
path: `${env.PUBLIC_BASE_PATH}/admin/tools`,
icon: WrenchScrewdriver,
name: 'Tools',
badge: null,
enabled: data.toolsRead
}
];

View File

@ -21,7 +21,8 @@
Users: Permissions.Users,
Reports: Permissions.Reports,
Feedback: Permissions.Feedback,
Settings: Permissions.Settings
Settings: Permissions.Settings,
Tools: Permissions.Tools
};
let newAdminUsername = $state('');

View File

@ -0,0 +1,3 @@
<div class="flex justify-center">
<slot />
</div>

View File

@ -0,0 +1,11 @@
import type { PageServerLoad } from './$types';
import { getSession } from '$lib/server/session';
import { Permissions } from '$lib/permissions';
import { redirect } from '@sveltejs/kit';
import { env } from '$env/dynamic/public';
export const load: PageServerLoad = async ({ cookies }) => {
if (getSession(cookies, { permissions: [Permissions.Settings] }) == null) {
throw redirect(302, `${env.PUBLIC_BASE_PATH}/admin`);
}
};

View File

@ -0,0 +1,15 @@
<script lang="ts">
import UuidFinder from './UuidFinder.svelte';
let tools = [{ label: 'Account UUID finder', component: UuidFinder }];
</script>
<div class="mt-4">
{#each tools as tool}
{@const Component = tool.component}
<fieldset class="border border-solid rounded border-gray-700 py-3 px-6">
<legend class="text-sm px-1">{tool.label}</legend>
<Component />
</fieldset>
{/each}
</div>

View File

@ -0,0 +1,71 @@
import { getSession } from '$lib/server/session';
import { Permissions } from '$lib/permissions';
import type { RequestHandler } from '@sveltejs/kit';
import {
ApiError,
getBedrockUuid,
getJavaUuid,
RateLimitError,
UserNotFoundError
} from '$lib/server/minecraft';
import type { ZodType } from 'zod';
import { FindUuidSchema } from './schema';
export const POST = (async ({ url, request, cookies }) => {
if (getSession(cookies, { permissions: [Permissions.Tools] }) == null) {
return new Response(null, { status: 401 });
}
const action = url.searchParams.get('action');
if (!action) {
return new Response(null, { status: 400 });
}
try {
switch (action) {
case 'getuuid': {
const data = await parseData(FindUuidSchema, await request.json());
return new Response(await findUuid(data.username, data.edition), { status: 200 });
}
}
return new Response(null, { status: 400 });
} catch (error) {
return new Response(JSON.stringify({ error: error }), { status: 400 });
}
}) satisfies RequestHandler;
async function parseData<Output>(schema: ZodType<Output>, json: string): Promise<Output> {
const parseResult = await schema.safeParseAsync(json);
if (!parseResult.success) throw new Error(parseResult.error.toString());
return parseResult.data;
}
async function findUuid(username: string, edition: 'java' | 'bedrock'): Promise<string> {
let uuid = '';
try {
switch (edition) {
case 'java':
uuid = await getJavaUuid(username);
break;
case 'bedrock':
uuid = await getBedrockUuid(username);
break;
}
} catch (e) {
if (e instanceof UserNotFoundError) {
throw `Der Spielername ${username} existiert nicht`;
} else if (e instanceof ApiError) {
throw (e as Error).message;
} else if (e instanceof RateLimitError) {
throw 'Rate limit exceeded, bitte versuche es erneut';
} else {
throw e;
}
}
return JSON.stringify({
uuid: uuid
});
}

View File

@ -0,0 +1,44 @@
<script lang="ts">
import Input from '$lib/components/Input/Input.svelte';
import Select from '$lib/components/Input/Select.svelte';
import { sendRequest } from './tools';
let username = $state('');
let edition = $state('java');
let uuid = $state('');
</script>
<div class="flex flex-col items-center space-y-4">
<div class="flex flex-row space-x-2">
<Input type="text" size="sm" bind:value={username}>
{#snippet label()}
<span>Username</span>
{/snippet}
</Input>
<Select label="Edition" size="sm" bind:value={edition}>
<option value="java">Java Edition</option>
<option value="bedrock">Bedrock Edition</option>
</Select>
</div>
<Input
type="submit"
size="sm"
disabled={!username}
value="UUID finden"
onclick={() =>
sendRequest('getuuid', { username, edition })
.then((data) => (uuid = data.uuid))
.catch(() => (uuid = ''))}
/>
<div class="w-full">
<Input
type="text"
size="sm"
readonly={true}
disabled={!uuid}
pickyWidth={false}
placeholder="UUID... "
value={uuid}
/>
</div>
</div>

View File

@ -0,0 +1,6 @@
import { z } from 'zod';
export const FindUuidSchema = z.object({
username: z.string(),
edition: z.enum(['java', 'bedrock'])
});

View File

@ -0,0 +1,22 @@
import { env } from '$env/dynamic/public';
import { errorMessage } from '$lib/stores';
type Actions = 'getuuid';
export async function sendRequest<T = any>(action: Actions, data: any): Promise<T> {
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/tools?action=${action}`, {
method: 'POST',
body: JSON.stringify(data)
});
if (!response.ok) {
const data = await response.json();
if (Object.hasOwn(data, 'error')) {
errorMessage.set(data['error']);
} else {
errorMessage.set('Ein Fehler ist aufgetreten');
}
throw new Error();
}
return response.json();
}