Compare commits
11 Commits
1.0.4
...
dc2766f0ef
| Author | SHA1 | Date | |
|---|---|---|---|
| dc2766f0ef | |||
| 7d231730a6 | |||
| 956571e470 | |||
| 4405c23bee | |||
| aaf09e13f5 | |||
| daf3f779aa | |||
| 48bc66b89a | |||
| 3a52b85dfb | |||
| 7349624da9 | |||
| 25037f4798 | |||
| 38eee8b38c |
@@ -18,8 +18,10 @@ services:
|
||||
JEOPARDY_URL: http://localhost:11000
|
||||
ports:
|
||||
- "11001:12345"
|
||||
volumes:
|
||||
- jeopardyserver_data_volume:/data
|
||||
mongo:
|
||||
image: mongo:8.0.14
|
||||
image: mongo:8.0.17
|
||||
restart: always
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: jeopardyadmin
|
||||
@@ -41,3 +43,4 @@ services:
|
||||
|
||||
volumes:
|
||||
mongodb_data_volume:
|
||||
jeopardyserver_data_volume:
|
||||
|
||||
4
docker-dev.sh
Normal file
4
docker-dev.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
docker compose down &
|
||||
docker build -t jeopardy .
|
||||
docker compose up -d
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "jeopardy",
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "jeopardy",
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.6",
|
||||
"dependencies": {
|
||||
"axios": "^1.12.2",
|
||||
"cookie": "^1.0.2"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jeopardy",
|
||||
"private": true,
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.6",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -12,7 +12,8 @@
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"docker-build": "docker build -t jeopardy ."
|
||||
"docker-build": "docker build -t jeopardy .",
|
||||
"docker-dev": "./docker-dev.sh"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.2.5",
|
||||
|
||||
11
src/app.css
11
src/app.css
@@ -17,6 +17,17 @@
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
color: grey !important;
|
||||
border-color: gray !important;
|
||||
cursor: unset !important;
|
||||
}
|
||||
|
||||
.btn:disabled:hover {
|
||||
background-color: unset !important;
|
||||
cursor: unset !important;
|
||||
}
|
||||
|
||||
.inputField {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"
|
||||
/>
|
||||
<script src="https://kit.fontawesome.com/4115dc7344.js" crossorigin="anonymous"></script>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover" class="size-full">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios from "axios";
|
||||
import { env } from "$env/dynamic/public";
|
||||
import UserSvelte from "./User.svelte";
|
||||
import UserSvelte, { type UserObj } from "./User.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
export async function isAuthenticated() {
|
||||
@@ -8,13 +8,13 @@ export async function isAuthenticated() {
|
||||
return true;
|
||||
}
|
||||
return axios
|
||||
.get(
|
||||
`${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}/user/username`,
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.get(`${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}/auth`, {
|
||||
withCredentials: true
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
UserSvelte.username = res.data;
|
||||
UserSvelte.user = res.data as UserObj;
|
||||
console.log(UserSvelte.id, UserSvelte.username, UserSvelte.role);
|
||||
return true;
|
||||
} else {
|
||||
goto("/login");
|
||||
|
||||
96
src/lib/Modal.svelte
Normal file
96
src/lib/Modal.svelte
Normal file
@@ -0,0 +1,96 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
interface Props {
|
||||
showModal: boolean;
|
||||
header: Snippet;
|
||||
children: Snippet;
|
||||
actionButtons?: Snippet;
|
||||
cancelFn?: () => void;
|
||||
okFn: () => Promise<boolean>;
|
||||
oncloseFn?: () => void;
|
||||
okButtonText?: string;
|
||||
[key: string]: unknown;
|
||||
width?: string;
|
||||
height?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
showModal = $bindable(),
|
||||
header,
|
||||
children,
|
||||
cancelFn,
|
||||
okFn,
|
||||
oncloseFn,
|
||||
actionButtons,
|
||||
okButtonText = "Ok",
|
||||
width,
|
||||
height
|
||||
}: Props = $props();
|
||||
|
||||
let dialog: HTMLDialogElement | undefined = $state(); // HTMLDialogElement
|
||||
|
||||
$effect(() => {
|
||||
if (showModal) dialog?.showModal();
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
|
||||
<dialog
|
||||
bind:this={dialog}
|
||||
onclose={() => {
|
||||
showModal = false;
|
||||
if (oncloseFn) oncloseFn();
|
||||
}}
|
||||
onclick={(e) => {
|
||||
if (e.target === dialog) dialog.close();
|
||||
}}
|
||||
class="rounded-md"
|
||||
style:width
|
||||
style:height
|
||||
>
|
||||
<div class="flex h-full flex-col gap-4 p-4">
|
||||
{@render header?.()}
|
||||
<div class="grow overflow-y-auto">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
<!-- <div class="grow"></div> -->
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<div class="flex justify-end gap-4">
|
||||
{@render actionButtons?.()}
|
||||
<button
|
||||
autofocus
|
||||
onclick={() => {
|
||||
dialog?.close();
|
||||
if (cancelFn) cancelFn();
|
||||
}}
|
||||
class="btn">Abbrechen</button
|
||||
>
|
||||
<button
|
||||
autofocus
|
||||
onclick={async () => {
|
||||
if (await okFn()) {
|
||||
dialog?.close();
|
||||
}
|
||||
}}
|
||||
class="btn min-w-[64px]">{okButtonText}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<style>
|
||||
dialog {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
/* Move it back 50% relative to self */
|
||||
-webkit-transform: translateX(-50%) translateY(-50%);
|
||||
-moz-transform: translateX(-50%) translateY(-50%);
|
||||
-ms-transform: translateX(-50%) translateY(-50%);
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
}
|
||||
dialog::backdrop {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
</style>
|
||||
452
src/lib/RessourceManager.svelte
Normal file
452
src/lib/RessourceManager.svelte
Normal file
@@ -0,0 +1,452 @@
|
||||
<script lang="ts">
|
||||
import { env } from "$env/dynamic/public";
|
||||
import axios from "axios";
|
||||
import Modal from "./Modal.svelte";
|
||||
import { url } from "./util";
|
||||
import { isDir, isRessource, type Directory, type Ressource } from "./Types";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
interface Props {
|
||||
show: boolean;
|
||||
ok?: (res: Ressource) => void;
|
||||
}
|
||||
|
||||
let { show = $bindable(false), ok = (res) => {} }: Props = $props();
|
||||
|
||||
let file: File | null = null;
|
||||
let path: string = $state("/");
|
||||
|
||||
let fetchingRessources = $state(false);
|
||||
let ressources: (Ressource | Directory)[] = $state([]);
|
||||
let selectedRessource: Ressource | undefined = $state();
|
||||
|
||||
let error = $state("");
|
||||
|
||||
let showNewDir = $state(false);
|
||||
let newDirName = $state("");
|
||||
let showDeleteDir = $state(false);
|
||||
let dirToDelete: Directory | undefined = $state();
|
||||
|
||||
let showDeleteRessource = $state(false);
|
||||
let resToDelete: Ressource | undefined = $state();
|
||||
|
||||
let showRenameFile = $state(false);
|
||||
let fileToRename: Ressource | undefined = $state();
|
||||
let newFileName = $state("");
|
||||
|
||||
function handleFileChange(event: Event) {
|
||||
const target = event.target as HTMLInputElement | null;
|
||||
if (target?.files?.[0]) {
|
||||
file = target.files[0];
|
||||
}
|
||||
}
|
||||
|
||||
function uploadData(event: SubmitEvent) {
|
||||
event.preventDefault();
|
||||
if (file === null) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("path", path);
|
||||
formData.append("file", file);
|
||||
|
||||
axios
|
||||
.post(
|
||||
`${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}/upload`,
|
||||
formData,
|
||||
{
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
},
|
||||
onUploadProgress: (event) => {
|
||||
console.log(event);
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
fetchDirectory();
|
||||
} else {
|
||||
alert("Failed with status: " + response.status);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
alert(err);
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteRessource(res: Ressource | Directory): Promise<boolean> {
|
||||
if (isRessource(res)) {
|
||||
return axios
|
||||
.delete(url("/cdn/" + res.user + "/" + res.filename), {
|
||||
withCredentials: true
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
fetchDirectory();
|
||||
return true;
|
||||
} else {
|
||||
alert("Something went wrong: " + response.status);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return false;
|
||||
});
|
||||
} else if (isDir(res)) {
|
||||
showDeleteDir = true;
|
||||
dirToDelete = res;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function deleteRessourceCancel() {
|
||||
resToDelete = undefined;
|
||||
}
|
||||
|
||||
async function deleteDir() {
|
||||
if (dirToDelete === undefined) return false;
|
||||
return axios
|
||||
.delete(url("/directory"), {
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
data: { path: path + (path === "/" ? "" : "/") + dirToDelete.name },
|
||||
withCredentials: true
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
fetchDirectory();
|
||||
dirToDelete = undefined;
|
||||
return true;
|
||||
} else {
|
||||
alert("Failed to delete directory: " + response.status);
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function deleteDirCancel() {
|
||||
dirToDelete = undefined;
|
||||
}
|
||||
|
||||
async function fetchDirectory() {
|
||||
fetchingRessources = true;
|
||||
return axios
|
||||
.post(
|
||||
url("/directory"),
|
||||
{ path },
|
||||
{
|
||||
withCredentials: true
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
ressources = response.data;
|
||||
ressources.sort((a, b) => {
|
||||
if (isDir(a) && !isDir(b)) return -1;
|
||||
if (!isDir(a) && isDir(b)) return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
if (path !== "/") {
|
||||
ressources.unshift({
|
||||
isDir: true,
|
||||
name: ".."
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
})
|
||||
.finally(() => {
|
||||
fetchingRessources = false;
|
||||
});
|
||||
}
|
||||
|
||||
async function addDirectoryOk(): Promise<boolean> {
|
||||
error = "";
|
||||
if (newDirName.length <= 0) {
|
||||
error = "Gib einen Namen für den Ordner ein";
|
||||
return false;
|
||||
}
|
||||
|
||||
return axios
|
||||
.put(url("/directory"), { name: newDirName, path }, { withCredentials: true })
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
fetchDirectory();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
error = "Etwas ist schief gelaufen";
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function addDirectoryCancel() {
|
||||
error = "";
|
||||
newDirName = "";
|
||||
}
|
||||
|
||||
function ressourceClicked(res: Ressource | Directory) {
|
||||
if (isRessource(res)) {
|
||||
selectedRessource = res;
|
||||
} else if (isDir(res)) {
|
||||
if (res.name === "..") {
|
||||
let breadcumbs = path.split("/");
|
||||
breadcumbs.pop();
|
||||
path = breadcumbs.join("/");
|
||||
if (path.length <= 0) path = "/";
|
||||
} else {
|
||||
if (!path.endsWith("/")) {
|
||||
path += "/";
|
||||
}
|
||||
path += res.name;
|
||||
}
|
||||
fetchDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
function renameRessource(res: Ressource) {
|
||||
fileToRename = res;
|
||||
newFileName = res.name;
|
||||
showRenameFile = true;
|
||||
}
|
||||
|
||||
async function renameFile() {
|
||||
if (fileToRename === undefined) return false;
|
||||
return axios
|
||||
.put(
|
||||
url("/cdn/" + fileToRename.user + "/" + fileToRename.filename),
|
||||
{
|
||||
name: newFileName
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
fetchDirectory();
|
||||
fileToRename = undefined;
|
||||
newFileName = "";
|
||||
return true;
|
||||
} else {
|
||||
alert("Failed to rename File: " + response.status);
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function renameFileCancel() {
|
||||
fileToRename = undefined;
|
||||
newFileName = "";
|
||||
}
|
||||
|
||||
function closeRessourceManager() {
|
||||
selectedRessource = undefined;
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (show) {
|
||||
fetchDirectory();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
bind:showModal={show}
|
||||
okFn={async () => {
|
||||
if (selectedRessource) ok({ ...selectedRessource });
|
||||
return true;
|
||||
}}
|
||||
oncloseFn={closeRessourceManager}
|
||||
okButtonText={selectedRessource !== undefined ? selectedRessource.name : "Ok"}
|
||||
width="90%"
|
||||
height="90%"
|
||||
>
|
||||
{#snippet header()}
|
||||
<h2 class="text-3xl">Ressourcenmanager - {path}</h2>
|
||||
{/snippet}
|
||||
<div class="h-full">
|
||||
{#if fetchingRessources}
|
||||
Loading...
|
||||
{:else if ressources.length > 0}
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each ressources as ressource}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="flex grow cursor-pointer items-center justify-between rounded-sm border-1 border-solid p-1 hover:bg-gray-200"
|
||||
onclick={() => ressourceClicked(ressource)}
|
||||
class:bg-green-200={isRessource(ressource) &&
|
||||
ressource._id === selectedRessource?._id}
|
||||
>
|
||||
<div>
|
||||
{#if isDir(ressource)}
|
||||
<i class="fa-solid fa-folder"></i>
|
||||
{ressource.name}
|
||||
{:else}
|
||||
{#if ressource.mimetype.includes("image")}
|
||||
<i class="fa-solid fa-image"></i>
|
||||
{:else if ressource.mimetype.includes("audio")}
|
||||
<i class="fa-solid fa-music"></i>
|
||||
{:else}
|
||||
<i class="fa-solid fa-file"></i>
|
||||
{/if}
|
||||
{ressource.name}
|
||||
{/if}
|
||||
</div>
|
||||
{#if !(isDir(ressource) && ressource.name === "..")}
|
||||
<div class="flex gap-4">
|
||||
{#if isRessource(ressource)}
|
||||
<!-- svelte-ignore a11y_consider_explicit_label -->
|
||||
<button
|
||||
type="button"
|
||||
class="btn border-black"
|
||||
onclick={(event) => {
|
||||
event.stopPropagation();
|
||||
renameRessource(ressource);
|
||||
}}><i class="fa-solid fa-pencil"></i></button
|
||||
>
|
||||
{/if}
|
||||
<!-- svelte-ignore a11y_consider_explicit_label -->
|
||||
<button
|
||||
type="button"
|
||||
class="btn border-red-600 text-red-600"
|
||||
onclick={(event) => {
|
||||
event.stopPropagation();
|
||||
if (isRessource(ressource)) {
|
||||
showDeleteRessource = true;
|
||||
resToDelete = ressource;
|
||||
} else if (isDir(ressource)) {
|
||||
dirToDelete = ressource;
|
||||
showDeleteDir = true;
|
||||
}
|
||||
}}><i class="fa-solid fa-trash"></i></button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex h-full w-full grow-2 flex-col items-center justify-center">
|
||||
<div class="text-[128px] text-gray-300"><i class="fa-solid fa-database"></i></div>
|
||||
<div class="text-[24px] text-gray-500 select-none">
|
||||
Noch keine Ressourcen vorhanden
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#snippet actionButtons()}
|
||||
<form
|
||||
action={`${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}/upload`}
|
||||
enctype="multipart/form-data"
|
||||
method="post"
|
||||
onsubmit={uploadData}
|
||||
>
|
||||
<input class="btn" type="file" name="file" onchange={handleFileChange} />
|
||||
<input type="submit" value="Hochladen" class="btn" />
|
||||
</form>
|
||||
<button class="btn" type="button" onclick={() => (showNewDir = true)}>Neuer Ordner</button>
|
||||
{/snippet}
|
||||
</Modal>
|
||||
|
||||
<Modal bind:showModal={showNewDir} okFn={addDirectoryOk} cancelFn={addDirectoryCancel}>
|
||||
{#snippet header()}
|
||||
<h2 class="text-3xl">Neuer Ordner</h2>
|
||||
{/snippet}
|
||||
<div>
|
||||
<label for="directory" class="">Name</label>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="directory"
|
||||
id="directory"
|
||||
class="borders mt-2 mb-2 w-full"
|
||||
bind:value={newDirName}
|
||||
/>
|
||||
</div>
|
||||
{#if error.length > 0}
|
||||
<div class="text-red-700">{error}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
bind:showModal={showDeleteRessource}
|
||||
okFn={async () => {
|
||||
if (resToDelete === undefined) return false;
|
||||
return deleteRessource(resToDelete);
|
||||
}}
|
||||
cancelFn={deleteRessourceCancel}
|
||||
>
|
||||
{#snippet header()}
|
||||
<h2 class="text-3xl">Ressource löschen</h2>
|
||||
{/snippet}
|
||||
|
||||
<div>
|
||||
Soll die Ressource {resToDelete?.name} wirklich gelöscht werden?
|
||||
</div>
|
||||
{#if error.length > 0}
|
||||
<div class="text-red-700">{error}</div>
|
||||
{/if}
|
||||
</Modal>
|
||||
|
||||
<Modal bind:showModal={showDeleteDir} okFn={deleteDir} cancelFn={deleteDirCancel}>
|
||||
{#snippet header()}
|
||||
<h2 class="text-3xl">Ordner löschen</h2>
|
||||
{/snippet}
|
||||
|
||||
<div>
|
||||
Soll der Ordner {dirToDelete?.name} wirklich gelöscht werden? Alle darin enthaltenen Daten gehen
|
||||
verloren.
|
||||
</div>
|
||||
{#if error.length > 0}
|
||||
<div class="text-red-700">{error}</div>
|
||||
{/if}
|
||||
</Modal>
|
||||
|
||||
<Modal bind:showModal={showRenameFile} okFn={renameFile} cancelFn={renameFileCancel}>
|
||||
{#snippet header()}
|
||||
<h2 class="text-3xl">Datei umbenennen</h2>
|
||||
{/snippet}
|
||||
<div>
|
||||
<label for="directory" class="">Name</label>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="directory"
|
||||
id="directory"
|
||||
class="borders mt-2 mb-2 w-full"
|
||||
bind:value={newFileName}
|
||||
/>
|
||||
</div>
|
||||
{#if error.length > 0}
|
||||
<div class="text-red-700">{error}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.borders {
|
||||
border: 1px solid black;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
padding: 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -1 +1,35 @@
|
||||
export type VisitedQuestions = number[][];
|
||||
|
||||
export type Directory = {
|
||||
name: string;
|
||||
isDir: true;
|
||||
};
|
||||
|
||||
export type Ressource = {
|
||||
_id: string;
|
||||
fullpath: string;
|
||||
path: string;
|
||||
user: string;
|
||||
mimetype: string;
|
||||
name: string;
|
||||
filename: string;
|
||||
};
|
||||
|
||||
export function isDir(dir: Directory | Ressource): dir is Directory {
|
||||
return (dir as Directory).isDir === true;
|
||||
}
|
||||
|
||||
export function isRessource(ressource: Ressource | Directory): ressource is Ressource {
|
||||
return (ressource as Directory).isDir === undefined;
|
||||
}
|
||||
|
||||
export type GameId = string;
|
||||
|
||||
export type Game = {
|
||||
name: string;
|
||||
owner: string;
|
||||
_id: GameId;
|
||||
walls: WallId[];
|
||||
};
|
||||
|
||||
export type WallId = string;
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
let username: string = "";
|
||||
let username: string = $state("");
|
||||
let role: string = $state("");
|
||||
let id: string = $state("");
|
||||
|
||||
export type UserObj = {
|
||||
username: string;
|
||||
role: string;
|
||||
_id: string;
|
||||
};
|
||||
|
||||
export default {
|
||||
get username(): string {
|
||||
@@ -6,5 +14,22 @@ export default {
|
||||
},
|
||||
set username(uname: string) {
|
||||
username = uname;
|
||||
},
|
||||
get role(): string {
|
||||
return role;
|
||||
},
|
||||
set role(newrole: string) {
|
||||
role = newrole;
|
||||
},
|
||||
get id(): string {
|
||||
return id;
|
||||
},
|
||||
set id(newid: string) {
|
||||
id = newid;
|
||||
},
|
||||
set user(userobj: UserObj) {
|
||||
username = userobj.username;
|
||||
role = userobj.role;
|
||||
id = userobj._id;
|
||||
}
|
||||
};
|
||||
|
||||
5
src/lib/util.ts
Normal file
5
src/lib/util.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { env } from "$env/dynamic/public";
|
||||
|
||||
export function url(path: string) {
|
||||
return `${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}${path.startsWith("/") ? "" : "/"}${path}`;
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { afterNavigate, goto } from "$app/navigation";
|
||||
import { isAuthenticated } from "$lib/Auth.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { env } from "$env/dynamic/public";
|
||||
import RessourceManager from "$lib/RessourceManager.svelte";
|
||||
import UserSvelte from "$lib/User.svelte";
|
||||
import websocket, { SocketConnectionType } from "$lib/websocket.svelte";
|
||||
import axios from "axios";
|
||||
|
||||
let showRessourceManager = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
if (websocket.connectionType === SocketConnectionType.HOST) {
|
||||
@@ -13,17 +18,80 @@
|
||||
goto("/connected/display");
|
||||
}
|
||||
});
|
||||
|
||||
function logout() {
|
||||
cookieStore
|
||||
.delete("jeopardytoken")
|
||||
.then(() => {
|
||||
goto("/login");
|
||||
})
|
||||
.catch((e) => {
|
||||
alert("Logout fehlgeschlagen!");
|
||||
goto("/login");
|
||||
});
|
||||
}
|
||||
|
||||
async function logoutFromAllDevices() {
|
||||
axios
|
||||
.post(
|
||||
`${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}/user/logout`,
|
||||
{},
|
||||
{
|
||||
withCredentials: true
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
logout();
|
||||
})
|
||||
.catch(() => {
|
||||
alert("Logout fehlgeschlagen!");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-full flex-col">
|
||||
<h1 class="m-4 mb-8 text-7xl font-bold">Jeopardy</h1>
|
||||
<div class="ms-4 me-4 flex items-center gap-4">
|
||||
<h1 class="text-7xl font-bold">Jeopardy</h1>
|
||||
<div class="grow"></div>
|
||||
{#if UserSvelte.role === "admin"}
|
||||
<button type="button" class="btn" onclick={() => goto("/admin")}>Administration</button>
|
||||
{/if}
|
||||
<button type="button" class="btn" onclick={() => (showRessourceManager = true)}
|
||||
>Ressourcen</button
|
||||
>
|
||||
<button type="button" class="btn" onclick={() => goto("/editor")}>Editor</button>
|
||||
<button type="button" class="btn" onclick={() => goto("/settings")}>Einstellungen</button>
|
||||
<button type="button" class="btn" onclick={logoutFromAllDevices}>Logout</button>
|
||||
<div class="btn profile ps-2 pe-2">
|
||||
<i class="fa-regular fa-user"></i>
|
||||
{UserSvelte.username}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex h-full grow items-center justify-around p-4">
|
||||
<button class="btn m-2 h-1/2 w-1/2 text-5xl" onclick={websocket.connectAsHost}
|
||||
>Connect as Host</button
|
||||
>Spiel hosten</button
|
||||
>
|
||||
<button class="btn m-2 h-1/2 w-1/2 text-5xl" onclick={websocket.connectAsDisplay}
|
||||
>Connect as Display</button
|
||||
>Spiel darstellen</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RessourceManager
|
||||
bind:show={showRessourceManager}
|
||||
ok={(res) => {
|
||||
console.log(res);
|
||||
}}
|
||||
></RessourceManager>
|
||||
|
||||
<style>
|
||||
.profile {
|
||||
border-color: gray;
|
||||
}
|
||||
|
||||
.profile:hover {
|
||||
background-color: unset !important;
|
||||
cursor: unset !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
350
src/routes/admin/+page.svelte
Normal file
350
src/routes/admin/+page.svelte
Normal file
@@ -0,0 +1,350 @@
|
||||
<script lang="ts">
|
||||
import { afterNavigate, goto } from "$app/navigation";
|
||||
import type { UserObj } from "$lib/User.svelte";
|
||||
import { env } from "$env/dynamic/public";
|
||||
import axios from "axios";
|
||||
import { onMount } from "svelte";
|
||||
import Modal from "$lib/Modal.svelte";
|
||||
import UserSvelte from "$lib/User.svelte";
|
||||
|
||||
let users: UserObj[] = $state([]);
|
||||
let roles: string[] = $state([]);
|
||||
|
||||
let showAddUser = $state(false);
|
||||
let addUserName = $state("");
|
||||
|
||||
let showDeleteUser = $state(false);
|
||||
let showResetPassword = $state(false);
|
||||
let showChangeRole = $state(false);
|
||||
let selectedUser: UserObj | undefined = $state(undefined);
|
||||
let roleToChange: string = $state("");
|
||||
|
||||
let error = $state("");
|
||||
|
||||
async function loadUsers() {
|
||||
console.log("loading users");
|
||||
return axios
|
||||
.get(
|
||||
`${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}/admin/user/list`,
|
||||
{
|
||||
withCredentials: true
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
users = response.data;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadRoles() {
|
||||
console.log("loading roles");
|
||||
return axios
|
||||
.get(
|
||||
`${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}/admin/roles`,
|
||||
{
|
||||
withCredentials: true
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
roles = response.data;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
}
|
||||
|
||||
afterNavigate(() => {
|
||||
if (UserSvelte.role !== "admin") goto("/");
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
if (UserSvelte.role !== "admin") goto("/");
|
||||
loadRoles().then(loadUsers);
|
||||
});
|
||||
|
||||
async function addUserOk(): Promise<boolean> {
|
||||
error = "";
|
||||
if (addUserName.length <= 0) {
|
||||
error = "Gib einen Nutzernamen ein";
|
||||
return false;
|
||||
}
|
||||
|
||||
return axios
|
||||
.put(
|
||||
`${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}/admin/user`,
|
||||
{
|
||||
username: addUserName
|
||||
},
|
||||
{
|
||||
withCredentials: true
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
alert(`Passwort: ${response.data.password}`);
|
||||
return true;
|
||||
} else {
|
||||
throw false;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
error = "Etwas ist schief gelaufen";
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteUserOk(): Promise<boolean> {
|
||||
error = "";
|
||||
|
||||
if (selectedUser === undefined) {
|
||||
error = "Etwas ist schief gelaufen. Kein Nutzer zum Löschen definiert.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return axios
|
||||
.delete(
|
||||
`${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}/admin/user`,
|
||||
{
|
||||
withCredentials: true,
|
||||
data: {
|
||||
userid: selectedUser._id
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
return true;
|
||||
})
|
||||
.catch(() => {
|
||||
error = "Nutzer konnte nicht gelöscht werden";
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
async function resetPasswordOk(): Promise<boolean> {
|
||||
error = "";
|
||||
|
||||
if (selectedUser === undefined) {
|
||||
error = "Etwas ist schief gelaufen. Kein Nutzer zum Löschen definiert.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return axios
|
||||
.post(
|
||||
`${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}/admin/user/resetpw`,
|
||||
{
|
||||
userid: selectedUser._id
|
||||
},
|
||||
{
|
||||
withCredentials: true
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
alert(`Passwort: ${response.data.password}`);
|
||||
return true;
|
||||
} else {
|
||||
throw false;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
error = "Etwas ist schief gelaufen";
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
async function changeRoleOk(): Promise<boolean> {
|
||||
error = "";
|
||||
|
||||
if (selectedUser === undefined) {
|
||||
error = "Etwas ist schief gelaufen. Kein Nutzer zum Löschen definiert.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (roleToChange === selectedUser.role) {
|
||||
error = `${selectedUser.username} hat bereits die Rolle ${roleToChange}. Wähle eine andere Rolle aus.`;
|
||||
return false;
|
||||
}
|
||||
|
||||
return axios
|
||||
.post(
|
||||
`${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}/admin/user/changerole`,
|
||||
{
|
||||
userid: selectedUser._id,
|
||||
role: roleToChange
|
||||
},
|
||||
{
|
||||
withCredentials: true
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
return true;
|
||||
} else {
|
||||
throw false;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
error = "Etwas ist schief gelaufen";
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function addUserCancel() {
|
||||
error = "";
|
||||
addUserName = "";
|
||||
}
|
||||
|
||||
function selectedUserCancel() {
|
||||
error = "";
|
||||
selectedUser = undefined;
|
||||
roleToChange = "";
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center">
|
||||
<h2 class="text-4xl font-bold">Administration</h2>
|
||||
<div class="grow"></div>
|
||||
<button type="button" class="btn" onclick={() => goto("/")}>Zurück</button>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each users as user (user._id)}
|
||||
<div class="flex justify-between gap-4 rounded-md border-1 p-2">
|
||||
<div class="shrink-0 grow-0 text-center align-middle">{user._id}</div>
|
||||
<div>{user.username}</div>
|
||||
<div>{user.role}</div>
|
||||
<div class="flex gap-2">
|
||||
<!-- svelte-ignore a11y_consider_explicit_label -->
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
disabled={user._id === UserSvelte.id}
|
||||
onclick={() => {
|
||||
selectedUser = user;
|
||||
roleToChange = selectedUser.role;
|
||||
showChangeRole = true;
|
||||
}}><i class="fa-solid fa-user"></i> Rolle ändern</button
|
||||
>
|
||||
<!-- svelte-ignore a11y_consider_explicit_label -->
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
disabled={user._id === UserSvelte.id}
|
||||
onclick={() => {
|
||||
selectedUser = user;
|
||||
showResetPassword = true;
|
||||
}}
|
||||
><i class="fa-solid fa-arrow-rotate-right"></i> Passwort zurücksetzen</button
|
||||
>
|
||||
<!-- svelte-ignore a11y_consider_explicit_label -->
|
||||
<button
|
||||
disabled={user._id === UserSvelte.id}
|
||||
type="button"
|
||||
class="btn border-red-600 text-red-600"
|
||||
onclick={() => {
|
||||
selectedUser = user;
|
||||
showDeleteUser = true;
|
||||
}}><i class="fa-solid fa-trash"></i></button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<button type="button" class="btn" onclick={() => (showAddUser = true)}>Nutzer hinzufügen</button
|
||||
>
|
||||
</div>
|
||||
|
||||
<Modal bind:showModal={showAddUser} okFn={addUserOk} cancelFn={addUserCancel} oncloseFn={loadUsers}>
|
||||
{#snippet header()}
|
||||
<h2 class="text-3xl">Nutzer hinzufügen</h2>
|
||||
{/snippet}
|
||||
|
||||
<div>
|
||||
<label for="username" class="">Name</label>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
id="username"
|
||||
class="borders mt-2 mb-2 w-full"
|
||||
bind:value={addUserName}
|
||||
/>
|
||||
</div>
|
||||
{#if error.length > 0}
|
||||
<div class="text-red-700">{error}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
bind:showModal={showDeleteUser}
|
||||
okFn={deleteUserOk}
|
||||
cancelFn={selectedUserCancel}
|
||||
oncloseFn={loadUsers}
|
||||
>
|
||||
{#snippet header()}
|
||||
<h2 class="text-3xl">Nutzer löschen</h2>
|
||||
{/snippet}
|
||||
|
||||
<div>Soll Nutzer {selectedUser?.username} wirklich gelöscht werden?</div>
|
||||
{#if error.length > 0}
|
||||
<div class="text-red-700">{error}</div>
|
||||
{/if}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
bind:showModal={showResetPassword}
|
||||
okFn={resetPasswordOk}
|
||||
cancelFn={selectedUserCancel}
|
||||
oncloseFn={loadUsers}
|
||||
>
|
||||
{#snippet header()}
|
||||
<h2 class="text-3xl">Passwort zurücksetzen</h2>
|
||||
{/snippet}
|
||||
|
||||
<div>
|
||||
Soll das Passwort von Nutzer {selectedUser?.username} wirklich zurückgesetzt werden?
|
||||
</div>
|
||||
{#if error.length > 0}
|
||||
<div class="text-red-700">{error}</div>
|
||||
{/if}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
bind:showModal={showChangeRole}
|
||||
okFn={changeRoleOk}
|
||||
cancelFn={selectedUserCancel}
|
||||
oncloseFn={loadUsers}
|
||||
>
|
||||
{#snippet header()}
|
||||
<h2 class="text-3xl">Rolle von {selectedUser?.username} ändern</h2>
|
||||
{/snippet}
|
||||
|
||||
<div>
|
||||
{#if selectedUser}
|
||||
<select name="roles" id="role-select" class="btn w-full" bind:value={roleToChange}>
|
||||
{#each roles as role, index (index)}
|
||||
<option value={role}>{role}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
</div>
|
||||
{#if error.length > 0}
|
||||
<div class="text-red-700">{error}</div>
|
||||
{/if}
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.borders {
|
||||
border: 1px solid black;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
padding: 2px;
|
||||
}
|
||||
</style>
|
||||
162
src/routes/editor/+page.svelte
Normal file
162
src/routes/editor/+page.svelte
Normal file
@@ -0,0 +1,162 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import Modal from "$lib/Modal.svelte";
|
||||
import type { Game } from "$lib/Types";
|
||||
import { url } from "$lib/util";
|
||||
import axios from "axios";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let games: Game[] = $state([]);
|
||||
|
||||
let error = $state("");
|
||||
|
||||
let showNewGame = $state(false);
|
||||
let newGameName = $state("");
|
||||
|
||||
let showDeleteGame = $state(false);
|
||||
let gameToDelete: Game | undefined = $state();
|
||||
|
||||
async function addNewGame() {
|
||||
if (!newGameName) return false;
|
||||
return axios
|
||||
.post(url("game"), { name: newGameName }, { withCredentials: true })
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
fetchGames();
|
||||
newGameName = "";
|
||||
return true;
|
||||
} else return false;
|
||||
});
|
||||
}
|
||||
|
||||
function addNewGameCancel() {
|
||||
newGameName = "";
|
||||
}
|
||||
|
||||
async function deleteGame() {
|
||||
if (gameToDelete === undefined) return false;
|
||||
return axios
|
||||
.delete(url("/game/" + gameToDelete._id), { withCredentials: true })
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
fetchGames();
|
||||
gameToDelete = undefined;
|
||||
return true;
|
||||
} else {
|
||||
console.error("Failed to delete Game: " + response.status);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function deleteGameCancel() {
|
||||
gameToDelete = undefined;
|
||||
}
|
||||
|
||||
function fetchGames() {
|
||||
axios
|
||||
.get(url("games"), { withCredentials: true })
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
games = response.data;
|
||||
games.sort((a, b) => a.name.localeCompare(b.name));
|
||||
} else {
|
||||
console.error("Could not fetch games: " + response.status);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
fetchGames();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex h-full flex-col">
|
||||
<div class="flex items-center gap-4">
|
||||
<h1 class="m-4 mb-8 text-7xl font-bold">Editor</h1>
|
||||
<button class="btn" type="button" onclick={() => (showNewGame = true)}
|
||||
><i class="fa-solid fa-plus"></i> Neues Spiel</button
|
||||
>
|
||||
</div>
|
||||
{#if games.length > 0}
|
||||
<div class="flex flex-col space-y-4 overflow-y-auto">
|
||||
{#each games as game}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="ms-4 me-4 flex items-center justify-between rounded-xl border-2 p-2 hover:cursor-pointer hover:bg-emerald-200"
|
||||
onclick={() => {
|
||||
goto(`/editor/${game._id}`);
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{game.name}
|
||||
</div>
|
||||
<!-- svelte-ignore a11y_consider_explicit_label -->
|
||||
<button
|
||||
type="button"
|
||||
class="btn border-red-600 text-red-600"
|
||||
onclick={(event) => {
|
||||
event.stopPropagation();
|
||||
gameToDelete = game;
|
||||
showDeleteGame = true;
|
||||
}}><i class="fa-solid fa-trash"></i></button
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex h-full w-full grow-2 flex-col items-center justify-center">
|
||||
<div class="text-[128px] text-gray-300"><i class="fa-solid fa-database"></i></div>
|
||||
<div class="text-[24px] text-gray-500 select-none">Noch keine Spiele vorhanden</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Modal bind:showModal={showNewGame} okFn={addNewGame} cancelFn={addNewGameCancel}>
|
||||
{#snippet header()}
|
||||
<h2 class="text-3xl">Neues Spiel</h2>
|
||||
{/snippet}
|
||||
<div>
|
||||
<label for="directory" class="">Name</label>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="directory"
|
||||
id="directory"
|
||||
class="borders mt-2 mb-2 w-full"
|
||||
bind:value={newGameName}
|
||||
/>
|
||||
</div>
|
||||
{#if error.length > 0}
|
||||
<div class="text-red-700">{error}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal bind:showModal={showDeleteGame} okFn={deleteGame} cancelFn={deleteGameCancel}>
|
||||
{#snippet header()}
|
||||
<h2 class="text-3xl">Spiel löschen</h2>
|
||||
{/snippet}
|
||||
|
||||
<div>Soll das Spiel {gameToDelete?.name} wirklich gelöscht werden?</div>
|
||||
{#if error.length > 0}
|
||||
<div class="text-red-700">{error}</div>
|
||||
{/if}
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.borders {
|
||||
border: 1px solid black;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
padding: 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { env } from "$env/dynamic/public";
|
||||
import UserState from "$lib/User.svelte";
|
||||
import UserState, { type UserObj } from "$lib/User.svelte";
|
||||
import axios from "axios";
|
||||
|
||||
let username = $state("");
|
||||
@@ -20,7 +20,8 @@
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
UserState.username = response.data;
|
||||
UserState.user = response.data as UserObj;
|
||||
console.log(UserState.id, UserState.username, UserState.role);
|
||||
goto("/");
|
||||
}
|
||||
})
|
||||
|
||||
92
src/routes/settings/+page.svelte
Normal file
92
src/routes/settings/+page.svelte
Normal file
@@ -0,0 +1,92 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { env } from "$env/dynamic/public";
|
||||
import UserState, { type UserObj } from "$lib/User.svelte";
|
||||
import axios from "axios";
|
||||
|
||||
let oldpassword = $state("");
|
||||
let newpassword = $state("");
|
||||
let reppassword = $state("");
|
||||
let error = $state("");
|
||||
|
||||
async function changePassword() {
|
||||
if (newpassword !== reppassword) {
|
||||
error = "Passwörter stimmen nicht überein.";
|
||||
} else {
|
||||
error = "";
|
||||
}
|
||||
|
||||
axios
|
||||
.post(
|
||||
`${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}/user/changepw`,
|
||||
{
|
||||
old: oldpassword,
|
||||
new: newpassword
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
goto("/login");
|
||||
} else {
|
||||
error = "Passwort ändern fehlgeschlagen";
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
error = "Passwort ändern fehlgeschlagen";
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center">
|
||||
<h2 class="text-4xl font-bold">Einstellungen</h2>
|
||||
<div class="grow"></div>
|
||||
<button type="button" class="btn" onclick={() => goto("/")}>Zurück</button>
|
||||
</div>
|
||||
<div class="rounded-md border-1 p-4">
|
||||
<h4 class="font-bold">Passwort ändern</h4>
|
||||
<div>
|
||||
<label for="oldpassword" class="">Altes Passwort</label>
|
||||
<input
|
||||
type="password"
|
||||
name="oldpassword"
|
||||
id="oldpassword"
|
||||
class="borders me-4 mt-2 mb-4"
|
||||
bind:value={oldpassword}
|
||||
/>
|
||||
<label for="newpassword" class="">Neues Passwort</label>
|
||||
<input
|
||||
type="password"
|
||||
name="newpassword"
|
||||
id="newpassword"
|
||||
class="borders me-4 mt-2 mb-4"
|
||||
bind:value={newpassword}
|
||||
/>
|
||||
<label for="reppassword" class="">Neues Passwort wiederholen</label>
|
||||
<input
|
||||
type="password"
|
||||
name="reppassword"
|
||||
id="reppassword"
|
||||
class="borders me-4 mt-2 mb-4"
|
||||
bind:value={reppassword}
|
||||
/>
|
||||
</div>
|
||||
<button type="button" class="btn mb-2 w-fit ps-4 pe-4" onclick={changePassword}
|
||||
>Passwort ändern</button
|
||||
>
|
||||
{#if error.length > 0}
|
||||
<div class="text-red-700">{error}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.borders {
|
||||
border: 1px solid black;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
font-size: larger;
|
||||
padding: 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { sveltekit } from "@sveltejs/kit/vite";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit()]
|
||||
plugins: [sveltekit(), tailwindcss()]
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user