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
This commit is contained in:
rene 2026-05-21 21:55:04 +02:00
parent 61c430f2e6
commit 39981c0d17
58 changed files with 2313 additions and 651 deletions

View file

@ -1,264 +1,153 @@
#!/usr/bin/env node
// Testdaten für vereins.haus Staging
// Aufruf: PB_URL=http://localhost:8090 PB_EMAIL=admin@test.de PB_PASSWORD=Test123456! node scripts/seed.js
// Seed-Script für vereins.haus (SvelteKit + SQLite, kein PocketBase)
// Verwendung: APP_URL=https://staging.vereins.haus node scripts/seed.js
const PB_URL = process.env.PB_URL || 'http://localhost:8090';
const PB_EMAIL = process.env.PB_EMAIL || '';
const PB_PWD = process.env.PB_PASSWORD || '';
const BASE = process.env.APP_URL || 'http://localhost:3000';
if (!PB_EMAIL || !PB_PWD) {
console.error('Fehler: PB_EMAIL und PB_PASSWORD setzen.');
process.exit(1);
}
let token = '';
async function pb(method, path, body) {
const res = await fetch(`${PB_URL}/api/${path}`, {
method,
headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: token } : {}) },
body: body ? JSON.stringify(body) : undefined,
async function req(method, path, body, token) {
const headers = { 'Content-Type': 'application/json' };
if (token) headers['Authorization'] = `Bearer ${token}`;
const res = await fetch(`${BASE}/api${path}`, {
method, headers, body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(`${method} /${path}${res.status}: ${JSON.stringify(json)}`);
if (!res.ok) throw new Error(`${method} ${path}${res.status}: ${JSON.stringify(json)}`);
return json;
}
async function find(collection, filter) {
const r = await pb('GET', `collections/${collection}/records?filter=${encodeURIComponent(filter)}&perPage=1`);
return r.items?.[0] ?? null;
}
async function main() {
console.log(`→ Seed gegen ${BASE}`);
async function create(collection, data) {
return pb('POST', `collections/${collection}/records`, data);
}
async function update(collection, id, data) {
return pb('PATCH', `collections/${collection}/records/${id}`, data);
}
// ── 1. Admin-Login ──────────────────────────────────────────────────────────
console.log('→ Admin-Login…');
const auth = await pb('POST', 'collections/_superusers/auth-with-password', {
identity: PB_EMAIL, password: PB_PWD,
});
token = auth.token;
console.log(' ✓ Eingeloggt als', PB_EMAIL);
// ── 2. Verein ────────────────────────────────────────────────────────────────
console.log('→ Verein anlegen…');
let verein = await find('vereine', 'name = "TSV Musterstadt 1983 e.V."');
if (!verein) {
verein = await create('vereine', {
name: 'TSV Musterstadt 1983 e.V.',
adresse: 'Vereinsstraße 12',
plz: '80333',
ort: 'Musterstadt',
bundesland: 'BY',
plan: 'starter',
dosb_mitglied: true,
email: 'info@tsv-musterstadt.de',
telefon: '089 123456',
website: 'https://tsv-musterstadt.de',
glaeubigerid: 'DE98ZZZ09999999999',
iban: 'DE89370400440532013000',
bic: 'COBADEFFXXX',
});
}
console.log(' ✓ Verein:', verein.name, `(${verein.id})`);
// ── 3. Admin-User ────────────────────────────────────────────────────────────
console.log('→ Admin-User…');
let adminUser = await find('users', 'email = "vorstand@tsv-musterstadt.de"');
if (!adminUser) {
adminUser = await create('users', {
// 1. Verein + Admin registrieren
console.log('→ Registrierung...');
const auth = await req('POST', '/auth/register', {
vereinName: 'TSV Musterstadt 1983 e.V.',
email: 'vorstand@tsv-musterstadt.de',
password: 'Test123456!', passwordConfirm: 'Test123456!',
name: 'Max Mustermann', verein_id: verein.id, rolle: null, // null = admin
emailVisibility: true,
password: 'Test123456!',
name: 'Max Vorstand',
});
}
console.log(' ✓ Admin:', adminUser.email);
const T = auth.token;
const VID = auth.verein_id;
console.log(` ✓ Verein ID: ${VID}`);
console.log(` ✓ User: vorstand@tsv-musterstadt.de`);
// ── 4. Gruppen ───────────────────────────────────────────────────────────────
console.log('→ Gruppen…');
const gruppenDef = ['Vorstand', 'Aktive Mitglieder', 'Jugend U15', 'Senioren'];
const gruppen = {};
for (const name of gruppenDef) {
let g = await find('gruppen', `name = "${name}" && verein_id = "${verein.id}"`);
if (!g) g = await create('gruppen', { verein_id: verein.id, name });
gruppen[name] = g.id;
}
console.log(' ✓', Object.keys(gruppen).join(', '));
// 2. Verein-Details ergänzen
await req('PATCH', '/vereine', {
adresse: 'Musterstraße 1', plz: '12345', ort: 'Musterstadt',
bundesland: 'Bayern', plan: 'starter', dosb_mitglied: true,
email: 'info@tsv-musterstadt.de', telefon: '01234 56789',
glaeubigerid: 'DE98ZZZ09999999999',
iban: 'DE89370400440532013000', bic: 'COBADEFFXXX',
}, T);
// ── 5. Mitglieder ────────────────────────────────────────────────────────────
console.log('→ Mitglieder (18)…');
const mitgliederDef = [
// Vorstand
{ vorname: 'Max', nachname: 'Mustermann', email: 'max.mustermann@example.de', telefon: '0170 1111111', geburtsdatum: '1975-03-15', eintrittsdatum: '2000-01-01', strasse: 'Hauptstraße 1', plz: '80333', ort: 'Musterstadt', iban: 'DE89370400440532013000', bic: 'COBADEFFXXX', status: 'aktiv', gruppe: 'Vorstand', mandatsreferenz: 'MANDAT-001', mandatsdatum: '2020-01-10' },
{ vorname: 'Sabine', nachname: 'Richter', email: 'sabine.richter@example.de', telefon: '0171 2222222', geburtsdatum: '1980-07-22', eintrittsdatum: '2005-03-01', strasse: 'Birkenweg 5', plz: '80334', ort: 'Musterstadt', iban: 'DE27100777770209299700', bic: 'SSKMDEMMXXX', status: 'aktiv', gruppe: 'Vorstand', mandatsreferenz: 'MANDAT-002', mandatsdatum: '2020-01-10' },
// Aktive mit IBAN
{ vorname: 'Anna', nachname: 'Schmidt', email: 'anna.schmidt@example.de', telefon: '0172 3333333', geburtsdatum: '1990-11-05', eintrittsdatum: '2015-09-01', strasse: 'Rosenstraße 8', plz: '80333', ort: 'Musterstadt', iban: 'DE61500105179767440929', bic: 'BELADEBEXXX', status: 'aktiv', gruppe: 'Aktive Mitglieder', mandatsreferenz: 'MANDAT-003', mandatsdatum: '2021-04-15' },
{ vorname: 'Peter', nachname: 'Wagner', email: 'peter.wagner@example.de', telefon: '0173 4444444', geburtsdatum: '1985-02-28', eintrittsdatum: '2010-01-15', strasse: 'Gartenweg 12', plz: '80334', ort: 'Musterstadt', iban: 'DE24500105171911148770', bic: 'BELADEBEXXX', status: 'aktiv', gruppe: 'Aktive Mitglieder', mandatsreferenz: 'MANDAT-004', mandatsdatum: '2021-04-15' },
{ vorname: 'Thomas', nachname: 'Fischer', email: 'thomas.fischer@example.de', telefon: '0174 5555555', geburtsdatum: '1978-06-10', eintrittsdatum: '2008-04-01', strasse: 'Lindenallee 3', plz: '80335', ort: 'Musterstadt', iban: 'DE12500105176228935005', bic: 'BELADEBEXXX', status: 'aktiv', gruppe: 'Aktive Mitglieder', mandatsreferenz: 'MANDAT-005', mandatsdatum: '2021-04-15' },
{ vorname: 'Claudia', nachname: 'König', email: 'claudia.koenig@example.de', telefon: '0175 6666666', geburtsdatum: '1992-09-17', eintrittsdatum: '2018-06-01', strasse: 'Feldweg 7', plz: '80333', ort: 'Musterstadt', iban: 'DE67200501001234567890', bic: 'HASPDEHHXXX', status: 'aktiv', gruppe: 'Aktive Mitglieder', mandatsreferenz: 'MANDAT-006', mandatsdatum: '2022-01-20' },
{ vorname: 'Michael', nachname: 'Koch', email: 'michael.koch@example.de', telefon: '0176 7777777', geburtsdatum: '1983-04-03', eintrittsdatum: '2012-02-01', strasse: 'Bergstraße 22', plz: '80336', ort: 'Musterstadt', iban: 'DE86200400600526015800', bic: 'COBADEFFXXX', status: 'aktiv', gruppe: 'Aktive Mitglieder', mandatsreferenz: 'MANDAT-007', mandatsdatum: '2021-04-15' },
{ vorname: 'Lisa', nachname: 'Zimmermann', email: 'lisa.zimm@example.de', telefon: '0177 8888888', geburtsdatum: '1995-12-25', eintrittsdatum: '2020-10-01', strasse: 'Blumenstraße 4', plz: '80333', ort: 'Musterstadt', iban: 'DE21700519950021267002', bic: 'BYLADEM1AUG', status: 'aktiv', gruppe: 'Aktive Mitglieder', mandatsreferenz: 'MANDAT-008', mandatsdatum: '2022-03-05' },
{ vorname: 'Petra', nachname: 'Schreiber', email: 'petra.schreiber@example.de', telefon: '0178 9999999', geburtsdatum: '1988-08-14', eintrittsdatum: '2014-07-01', strasse: 'Weinbergweg 9', plz: '80334', ort: 'Musterstadt', iban: 'DE36200400600532013004', bic: 'COBADEFFXXX', status: 'aktiv', gruppe: 'Aktive Mitglieder', mandatsreferenz: 'MANDAT-009', mandatsdatum: '2021-04-15' },
// Aktive ohne IBAN
{ vorname: 'Maria', nachname: 'Becker', email: 'maria.becker@example.de', telefon: '0179 1010101', geburtsdatum: '1987-01-30', eintrittsdatum: '2016-03-01', strasse: 'Kirchgasse 6', plz: '80335', ort: 'Musterstadt', iban: null, bic: null, status: 'aktiv', gruppe: 'Aktive Mitglieder', mandatsreferenz: null, mandatsdatum: null },
{ vorname: 'Julia', nachname: 'Müller', email: 'julia.mueller@example.de', telefon: '0151 1111222', geburtsdatum: '1993-05-19', eintrittsdatum: '2019-01-01', strasse: 'Parkstraße 11', plz: '80333', ort: 'Musterstadt', iban: null, bic: null, status: 'aktiv', gruppe: 'Aktive Mitglieder', mandatsreferenz: null, mandatsdatum: null },
{ vorname: 'Markus', nachname: 'Schäfer', email: 'markus.schaefer@example.de', telefon: '0152 3333444', geburtsdatum: '1991-07-07', eintrittsdatum: '2021-05-01', strasse: 'Schulweg 2', plz: '80336', ort: 'Musterstadt', iban: null, bic: null, status: 'aktiv', gruppe: 'Aktive Mitglieder', mandatsreferenz: null, mandatsdatum: null },
// Jugend
{ vorname: 'Lena', nachname: 'Bauer', email: null, telefon: '0160 1234567', geburtsdatum: '2012-03-22', eintrittsdatum: '2023-09-01', strasse: 'Schulstraße 14', plz: '80333', ort: 'Musterstadt', iban: 'DE47200400600128491600', bic: 'COBADEFFXXX', status: 'aktiv', gruppe: 'Jugend U15', mandatsreferenz: 'MANDAT-013', mandatsdatum: '2023-09-01' },
{ vorname: 'Kevin', nachname: 'Hoffmann', email: null, telefon: '0160 7654321', geburtsdatum: '2013-11-08', eintrittsdatum: '2023-09-01', strasse: 'Waldweg 3', plz: '80334', ort: 'Musterstadt', iban: null, bic: null, status: 'aktiv', gruppe: 'Jugend U15', mandatsreferenz: null, mandatsdatum: null },
{ vorname: 'Emma', nachname: 'Klein', email: null, telefon: '0160 1122334', geburtsdatum: '2011-06-15', eintrittsdatum: '2022-09-01', strasse: 'Seeweg 7', plz: '80335', ort: 'Musterstadt', iban: null, bic: null, status: 'aktiv', gruppe: 'Jugend U15', mandatsreferenz: null, mandatsdatum: null },
// Senioren
{ vorname: 'Gertrude', nachname: 'Neumann', email: 'g.neumann@example.de', telefon: '089 9876543', geburtsdatum: '1948-04-12', eintrittsdatum: '1990-01-01', strasse: 'Ahornweg 1', plz: '80333', ort: 'Musterstadt', iban: 'DE43500105176118506698', bic: 'BELADEBEXXX', status: 'aktiv', gruppe: 'Senioren', mandatsreferenz: 'MANDAT-016', mandatsdatum: '2019-06-01' },
// Passiv / Ausgetreten
{ vorname: 'Hans', nachname: 'Schneider', email: 'hans.schneider@example.de', telefon: '0173 0000001', geburtsdatum: '1965-10-20', eintrittsdatum: '1995-04-01', strasse: 'Bergblick 5', plz: '80334', ort: 'Musterstadt', iban: null, bic: null, status: 'passiv', gruppe: 'Senioren', mandatsreferenz: null, mandatsdatum: null },
{ vorname: 'Horst', nachname: 'Braun', email: 'horst.braun@example.de', telefon: null, geburtsdatum: '1960-02-14', eintrittsdatum: '1988-01-01', strasse: 'Alte Gasse 3', plz: '80336', ort: 'Musterstadt', iban: null, bic: null, status: 'ausgetreten', gruppe: 'Aktive Mitglieder', mandatsreferenz: null, mandatsdatum: null, austrittsdatum: '2024-12-31' },
];
// 3. Gruppen
console.log('→ Gruppen...');
const gruppen = {};
for (const name of ['Vorstand', 'Aktive Mitglieder', 'Jugend U15', 'Senioren']) {
const g = await req('POST', '/gruppen', { name }, T);
gruppen[name] = g.id;
}
console.log(`${Object.keys(gruppen).length} Gruppen`);
let mitgliederIds = [];
for (const m of mitgliederDef) {
let rec = await find('mitglieder', `vorname = "${m.vorname}" && nachname = "${m.nachname}" && verein_id = "${verein.id}"`);
if (!rec) {
rec = await create('mitglieder', {
verein_id: verein.id,
vorname: m.vorname,
nachname: m.nachname,
email: m.email || null,
telefon: m.telefon || null,
geburtsdatum: m.geburtsdatum,
eintrittsdatum: m.eintrittsdatum,
austrittsdatum: m.austrittsdatum || null,
strasse: m.strasse,
plz: m.plz,
ort: m.ort,
iban: m.iban || null,
bic: m.bic || null,
mandatsreferenz:m.mandatsreferenz || null,
mandatsdatum: m.mandatsdatum || null,
status: m.status,
gruppe_ids: [gruppen[m.gruppe]].filter(Boolean),
// 4. Mitglieder
console.log('→ Mitglieder (18)...');
const mitgliederDaten = [
{ vorname: 'Max', nachname: 'Vorstand', email: 'vorstand@tsv-musterstadt.de', status: 'aktiv', gruppe_ids: [gruppen['Vorstand'], gruppen['Aktive Mitglieder']], eintrittsdatum: '2010-01-01' },
{ vorname: 'Anna', nachname: 'Schmidt', email: 'anna.schmidt@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder']], eintrittsdatum: '2015-03-15', iban: 'DE89370400440532013001' },
{ vorname: 'Thomas', nachname: 'Müller', email: 'thomas.mueller@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder'], gruppen['Senioren']], eintrittsdatum: '2012-06-01' },
{ vorname: 'Lisa', nachname: 'Weber', email: 'lisa.weber@example.de', status: 'aktiv', gruppe_ids: [gruppen['Jugend U15']], eintrittsdatum: '2022-09-01', geburtsdatum: '2010-04-12' },
{ vorname: 'Klaus', nachname: 'Fischer', email: 'klaus.fischer@example.de', status: 'aktiv', gruppe_ids: [gruppen['Senioren']], eintrittsdatum: '2008-01-15' },
{ vorname: 'Maria', nachname: 'Bauer', email: 'maria.bauer@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder']], eintrittsdatum: '2019-02-20' },
{ vorname: 'Peter', nachname: 'Wagner', email: 'peter.wagner@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder']], eintrittsdatum: '2017-05-10', iban: 'DE89370400440532013002' },
{ vorname: 'Julia', nachname: 'Becker', email: 'julia.becker@example.de', status: 'aktiv', gruppe_ids: [gruppen['Jugend U15']], eintrittsdatum: '2023-01-01', geburtsdatum: '2011-07-22' },
{ vorname: 'Stefan', nachname: 'Hoffmann', email: 'stefan.hoffmann@example.de', status: 'passiv', gruppe_ids: [], eintrittsdatum: '2005-03-01' },
{ vorname: 'Sandra', nachname: 'Koch', email: 'sandra.koch@example.de', status: 'aktiv', gruppe_ids: [gruppen['Senioren']], eintrittsdatum: '2014-11-15' },
{ vorname: 'Michael', nachname: 'Schäfer', email: 'michael.schaefer@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder']], eintrittsdatum: '2020-04-01' },
{ vorname: 'Sabine', nachname: 'Zimmermann', email: 'sabine.zimm@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder']], eintrittsdatum: '2018-08-20' },
{ vorname: 'Andreas', nachname: 'Braun', email: 'andreas.braun@example.de', status: 'aktiv', gruppe_ids: [gruppen['Senioren']], eintrittsdatum: '2011-01-01' },
{ vorname: 'Monika', nachname: 'Richter', email: 'monika.richter@example.de', status: 'passiv', gruppe_ids: [], eintrittsdatum: '2009-06-15' },
{ vorname: 'Tobias', nachname: 'Wolf', email: 'tobias.wolf@example.de', status: 'aktiv', gruppe_ids: [gruppen['Jugend U15']], eintrittsdatum: '2022-01-15', geburtsdatum: '2012-01-30' },
{ vorname: 'Eva', nachname: 'Krause', email: 'eva.krause@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder']], eintrittsdatum: '2021-03-01' },
{ vorname: 'Markus', nachname: 'Schwarz', email: 'markus.schwarz@example.de', status: 'aktiv', gruppe_ids: [gruppen['Aktive Mitglieder'], gruppen['Vorstand']], eintrittsdatum: '2013-09-01' },
{ vorname: 'Nina', nachname: 'Lange', email: 'nina.lange@example.de', status: 'ausgetreten', gruppe_ids: [], eintrittsdatum: '2016-02-01', austrittsdatum: '2023-12-31' },
];
for (const m of mitgliederDaten) await req('POST', '/mitglieder', m, T);
console.log(` ✓ 18 Mitglieder`);
// 5. Veranstaltungsorte
console.log('→ Orte...');
const orte = {};
for (const o of [
{ name: 'Turnhalle Grundschule Muster', adresse: 'Schulweg 5, 12345 Musterstadt', typ: 'halle', aktiv: true },
{ name: 'Vereinsheim TSV', adresse: 'Musterstraße 3, 12345 Musterstadt', typ: 'gebaeude', aktiv: true },
{ name: 'Sportplatz West', adresse: 'Weststraße 10, 12345 Musterstadt', typ: 'platz', aktiv: true },
]) {
const r = await req('POST', '/orte', o, T);
orte[o.name] = r.id;
}
console.log(` ✓ 3 Orte`);
// 6. Beitragsarten
console.log('→ Beitragsarten...');
for (const b of [
{ name: 'Jahresbeitrag Erwachsene', betrag: 120, rhythmus: 'jaehrlich', beschreibung: 'Normalbeitrag für erwachsene Mitglieder' },
{ name: 'Jahresbeitrag Jugend', betrag: 60, rhythmus: 'jaehrlich', beschreibung: 'Ermäßigter Beitrag bis 18 Jahre' },
{ name: 'Aufnahmegebühr', betrag: 25, rhythmus: 'einmalig', beschreibung: 'Einmalige Gebühr bei Eintritt' },
{ name: 'Monatsbeitrag Fitness', betrag: 15, rhythmus: 'monatlich', beschreibung: 'Zusatzbeitrag Fitnessraum' },
]) {
await req('POST', '/beitraege', b, T);
}
console.log(` ✓ 4 Beitragsarten`);
// 7. Termine
console.log('→ Termine...');
const now = new Date();
const dt = (offsetDays, h = 18, m = 0) => {
const d = new Date(now);
d.setDate(d.getDate() + offsetDays);
d.setHours(h, m, 0, 0);
return d.toISOString().slice(0, 19);
};
const termine = [
{ titel: 'Vorstandssitzung', beginn: dt(3, 19), ort_id: orte['Vereinsheim TSV'], gruppe_ids: [gruppen['Vorstand']], verfuegbarkeit: 'bestaetigt' },
{ titel: 'Vereinsmeisterschaft', beginn: dt(14, 9), ort_id: orte['Sportplatz West'], gruppe_ids: [], beschreibung: 'Jährliche Meisterschaft', verfuegbarkeit: 'bestaetigt' },
{ titel: 'Jugendtraining', beginn: dt(2, 16), ort_id: orte['Turnhalle Grundschule Muster'], gruppe_ids: [gruppen['Jugend U15']], verfuegbarkeit: 'bestaetigt' },
{ titel: 'Mitgliederversammlung', beginn: dt(21, 19,30),ort_id: orte['Vereinsheim TSV'], gruppe_ids: [], beschreibung: 'Ordentliche Jahreshauptversammlung', verfuegbarkeit: 'offen' },
{ titel: 'Seniorensport', beginn: dt(7, 10), ort_id: orte['Turnhalle Grundschule Muster'], gruppe_ids: [gruppen['Senioren']], verfuegbarkeit: 'bestaetigt' },
{ titel: 'Sommerfest', beginn: dt(45, 14), ort_id: orte['Sportplatz West'], gruppe_ids: [], beschreibung: 'Großes Vereinssommerfest', verfuegbarkeit: 'offen' },
];
// 8 Wochen Dienstags-Training
let di = new Date(now);
di.setDate(di.getDate() + ((2 - di.getDay() + 7) % 7 || 7));
di.setHours(18, 0, 0, 0);
for (let i = 0; i < 8; i++) {
const b = new Date(di); b.setDate(b.getDate() + i * 7);
termine.push({
titel: 'Training Aktive', beginn: b.toISOString().slice(0, 19),
ort_id: orte['Turnhalle Grundschule Muster'],
gruppe_ids: [gruppen['Aktive Mitglieder']], verfuegbarkeit: 'offen',
rrule: 'FREQ=WEEKLY;BYDAY=TU',
});
}
mitgliederIds.push(rec.id);
}
console.log(`${mitgliederIds.length} Mitglieder`);
for (const t of termine) await req('POST', '/termine', t, T);
console.log(`${termine.length} Termine`);
// ── 6. Veranstaltungsorte ────────────────────────────────────────────────────
console.log('→ Veranstaltungsorte…');
const orteDef = [
{ name: 'Turnhalle Grundschule Muster', adresse: 'Schulstraße 1, 80333 Musterstadt', typ: 'halle', aktiv: true },
{ name: 'Vereinsheim TSV', adresse: 'Vereinsstraße 12, 80333 Musterstadt', typ: 'gebaeude', aktiv: true },
{ name: 'Sportplatz West', adresse: 'Weststraße 99, 80335 Musterstadt', typ: 'platz', aktiv: true },
];
const orte = {};
for (const o of orteDef) {
let rec = await find('veranstaltungsorte', `name = "${o.name}" && verein_id = "${verein.id}"`);
if (!rec) rec = await create('veranstaltungsorte', { verein_id: verein.id, ...o });
orte[o.name] = rec.id;
}
// Ausfall: Turnhalle gesperrt wegen Schulveranstaltung
const turnhalleId = orte['Turnhalle Grundschule Muster'];
const existingAusfall = await find('ort_ausfaelle', `ort_id = "${turnhalleId}"`);
if (!existingAusfall) {
const nextWeek = new Date(); nextWeek.setDate(nextWeek.getDate() + 7);
const nextWeek2 = new Date(); nextWeek2.setDate(nextWeek2.getDate() + 9);
await create('ort_ausfaelle', {
ort_id: turnhalleId,
von: nextWeek.toISOString().slice(0,10),
bis: nextWeek2.toISOString().slice(0,10),
grund: 'Schulveranstaltung Halle nicht verfügbar',
});
}
console.log(' ✓ Orte:', Object.keys(orte).join(', '));
// 8. Nachricht
console.log('→ Nachricht...');
await req('POST', '/nachrichten', {
betreff: 'Willkommen in vereins.haus!',
text: '<p>Hallo und herzlich willkommen! Dies ist eine Beispiel-Nachricht an alle Mitglieder.</p>',
gruppe_ids: [],
}, T);
console.log(` ✓ Nachricht erstellt`);
// ── 7. Beitragsarten ─────────────────────────────────────────────────────────
console.log('→ Beitragsarten…');
const beitraegeDef = [
{ name: 'Jahresbeitrag Erwachsene', betrag: 48, rhythmus: 'jaehrlich', beschreibung: 'Für Mitglieder ab 18 Jahren' },
{ name: 'Jahresbeitrag Jugend', betrag: 24, rhythmus: 'jaehrlich', beschreibung: 'Für Mitglieder unter 18 Jahren' },
{ name: 'Aufnahmegebühr', betrag: 20, rhythmus: 'einmalig', beschreibung: 'Einmalig bei Vereinseintritt' },
{ name: 'Monatsbeitrag Fitness', betrag: 15, rhythmus: 'monatlich', beschreibung: 'Für die Fitnessgruppe (optional)' },
];
for (const b of beitraegeDef) {
const ex = await find('beitraege', `name = "${b.name}" && verein_id = "${verein.id}"`);
if (!ex) await create('beitraege', { verein_id: verein.id, ...b });
}
console.log(' ✓', beitraegeDef.map(b => b.name).join(', '));
// ── 8. Termine ───────────────────────────────────────────────────────────────
console.log('→ Termine…');
const d = (offsetDays, h = 0, m = 0) => {
const dt = new Date();
dt.setDate(dt.getDate() + offsetDays);
dt.setHours(h, m, 0, 0);
return dt.toISOString();
};
const termineDef = [
// Vergangen
{ titel: 'Jahreshauptversammlung 2026', beginn: d(-60, 19, 0), ende: d(-60, 21, 0), ort_id: orte['Vereinsheim TSV'], gruppe_ids: Object.values(gruppen), beschreibung: 'Jahresabrechnung, Vorstandswahl, Planung Sommerfest' },
{ titel: 'Trainingssession', beginn: d(-14, 18, 0), ende: d(-14, 20, 0), ort_id: orte['Turnhalle Grundschule Muster'], gruppe_ids: [gruppen['Aktive Mitglieder']] },
{ titel: 'Jugendtraining', beginn: d(-7, 16, 0), ende: d(-7, 17, 30), ort_id: orte['Turnhalle Grundschule Muster'], gruppe_ids: [gruppen['Jugend U15']] },
// Upcoming
{ titel: 'Vorstandssitzung', beginn: d(3, 19, 30), ende: d(3, 21, 0), ort_id: orte['Vereinsheim TSV'], gruppe_ids: [gruppen['Vorstand']], beschreibung: 'Vorbereitung Sommerfest, Kassenstand Q1' },
{ titel: 'Sommerfest TSV', beginn: d(32, 14, 0), ende: d(32, 22, 0), ort_id: orte['Sportplatz West'], gruppe_ids: Object.values(gruppen), beschreibung: 'Für Mitglieder und Familien Grillen, Spiele, Live-Musik' },
{ titel: 'Auswärtsspiel Musterliga', beginn: d(18, 11, 0), ende: d(18, 13, 0), ort_id: null, gruppe_ids: [gruppen['Aktive Mitglieder']], ort: 'FC Gegner Stadionstraße 1', beschreibung: 'Hinfahrt 10:00 Uhr am Vereinsheim' },
];
// Wöchentliches Training als Serie
const serie_id = 'seed-serie-dienstag-2026';
const trainingsTermine = [];
for (let i = 1; i <= 8; i++) {
// Nächster Dienstag + i Wochen
const dt = new Date();
const daysToTue = (2 - dt.getDay() + 7) % 7 || 7;
dt.setDate(dt.getDate() + daysToTue + (i - 1) * 7);
dt.setHours(18, 30, 0, 0);
trainingsTermine.push({
titel: 'Dienstags-Training',
beginn: dt.toISOString(),
ende: new Date(dt.getTime() + 90 * 60 * 1000).toISOString(),
ort_id: orte['Turnhalle Grundschule Muster'],
gruppe_ids:[gruppen['Aktive Mitglieder']],
rrule: 'FREQ=WEEKLY;BYDAY=TU',
serie_id,
});
console.log('');
console.log('✓ Seed abgeschlossen!');
console.log('');
console.log(` Verein: TSV Musterstadt 1983 e.V.`);
console.log(` Login: vorstand@tsv-musterstadt.de / Test123456!`);
console.log(` App: ${BASE}`);
}
const allTermine = [...termineDef, ...trainingsTermine];
let termineAngelegt = 0;
for (const t of allTermine) {
const ex = await find('termine', `titel = "${t.titel}" && verein_id = "${verein.id}" && beginn = "${t.beginn}"`);
if (!ex) {
await create('termine', { verein_id: verein.id, verfuegbarkeit: 'offen', ...t });
termineAngelegt++;
}
}
console.log(`${termineAngelegt} Termine (inkl. ${trainingsTermine.length} in Dienstags-Serie)`);
// ── 9. Nachricht ─────────────────────────────────────────────────────────────
console.log('→ Beispiel-Nachricht…');
const exMsg = await find('nachrichten', `verein_id = "${verein.id}"`);
if (!exMsg) {
await create('nachrichten', {
verein_id: verein.id,
autor_id: adminUser.id,
betreff: 'Willkommen bei vereins.haus!',
text: 'Liebe Mitglieder,\n\nwir haben unsere Vereinsverwaltung auf vereins.haus umgestellt. Hier findet ihr alle Termine, Nachrichten und Informationen rund um unseren Verein.\n\nBei Fragen wendet euch an den Vorstand.\n\nEuer Vorstand\nTSV Musterstadt 1983 e.V.',
gruppe_ids: Object.values(gruppen),
gesendet_am: new Date().toISOString(),
});
}
// ── Zusammenfassung ──────────────────────────────────────────────────────────
console.log('\n✓ Seed abgeschlossen!\n');
console.log(' Verein: ', verein.name);
console.log(' Login: vorstand@tsv-musterstadt.de / Test123456!');
console.log(' Mitglieder:', mitgliederIds.length);
console.log(' PocketBase:', PB_URL);
main().catch(e => { console.error('✗ Seed-Fehler:', e.message); process.exit(1); });