Vollständige Migration weg von PocketBase. Neuer Stack: - better-sqlite3 (WAL-Mode, direkte SQLite-Abfragen) - jose (JWT HS256, 30 Tage Laufzeit) - bcryptjs (Passwort-Hashing, cost 12) Neue Dateien: - src/lib/server/db.ts → SQLite-Singleton + Schema + Helpers - src/lib/server/auth.ts → JWT sign/verify, bcrypt, Bearer-Token - src/lib/user.ts → Svelte-Store (ersetzt pb.authStore) - src/lib/api.ts → fetch()-Wrapper (ersetzt pb.collection()) - src/app.d.ts → App.Locals TypeScript-Deklaration - 30 neue API-Routes unter src/routes/api/ Entfernt: - Abhängigkeit von pocketbase npm-Paket (bleibt im package.json bis alle Referenzen bereinigt sind) - PocketBase-Container aus docker-compose.yml - Migrations und Hooks aus Deploy-Pipeline Docker: Ein einziger Container, SQLite-Volume unter /data/ Makefile: PocketBase-spezifische Targets entfernt seed.js: Komplett neu für neue REST-API
143 lines
2.3 KiB
Svelte
143 lines
2.3 KiB
Svelte
<script lang="ts">
|
|
import { goto } from '$app/navigation';
|
|
import { api } from '$lib/api';
|
|
import { user } from '$lib/user';
|
|
import type { AppUser } from '$lib/user';
|
|
|
|
let email = $state('');
|
|
let password = $state('');
|
|
let error = $state('');
|
|
let loading = $state(false);
|
|
|
|
async function login() {
|
|
error = '';
|
|
loading = true;
|
|
try {
|
|
const u = await api.post<AppUser & { token: string }>('/auth/login', { email, password });
|
|
user.set(u);
|
|
goto('/');
|
|
} catch {
|
|
error = 'E-Mail oder Passwort falsch.';
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>Anmelden — vereins.haus</title>
|
|
</svelte:head>
|
|
|
|
<h1>Anmelden</h1>
|
|
|
|
<form onsubmit={(e) => { e.preventDefault(); login(); }}>
|
|
<div class="field">
|
|
<label for="email">E-Mail</label>
|
|
<input
|
|
id="email"
|
|
type="email"
|
|
bind:value={email}
|
|
autocomplete="email"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="password">Passwort</label>
|
|
<input
|
|
id="password"
|
|
type="password"
|
|
bind:value={password}
|
|
autocomplete="current-password"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
{#if error}
|
|
<p class="error">{error}</p>
|
|
{/if}
|
|
|
|
<button type="submit" disabled={loading}>
|
|
{loading ? 'Anmelden…' : 'Anmelden'}
|
|
</button>
|
|
</form>
|
|
|
|
<p class="switch">
|
|
Noch kein Konto? <a href="/register">Verein registrieren</a>
|
|
</p>
|
|
|
|
<style>
|
|
h1 {
|
|
font-size: 1.4rem;
|
|
font-weight: 700;
|
|
margin-bottom: 1.5rem;
|
|
color: #1e293b;
|
|
}
|
|
|
|
.field {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.35rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
label {
|
|
font-size: 0.9rem;
|
|
font-weight: 500;
|
|
color: #475569;
|
|
}
|
|
|
|
input {
|
|
padding: 0.65rem 0.85rem;
|
|
border: 1.5px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
font-size: 1rem;
|
|
transition: border-color .15s;
|
|
background: #fff;
|
|
}
|
|
|
|
input:focus {
|
|
outline: none;
|
|
border-color: #1e40af;
|
|
}
|
|
|
|
.error {
|
|
color: #dc2626;
|
|
font-size: 0.9rem;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
button {
|
|
width: 100%;
|
|
padding: 0.75rem;
|
|
background: #1e40af;
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
margin-top: 0.5rem;
|
|
transition: background .15s;
|
|
}
|
|
|
|
button:hover:not(:disabled) {
|
|
background: #1d3a9e;
|
|
}
|
|
|
|
button:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.switch {
|
|
margin-top: 1.25rem;
|
|
font-size: 0.9rem;
|
|
color: #64748b;
|
|
text-align: center;
|
|
}
|
|
|
|
.switch a {
|
|
color: #1e40af;
|
|
font-weight: 500;
|
|
}
|
|
</style>
|