f047_Edit-der-Namen #15

Closed
jared wants to merge 11 commits from f047_Edit-der-Namen into development
18 changed files with 598 additions and 210 deletions

View File

@@ -1,4 +1,15 @@
<!DOCTYPE html>
<html lang="de">
<head>
</head>
<body>
<h1>Du wurdest automatisch ausgeloggt</h1>
<p>Lösche deine Cookies aus dem Browser und logge dich neu ein</p>
<p>Code %sveltekit.status%</p>
<p>%sveltekit.error.message%</p>
</body>
</html>

View File

@@ -0,0 +1,93 @@
<script lang="ts">
import Edit from '$lib/icons/Edit.svelte';
import Trash from '$lib/icons/Trash.svelte';
import { tick } from 'svelte';
interface ListItem {
name: string;
token?: string;
// add other properties as needed
}
let {
list,
editedName = $bindable(),
currentName,
mina marked this conversation as resolved Outdated
Outdated
Review

Der Kommentar könnte m.M.n. raus

Der Kommentar könnte m.M.n. raus
onSave = () => {},
onDelete = () => {}
} = $props();
let names = list.map((l: ListItem) => l.name);
let localName = $state(currentName);
mina marked this conversation as resolved Outdated
Outdated
Review

Bitte Console.log entfernen.

Bitte Console.log entfernen.
let wasCancelled = $state(false);
let error: string = $derived(validateName(localName));
let manualError = $state('');
mina marked this conversation as resolved Outdated
Outdated
Review

Typo editSart to editStart

