vereinshaus/app/src/routes/api/push/senden/+server.ts
rene 39981c0d17 Migrate: PocketBase → SvelteKit + better-sqlite3 + JWT
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
2026-05-21 21:55:04 +02:00

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 });
}