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
53 lines
1.7 KiB
TypeScript
53 lines
1.7 KiB
TypeScript
import { json } from '@sveltejs/kit';
|
|
import { env } from '$env/dynamic/private';
|
|
import webpush from 'web-push';
|
|
import { requireAuth } from '$lib/server/auth';
|
|
import { getDb } from '$lib/server/db';
|
|
|
|
export async function POST({ request }) {
|
|
const authUser = await requireAuth(request).catch(() => null);
|
|
if (!authUser) return json({ error: 'Nicht authentifiziert.' }, { status: 401 });
|
|
|
|
const { titel, body, url = '/nachrichten' } = await request.json();
|
|
|
|
if (!titel) return json({ error: 'Titel fehlt.' }, { status: 400 });
|
|
|
|
const vapidPublic = env.PUBLIC_VAPID_KEY ?? '';
|
|
const vapidPrivate = env.VAPID_PRIVATE_KEY ?? '';
|
|
const vapidSubject = env.VAPID_SUBJECT ?? 'mailto:info@vereins.haus';
|
|
|
|
if (!vapidPublic || !vapidPrivate) {
|
|
return json({ error: 'VAPID-Keys nicht konfiguriert.' }, { status: 500 });
|
|
}
|
|
|
|
webpush.setVapidDetails(vapidSubject, vapidPublic, vapidPrivate);
|
|
|
|
const db = getDb();
|
|
const items = db.prepare(
|
|
'SELECT * FROM push_subscriptions WHERE verein_id = ?'
|
|
).all(authUser.verein_id) as { endpoint: string; p256dh: string; auth: string; id: string }[];
|
|
|
|
if (!items.length) return json({ sent: 0 });
|
|
|
|
const payload = JSON.stringify({ title: titel, body, url });
|
|
let sent = 0;
|
|
|
|
await Promise.allSettled(
|
|
items.map(async (sub) => {
|
|
try {
|
|
await webpush.sendNotification(
|
|
{ endpoint: sub.endpoint, keys: { p256dh: sub.p256dh, auth: sub.auth } },
|
|
payload,
|
|
);
|
|
sent++;
|
|
} catch (err: unknown) {
|
|
// 410 Gone = Subscription abgelaufen → löschen
|
|
if ((err as { statusCode?: number }).statusCode === 410) {
|
|
db.prepare('DELETE FROM push_subscriptions WHERE id = ?').run(sub.id);
|
|
}
|
|
}
|
|
}),
|
|
);
|
|
|
|
return json({ sent });
|
|
}
|