Typo editSart to editS**t**art
let isEditing = $state(false);
let inputRef: HTMLInputElement;
function validateName(name: string) {
jared marked this conversation as resolved Outdated
Outdated
Review

The signature '(): EventDispatcher<{ editSart: {}; save: {}; delete: {}; cancel: void; }>' of 'createEventDispatcher' is deprecated.

Mittlerweile wird ein Event mittels Callback function dispatched. Gucke ggf. noch einmal in die Docs.

Zum Beispiel:

<script type="ts"> const props: { onClick(): void; onExplode(name: string, age: number): void; } = $props(); </script>

<MyComponent
onClick={() => alert('clicked')}
onExplode={(name, age) => alert(name + ' ' + age)}
/>

The signature '(): EventDispatcher<{ editSart: {}; save: {}; delete: {}; cancel: void; }>' of 'createEventDispatcher' is deprecated. Mittlerweile wird ein Event mittels Callback function dispatched. Gucke ggf. noch einmal in die Docs. Zum Beispiel: <script type="ts"> const props: { onClick(): void; onExplode(name: string, age: number): void; } = $props(); </script> <MyComponent onClick={() => alert('clicked')} onExplode={(name, age) => alert(name + ' ' + age)} />
Outdated
Review

Ich habe es angepasst, es musste einiges verändert werden, bitte schaue es dir nochmal an.

Ich habe es angepasst, es musste einiges verändert werden, bitte schaue es dir nochmal an.
const trimmed = name.trim();
jared marked this conversation as resolved Outdated
Outdated
Review

Villt die Variable noch besser bennennen. InternalValue oder oldValue, kann halt alles sein...

Villt die Variable noch besser bennennen. InternalValue oder oldValue, kann halt alles sein...
Outdated
Review

Allgemein, weil es eine Komponente ist, die vielseitig einsetzbar. Es geht darum quasi den Startwert? zu speichern, für den Abbruch und den aktuellen Wert, um ein editieren in Gang zu bringen. Man könnte es auch cancelValue und EditValue nennen? Vielleicht besser?

Allgemein, weil es eine Komponente ist, die vielseitig einsetzbar. Es geht darum quasi den Startwert? zu speichern, für den Abbruch und den aktuellen Wert, um ein editieren in Gang zu bringen. Man könnte es auch cancelValue und EditValue nennen? Vielleicht besser?
Outdated
Review

Naja, der Begriff Value bringt halt nicht, weil das quasi immer ein value ist. Daher so explizit bennen wie es geht

Naja, der Begriff Value bringt halt nicht, weil das quasi immer ein value ist. Daher so explizit bennen wie es geht
if (!trimmed) {
return 'Name darf nicht leer sein.';
}
const duplicate = list.some(
mina marked this conversation as resolved Outdated
Outdated
Review

Wird im weiteren Verlauf nicht verwendet, daher löschen

Wird im weiteren Verlauf nicht verwendet, daher löschen
(item: ListItem) => item.name === trimmed && item.name !== currentName
);
if (duplicate) return 'Name existiert bereits.';
return '';
}
function commitIfValid() {
if (!error && !wasCancelled && localName != currentName) {
editedName = localName.trim();
onSave(editedName, currentName);
} else {
localName = currentName;
resetEdit();
}
}
function resetEdit() {
wasCancelled = false;
manualError = '';
inputRef?.blur();
isEditing = false;
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Enter') {
event.preventDefault();
commitIfValid();
} else if (event.key === 'Escape') {
event.preventDefault();
localName = currentName;
resetEdit();
}
}
async function startEdit() {
isEditing = true;
await tick();
inputRef?.focus();
}
</script>
<div>
<input
bind:this={inputRef}
bind:value={localName}
onblur={commitIfValid}
onkeydown={handleKeydown}
/>
<button onclick={startEdit}><Edit /></button>
<button onclick={() => onDelete(currentName)}><Trash /></button>
{#if manualError || error}
<p style="color: red;">{manualError || error}</p>
{/if}
</div>

View File

@@ -1,3 +1,3 @@
import { readFileSync } from 'fs';
export default JSON.parse(readFileSync('./config.json').toString());
export default JSON.parse(readFileSync('./config_prod.json').toString());

View File

@@ -0,0 +1,27 @@
/**
*
* @param oldValue
* @param inputValue
* @param options
* @returns
*/
export function validateNameInput(oldValue:string, inputValue: string, options: { minLength?: number; existingNames?: string[] }) {
const errors: string[] = [];
Review

Grundsätzlich wird hier der Value in diesem Fall ein Name validiert. Es ist keine generische validateInput Funktion. Daher sollte sie entsprechend benannt werden. Darüber hinaus wird sie nur in EditableItem verwendet. Daher ist ggf. ein Auslagern nicht sinnvoll, oder soll sie noch wo anders verwendet werden?

Grundsätzlich wird hier der Value in diesem Fall ein Name validiert. Es ist keine generische validateInput Funktion. Daher sollte sie entsprechend benannt werden. Darüber hinaus wird sie nur in EditableItem verwendet. Daher ist ggf. ein Auslagern nicht sinnvoll, oder soll sie noch wo anders verwendet werden?
Review

Werde ich nochmal überarbeiten

Werde ich nochmal überarbeiten
if (!inputValue.trim()) errors.push('Feld darf nicht leer sein');
if (options.existingNames?.includes(inputValue) && oldValue !== inputValue)
errors.push('Name existiert bereits');
return errors;
}
// siehe suche: fail
// Button
// BaseInputField
// fetch siehe b038
// api/list/vorgang/tatort/server.ts
// api/list/vorgang/server.ts

View File

@@ -1,10 +1,15 @@
<script>
let classNames = '';
export { classNames as class };
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class=" w-6 h-6"
class=" w-6 h-6 {classNames}"
>
<path
stroke-linecap="round"

Before

Width:  |  Height:  |  Size: 483 B

After

Width:  |  Height:  |  Size: 571 B

View File

@@ -19,7 +19,7 @@ export const checkIfExactDirectoryExists = (dir: string): Promise<boolean> => {
});
};
export const getContentOfTextObject = async (bucket: string, objPath: string) => {
export const getContentOfTextObject = async (bucket: string, objPath: string) => { //Was kriege ich da raus?
const res = await client.getObject(bucket, objPath);
const text = await new Response(res).text();

View File

@@ -2,9 +2,9 @@ import { getListOfVorgänge } from '$lib/server/vorgangService';
import type { PageServerLoad } from '../../(token-based)/view/$types';
export const load: PageServerLoad = async () => {
const caseList = await getListOfVorgänge();
const vorgangList = getListOfVorgänge();
return {
caseList
};
return {
vorgangList
};
};

View File

@@ -1,11 +1,21 @@
<script lang="ts">
import Trash from '$lib/icons/Trash.svelte';
import Folder from '$lib/icons/Folder.svelte';
import type { PageData } from '../$types';
import EditableItem from '$lib/components/EditableItem.svelte';
import { invalidate } from '$app/navigation';
export let data: PageData;
interface ListItem {
name: string;
token?: string;
Review

Man könnte einen Ordner für type anlegen src/lib/types/...

Man könnte einen Ordner für type anlegen src/lib/types/...
Review

s.u.

s.u.
// add other properties as needed
}
const caseList = data.caseList;
let { data } = $props();
const caseList: ListItem[] = $state(data.caseList);
let names: string[] = $state(caseList.map((l) => l.name));
let editedName: string = $state('');
let currentName: string = $state('');
async function delete_item(ev: Event) {
let delete_item = window.confirm('Bist du sicher?');
@@ -14,7 +24,7 @@
const target = ev.currentTarget as HTMLElement | null;
if (!target) return;
let filename = target.id.split('del__')[1];
// delete request
// --------------
@@ -36,6 +46,51 @@
}
}
}
async function handleUpdateCase(newName: string, oldName: string) {
try {
const res = await fetch(`/api/list/${oldName}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ oldName, newName })
});
if (!res.ok) {
const msg = await res.text();
console.error('❌ Fehler beim Speichern Update:', msg);
} else {
console.log('✅ Erfolgreich gespeichert:', newName);
await invalidate('');
currentName = newName;
}
} catch (err) {
console.error('⚠️ Netzwerkfehler:', err);
}
}
async function handleDelete(vorgang: string) {
let url = `/api/list/${vorgang}`;
try {
const res = await fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ vorgang })
});
if (!res.ok) {
const msg = await res.text();
console.error('❌ Fehler beim Löschen:', msg);
} else {
console.log('🗑️ Erfolgreich gelöscht:', vorgang);
}
} catch (err) {
console.error('⚠️ Netzwerkfehler beim Löschen:', err);
}
}
</script>
<div class="-z-10 bg-white">
@@ -44,29 +99,32 @@
</div>
<div class="mx-auto flex justify-center max-w-7xl h-full">
<ul role="list" class="divide-y divide-gray-100">
{#each caseList as item}
{#each caseList as item, i}
<li>
<a href="/list/{item.name}?token={item.token}" class="flex justify-between gap-x-6 py-5">
<div class="flex gap-x-4">
<!-- Ordner -->
<div class="flex gap-x-4">
<!-- Ordner -->
<a
href="/list/{currentName}?token={item.token}"
class=" flex justify-between gap-x-6 py-5"
>
<Folder />
</a>
{#if data.user.admin}
<div class="min-w-0 flex-auto">
<span class="text-sm font-semibold leading-6 text-gray-900">{item.name}</span>
<!-- Delete button -->
<button
style="padding: 2px"
id="del__{item.name}"
on:click|preventDefault={delete_item}
aria-label="Vorgang {item.name} löschen"
>
<Trash />
</button>
<EditableItem
list={caseList}
bind:editedName={names[i]}
currentName={item.name}
onSave={handleUpdateCase}
onDelete={handleDelete}
></EditableItem>
</div>
</div>
<div class="hidden sm:flex sm:flex-col sm:items-end">
<p class="text-sm leading-6 text-gray-900">Vorgang</p>
</div>
</a>
{:else}
<div class="hidden sm:flex sm:flex-col sm:items-end">
<p class="text-sm leading-6 text-gray-900">Vorgang</p>
</div>
{/if}
</div>
</li>
{/each}
</ul>

View File

@@ -0,0 +1,23 @@
export async function load({ fetch }) {
//const caseToken = url.searchParams.get('token');
const adminRes = await fetch(`/api/user`)
const user = await adminRes.json()
const res = await fetch(`/api/list`, {
/* headers: {
Authorization: `Bearer ${caseToken}`
} */
});
const data = await res.json();
// console.log("Kay", data.caseList);
const caseList = data.caseList
return {
caseList, //{name, token}
user //für Abfrage ob eingeloggt
};
};

View File

@@ -1,4 +1,5 @@
<script lang="ts">
//Kann komplett gelöscht werden
import Alert from '$lib/components/Alert.svelte';
import Button from '$lib/components/Button.svelte';
import Modal from '$lib/components/Modal/Modal.svelte';
@@ -75,19 +76,14 @@
</div>
<label for="code">
<span >Zugangscode (optional) </span>
<span>Zugangscode (optional) </span>
</label>
<div class="mt-2">
<div
>
<input
type="text"
id="code"
/>
<div>
<input type="text" id="code" />
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,14 +0,0 @@
import { getVorgangByCaseId } from '$lib/server/vorgangService';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params, url }) => {
const caseId = params.vorgang;
const caseToken = url.searchParams.get('token');
const crimesList = await getVorgangByCaseId(caseId);
return {
crimesList,
caseToken
};
};

View File

@@ -1,64 +1,59 @@
<script lang="ts">
import shortenFileSize from '$lib/helper/shortenFileSize';
import { page } from '$app/stores';
import timeElapsed from '$lib/helper/timeElapsed';
import Alert from '$lib/components/Alert.svelte';
import Button from '$lib/components/Button.svelte';
import Modal from '$lib/components/Modal/Modal.svelte';
import ModalTitle from '$lib/components/Modal/ModalTitle.svelte';
import ModalContent from '$lib/components/Modal/ModalContent.svelte';
import ModalFooter from '$lib/components/Modal/ModalFooter.svelte';
import EditableItem from '$lib/components/EditableItem.svelte';
import Cube from '$lib/icons/Cube.svelte';
import Edit from '$lib/icons/Edit.svelte';
import Trash from '$lib/icons/Trash.svelte';
import shortenFileSize from '$lib/helper/shortenFileSize.js';
import timeElapsed from '$lib/helper/timeElapsed.js';
import { invalidate } from '$app/navigation';
import { page } from '$app/stores';
/** export let data; */
/** @type {import('./$types').PageData} */
export let data;
//Seite für die Tatort-Liste
let { data } = $props();
Outdated
Review

s. o.

s. o.
Outdated
Review

Ja, das können wir mal im Daily ansprechen, ich habe ein Ticket daraus gemacht.

Ja, das können wir mal im Daily ansprechen, ich habe ein Ticket daraus gemacht.
console.log('tatorte: debug ', data);
interface ListItem {
name: string;
size: number;
lastModified: string | number | Date;
size?: number;
lastModified?: string | number | Date | undefined;
show_button?: boolean;
// add other properties as needed
}
const crimesList: ListItem[] = data.crimesList;
const token: string = data.caseToken;
const crimesList: ListItem[] = $state(data.crimesList);
const token: string | null = data.caseToken;
let vorgang = data.caseId;
/* export let vorgang = $page.params.vorgang; */
let open = false;
$: open;
let inProgress = false;
$: inProgress;
let err = false;
$: err;
//Variabeln für EditableItem
let names: string[] = $state(crimesList.map((l) => l.name));
let editedName: string = $state('');
let currentName: string = $state('');
let open = $state(false);
let inProgress = $state(false);
let err = $state(false);
let editingId: number;
// let admin = data?.user?.admin;
let admin = true;
let rename_input;
$: rename_input;
$effect(() => {
console.log('rename_input hat sich geändert:', rename_input);
});
function uploadSuccessful() {
open = false;
}
function defocus_element(i: number) {
let item = crimesList[i];
let text_field_id = `label__${item.name}`;
let text_field = document.getElementById(text_field_id);
if (text_field) {
text_field.setAttribute('contenteditable', 'false');
text_field.textContent = item.name;
}
// reshow button
crimesList[i].show_button = true;
return;
}
async function handle_input(ev: KeyboardEvent, i: number) {
/* async function handle_input(ev: KeyboardEvent, i: number) {
let item = crimesList[i];
if (ev.key == 'Escape') {
let text_field_id = `label__${item.name}`;
@@ -127,125 +122,111 @@
return;
}
} */
async function handleSave(newName: string, oldName: string) {
console.log('Eltern, speichern erfolgreich', newName, oldName);
try {
const res = await fetch(`/api/list/${vorgang}/${oldName}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ vorgang, oldName, newName })
});
const clone = res.clone();
const data = await res.json();
let message = data.message;
let error = !data.success;
console.log('Tatort Update: ', message);
if (!res.ok) {
const msg = await clone.text();
console.error('❌ Fehler beim Speichern:', msg);
} else {
console.log('✅ Erfolgreich gespeichert:', newName);
await invalidate('');
currentName = newName;
}
} catch (err) {
console.error('⚠️ Netzwerkfehler:', err);
}
}
async function handleDelete(tatort: string) {
let url = new URL($page.url);
url.pathname += `/${tatort}`;
console.log('Delete tatort: ', `/api${url.pathname}`, url.pathname);
try {
const res = await fetch(`/api${url.pathname}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ vorgang, tatort })
});
if (!res.ok) {
const msg = await res.text();
console.error('❌ Fehler beim Löschen:', msg);
} else {
console.log('🗑️ Erfolgreich gelöscht:', url.pathname);
}
} catch (err) {
console.error('⚠️ Netzwerkfehler beim Löschen:', err);
}
}
</script>
<div class="-z-10 bg-white">
<div class="flex flex-col items-center justify-center w-full">
<h1 class="text-xl">Vorgang {$page.params.vorgang}</h1>
<h1 class="text-xl">Vorgang {vorgang}</h1>
</div>
<div class="mx-auto flex justify-center max-w-7xl h-full">
<ul class="divide-y divide-gray-100">
{#each crimesList as item, i}
<li>
<a
href="/view/{$page.params.vorgang}/{item.name}?token={token}"
class=" flex justify-between gap-x-6 py-5"
aria-label="zum 3D-modell"
>
<div class=" flex gap-x-4">
<div class=" flex gap-x-4">
<a
href="/view/{$page.params.vorgang}/{currentName}?token={token}"
class=" flex justify-between gap-x-6 py-5"
aria-label="zum 3D-modell"
>
<Cube />
<div class="min-w-0 flex-auto">
{#if data?.user?.admin}
<span
id="label__{item.name}"
class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1"
contenteditable={!item.show_button}
role="textbox"
tabindex="0"
aria-label="Dateiname bearbeiten"
on:focusout={() => {
defocus_element(i);
}}
on:keydown|stopPropagation={// event needed to identify ID
// TO-DO: check if event is needed or if index is sufficient
async (ev) => {
handle_input(ev, i);
}}>{item.name}</span
>
{#if item.show_button}
<button
style="padding: 2px"
id="edit__{item.name}"
aria-label="Datei umbenennen"
on:click|preventDefault={(ev) => {
let text_field_id = `label__${item.name}`;
let text_field = document.getElementById(text_field_id);
if (text_field) {
text_field.setAttribute('contenteditable', 'true');
text_field.focus();
text_field.textContent = '';
}
// hide button
item.show_button = false;
}}
>
<Edit />
</button>
{/if}
<button
style="padding: 2px"
id="del__{item.name}"
on:click|preventDefault={async (ev) => {
let delete_item = window.confirm('Bist du sicher?');
if (delete_item) {
// bucket: tatort, name: <vorgang>/item-name
let vorgang = $page.params.vorgang;
let filename = '';
if (ev && ev.currentTarget && (ev.currentTarget as HTMLElement).id) {
filename = (ev.currentTarget as HTMLElement).id.split('del__')[1];
}
// delete request
// --------------
let url = new URL($page.url);
url.pathname += `/${filename}`;
try {
const response = await fetch(`/api${url.pathname}`, { method: 'DELETE' });
if (response.status == 204) {
setTimeout(() => {
window.location.reload();
}, 500);
}
} catch (error) {
if (error instanceof Error) {
console.log(error.message);
} else {
console.log(error);
}
}
}
}}
aria-label="Datei löschen"
>
<Trash />
</button>
{:else}
<span class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1"
>{item.name}</span
>
{/if}
</a>
<div class="min-w-0 flex-auto">
{#if admin}
<EditableItem
list={crimesList}
bind:editedName={names[i]}
currentName={item.name}
onSave={handleSave}
onDelete={handleDelete}
></EditableItem>
{:else}
<span class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1"
>{item.name}</span
>
{/if}
{#if item.size}
<p class="mt-1 truncate text-xs leading-5 text-gray-500">
{shortenFileSize(item.size)}
</p>
</div>
{/if}
</div>
<div class="hidden sm:flex sm:flex-col sm:items-end">
<p class="text-sm leading-6 text-gray-900">3D Tatort</p>
</div>
<div class="hidden sm:flex sm:flex-col sm:items-end">
<p class="text-sm leading-6 text-gray-900">3D Tatort</p>
{#if item.lastModified}
<p class="mt-1 text-xs leading-5 text-gray-500">
Zuletzt geändert <time datetime="2023-01-23T13:23Z"
>{timeElapsed(new Date(item.lastModified))}</time
>
</p>
</div>
</a>
{/if}
</div>
</li>
{/each}
</ul>

View File

@@ -0,0 +1,34 @@
// load für die Tatort-Liste
export async function load({ fetch, params, url }) {
const caseId = params.vorgang;
const caseToken = url.searchParams.get('token');
const adminRes = await fetch(`/api/user`)
const user = await adminRes.json()
const res = await fetch(`/api/list/${caseId}`, {
headers: {
Authorization: `Bearer ${caseToken}`
}
});
const data = await res.json();
const crimesList = data.crimesList
return {
caseId,
crimesList,
caseToken,
user
};
};

View File

@@ -1,4 +1,4 @@
import { client } from '$lib/minio';
/* import { client } from '$lib/minio';
import { json } from '@sveltejs/kit';
@@ -33,3 +33,4 @@ export async function PUT({ request }: {request: Request}) {
return json({ success: 'success' }, { status: 200 });
};
*/

View File

@@ -0,0 +1,27 @@
import { getListOfVorgänge } from "$lib/server/vorgangService";
import { json } from "@sveltejs/kit";
//Get für die Vorgangsliste mit token, daher authorized Abfrage
export async function GET({locals, url, request}) {
/* if(!locals.user?.admin){
return new Response('Unauthorized',{status: 401})
}
const urlToken = url.searchParams.get('token');
const caseToken = request.headers.get('Authorization')?.replace('Bearer ', '');
if(!(urlToken || caseToken)) return new Response('Unauthorized', { status: 401 });
*/
let caseList;
try {
caseList = await getListOfVorgänge();
} catch (err) {
console.error('Fehler beim Laden der Liste:', err);
}
if(!caseList) return new Response(`Keine Liste vorhanden ${caseList}`, {status: 404})
const data = {caseList}
return json(data)
}

View File

@@ -1,24 +1,120 @@
import { client } from '$lib/minio';
import { BUCKET, client } from '$lib/minio';
import { getVorgangByCaseId } from '$lib/server/vorgangService';
import type { RequestHandler } from '@sveltejs/kit';
import { json } from '@sveltejs/kit';
export async function DELETE({ params }) {
const vorgang = params.vorgang;
// Seite für die Vorgangs-Liste
const object_list = await new Promise((resolve, reject) => {
const res = [];
const items_str = client.listObjects('tatort', vorgang, true);
//Get für die Tatortliste mit token, daher authorized Abfrage
export async function GET({locals,request, params, url}) {
if(!locals.user?.admin){
return new Response('Unauthorized',{status: 401})
}
const caseId = params.vorgang;
const urlToken = url.searchParams.get('token');
const caseToken = request.headers.get('Authorization')?.replace('Bearer ', '');
if(!(urlToken || caseToken)) return new Response('Unauthorized', { status: 401 });
const crimesList = await getVorgangByCaseId(caseId);
const data = {
caseId,
crimesList,
caseToken
}
return json(data)
}
// rename operation
export const PUT: RequestHandler= async ({request, locals}) => {
if(!locals.user?.admin){
return new Response('Forbidden', { status: 403 });
}
const { oldName, newName} = await request.json();
if (!newName || !newName.trim()) {
return new Response('Ungültiger Name', { status: 400 });
}
try {
const stat = await client.statObject(BUCKET, newName).catch(()=> null)
if(stat){
return json({ error: 'Datei mit dem neuen Namen existiert bereits.' }, { status: 409 });
}
// 2. Kopieren
const Object_list:string[] = await new Promise((resolve, reject) => {
const res: string[] = [];
const items_str = client.listObjects(BUCKET, oldName, true);
items_str.on('data', (obj) => {
if (obj.name) res.push(obj.name);
});
items_str.on('error', reject);
items_str.on('end', async () => {
try {
for (const oldKey of res) {
const oldPath = `${oldName}/`;
const newPath = `${newName}/`;
const newKey = oldKey.replace(oldPath, newPath);
await client.copyObject(BUCKET, newKey, `/${BUCKET}/${oldKey}`);
}
resolve(res);
} catch (err) {
reject(err);
}
});
});
//3. Löschen
await client.removeObjects(BUCKET, Object_list)
const data = { success: true, message: 'Datei erfolgreich umbenannt.' }
return json(data, { status: 200 });
} catch (err) {
console.error('Fehler beim Umbenennen', err)
return json({error: `Fehler beim Umbenennen der Datei ${oldName} zu ${newName}`}, {status: 500})
}
};
export const DELETE: RequestHandler = async ({ request })=> { //body: {request}, keine params // params= de?param1=value&params2
// const vorgang = params.vorgang;
const { vorgang } = await request.json();
const object_list:string[] = await new Promise((resolve, reject) => {
const res: string[] = [];
const items_str = client.listObjects(BUCKET, vorgang, true);
items_str.on('data', (obj) => {
res.push(obj.name);
});
if(obj.name) res.push(obj.name);
});
items_str.on('error', reject);
items_str.on('error', reject);
items_str.on('end', async () => {
items_str.on('end', async () => {
resolve(res);
});
});
});
await client.removeObjects('tatort', object_list);
return new Response(null, { status: 204 });
}
return new Response(null, { status: 204 });
}

View File

@@ -1,5 +1,9 @@
import { BUCKET, client } from '$lib/minio';
import type { RequestHandler } from '@sveltejs/kit';
import { json } from '@sveltejs/kit';
// Seite für die Tatorte-Liste
export async function GET() {
const stream = client.listObjectsV2(BUCKET, '', true);
const result = new ReadableStream({
@@ -21,15 +25,51 @@ export async function GET() {
'content-type': 'text/event-stream'
}
});
}
export const PUT: RequestHandler= async ({request, locals}) => {
export async function DELETE({ request }: { request: Request }) {
const url_fragments = request.url.split('/');
const item = url_fragments.at(-1);
const vorgang = url_fragments.at(-2);
if(!locals.user?.admin){
return new Response('Forbidden', { status: 403 });
}
const {vorgang, oldName, newName} = await request.json();
if (!newName || !newName.trim()) {
return new Response('Ungültiger Name', { status: 400 });
}
const oldKey = `${vorgang}/${oldName}`;
const newKey = `${vorgang}/${newName}`;
console.log("PUT Tatorte-Liste: ", vorgang, oldKey, newKey);
try {
const stat = await client.statObject(BUCKET, newKey).catch(()=> null)
if(stat){
return json({ error: 'Datei mit dem neuen Namen existiert bereits.' }, { status: 409 });
}
// 2. Kopieren
await client.copyObject(BUCKET, newKey, `/${BUCKET}/${oldKey}`);
//3. Löschen
await client.removeObject(BUCKET, oldKey)
const data = { success: true, message: 'Datei erfolgreich umbenannt.' }
return json(data);
} catch (err) {
console.error('Fehler beim Umbenennen', err)
return json({error: 'Fehler beim Umbenennen der Datei'}, {status: 500})
}
await client.removeObject(BUCKET, `${vorgang}/${item}`);
return new Response(null, { status: 204 });
}
export const DELETE: RequestHandler = async ({ request })=> { //body: {request}, keine params // params= de?param1=value&params2
const url_fragments = request.url.split('/');
const item = url_fragments.at(-1);
const vorgang = url_fragments.at(-2);
await client.removeObject(BUCKET, `${vorgang}/${item}`);
return new Response(null, { status: 204 });
}

View File

@@ -0,0 +1,10 @@
//Rollenabfrage ob Benutzer admin ist
import { json } from "@sveltejs/kit";
export async function GET({locals}) {
const isAdmin = locals.user?.admin === true;
const data = {admin: isAdmin}
return json(data)
}