admin Berreich mit Passwort

This commit is contained in:
titver968
2025-04-16 11:53:18 +02:00
parent 10c443285d
commit 5177bce04c
10 changed files with 153 additions and 80 deletions

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@ node_modules
.wrangler .wrangler
/.svelte-kit /.svelte-kit
/build /build
/data
# OS # OS
.DS_Store .DS_Store

Binary file not shown.

View File

@@ -1,65 +1,60 @@
<script lang="ts"> <script lang="ts">
let passwort = ''; let passwort = '';
let eingeloggt = false; let eingeloggt = false;
let fehler = false; let fehler = false;
let anmeldungen = [];
async function login() { async function login() {
const res = await fetch('/api/admin/login', { const res = await fetch('/api/admin/login', {
method: 'POST', method: 'POST',
body: JSON.stringify({ passwort }), body: JSON.stringify({ passwort }),
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }
}); });
if (res.ok) { if (res.ok) {
eingeloggt = true; eingeloggt = true;
fehler = false; fehler = false;
const result = await fetch('/api/admin/anmeldungen'); } else {
anmeldungen = await result.json(); fehler = true;
} else {
fehler = true;
}
} }
}
</script>
<div class="p-6 max-w-lg mx-auto">
<div class="p-6 max-w-4xl mx-auto"> {#if !eingeloggt}
{#if !eingeloggt} <div class="space-y-4">
<div class="space-y-4"> <h1 class="text-2xl font-bold">Admin Login</h1>
<h1 class="text-2xl font-bold">Admin Login</h1> <input type="password" bind:value={passwort} placeholder="Passwort" class="input w-full" />
<input type="password" bind:value={passwort} placeholder="Passwort" class="input w-full" /> <button on:click={login} class="bg-blue-600 text-white px-4 py-2 rounded">Login</button>
<button on:click={login} class="bg-blue-600 text-white px-4 py-2 rounded">Login</button> {#if fehler}
{#if fehler} <p class="text-red-600">Falsches Passwort</p>
<p class="text-red-600">Falsches Passwort</p> {/if}
</div>
{:else}
<div class="space-y-4">
<h1 class="text-2xl font-bold mb-4">Admin-Bereich</h1>
<div class="flex flex-col gap-4">
<a href="/admin/anmeldungen" class="bg-blue-600 text-white px-4 py-3 rounded text-center hover:bg-blue-700">
📝 Anmeldungen anzeigen
</a>
<a href="/admin/dienststellen" class="bg-green-600 text-white px-4 py-3 rounded text-center hover:bg-green-700">
🏢 Dienststellen verwalten
</a>
</div> </div>
</div> <button
{:else} on:click={async () => {
<p><a href="/admin/dienststellen" class="text-blue-600 underline">Dienststellen verwalten</a></p> await fetch('/api/admin/logout', { method: 'POST' });
<h1 class="text-2xl font-bold mb-4">Alle Anmeldungen</h1> location.reload();
<table class="w-full border text-sm"> }}
<thead> class="text-sm text-red-600 underline"
<tr class="bg-gray-200"> >
<th class="p-2 text-left">Name</th> Logout
<th class="p-2 text-left">E-Mail</th> </button>
<th class="p-2 text-left">Wunsch 13</th> </div>
<th class="p-2 text-left">Datum</th> {/if}
</tr> </div>
</thead>
<tbody>
{#each anmeldungen as a}
<tr class="border-t">
<td class="p-2">{a.anrede} {a.vorname} {a.nachname}</td>
<td class="p-2">{a.email}</td>
<td class="p-2">{a.wunsch1}, {a.wunsch2}, {a.wunsch3}</td>
<td class="p-2">{new Date(a.timestamp).toLocaleDateString()}</td>
</tr>
{/each}
</tbody>
</table>
{/if}
<style>
<style> .input {
.input { @apply border rounded px-3 py-2 w-full;
@apply border rounded px-3 py-2 w-full; }
} </style>

View File

@@ -0,0 +1,8 @@
import type { PageServerLoad } from '../../api/admin/anmeldungen/$types';
import { redirect } from '@sveltejs/kit';
export const load: PageServerLoad = async ({ cookies }) => {
if (cookies.get('admin_session') !== 'true') {
throw redirect(303, '/admin');
}
};

View File

@@ -0,0 +1,40 @@
<script lang="ts">
import { onMount } from 'svelte';
let anmeldungen = [];
async function ladeAnmeldungen() {
const res = await fetch('/api/admin/anmeldungen');
anmeldungen = await res.json();
}
onMount(ladeAnmeldungen);
</script>
<div class="p-6 max-w-4xl mx-auto">
<h1 class="text-2xl font-bold mb-4">Alle Anmeldungen</h1>
<table class="w-full border text-sm">
<thead>
<tr class="bg-gray-200">
<th class="p-2 text-left">Name</th>
<th class="p-2 text-left">E-Mail</th>
<th class="p-2 text-left">Wunsch 13</th>
<th class="p-2 text-left">Datum</th>
</tr>
</thead>
<tbody>
{#each anmeldungen as a}
<tr class="border-t">
<td class="p-2">{a.anrede} {a.vorname} {a.nachname}</td>
<td class="p-2">{a.email}</td>
<td class="p-2">{a.wunsch1.name}<br>{a.wunsch2.name}<br>{a.wunsch3.name}</td>
<td class="p-2">{new Date(a.timestamp).toLocaleDateString()}</td>
</tr>
{/each}
</tbody>
</table>
</div>
<style>
.input {
@apply border rounded px-3 py-2 w-full;
}

View File

@@ -0,0 +1,8 @@
import type { PageServerLoad } from './$types';
import { redirect } from '@sveltejs/kit';
export const load: PageServerLoad = async ({ cookies }) => {
if (cookies.get('admin_session') !== 'true') {
throw redirect(303, '/admin'); // zurück zur Login-Seite
}
};

View File

@@ -5,11 +5,11 @@ const prisma = new PrismaClient();
export const GET: RequestHandler = async () => { export const GET: RequestHandler = async () => {
const anmeldungen = await prisma.anmeldung.findMany({ const anmeldungen = await prisma.anmeldung.findMany({
//include: { include: {
// wunsch1: true, wunsch1: true,
// wunsch2: true, wunsch2: true,
// wunsch3: true wunsch3: true
//}; },
orderBy: { timestamp: 'desc' } orderBy: { timestamp: 'desc' }
}); });

View File

@@ -1,25 +1,33 @@
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import type { RequestHandler } from '@sveltejs/kit'; import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
const prisma = new PrismaClient(); const prisma = new PrismaClient();
export const GET: RequestHandler = async () => { function checkAuth(cookies: any) {
return cookies.get('admin_session') === 'true';
}
export const GET: RequestHandler = async ({ cookies }) => {
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
const dienststellen = await prisma.dienststelle.findMany({ orderBy: { name: 'asc' } }); const dienststellen = await prisma.dienststelle.findMany({ orderBy: { name: 'asc' } });
return new Response(JSON.stringify(dienststellen)); return json(dienststellen);
}; };
export const POST: RequestHandler = async ({ request }) => { export const POST: RequestHandler = async ({ cookies, request }) => {
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
const { name } = await request.json(); const { name } = await request.json();
try { try {
const created = await prisma.dienststelle.create({ data: { name } }); const created = await prisma.dienststelle.create({ data: { name } });
return new Response(JSON.stringify(created)); return json(created);
} catch (e) { } catch (e) {
return new Response(JSON.stringify({ error: 'Dienststelle existiert bereits' }), { status: 400 }); return json({ error: 'Dienststelle existiert bereits' }, { status: 400 });
} }
}; };
export const DELETE: RequestHandler = async ({ url }) => { export const DELETE: RequestHandler = async ({ cookies, url }) => {
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
const id = Number(url.searchParams.get('id')); const id = Number(url.searchParams.get('id'));
await prisma.dienststelle.delete({ where: { id } }); await prisma.dienststelle.delete({ where: { id } });
return new Response(JSON.stringify({ success: true })); return json({ success: true });
}; };

View File

@@ -1,13 +1,20 @@
import type { RequestHandler } from '@sveltejs/kit'; import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
const ADMIN_PASS = 'supergeheim'; // Passwort hier festlegen const ADMIN_PASS = 'supergeheim'; // 🔒 Besser aus .env lesen
export const POST: RequestHandler = async ({ request }) => { export const POST: RequestHandler = async ({ request, cookies }) => {
const { passwort } = await request.json(); const { passwort } = await request.json();
if (passwort === ADMIN_PASS) { if (passwort === ADMIN_PASS) {
return new Response(JSON.stringify({ success: true })); cookies.set('admin_session', 'true', {
path: '/',
httpOnly: true,
sameSite: 'strict',
maxAge: 60 * 60 * 4 // 4 Stunden
});
return json({ success: true });
} }
return new Response('Unauthorized', { status: 401 }); return json({ error: 'Falsches Passwort' }, { status: 401 });
}; };

View File

@@ -0,0 +1,6 @@
import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ cookies }) => {
cookies.delete('admin_session', { path: '/' });
return new Response('Ausgeloggt');
};