From 5658ef7bcce2b4f9331247b04e9b983f3ea48e02 Mon Sep 17 00:00:00 2001 From: Daniel Michelberger Date: Thu, 6 Aug 2026 13:42:24 +0200 Subject: [PATCH] chore: veraltete Scripts unter frontend/scripts entfernen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alle vier Scripts setzten auf einem Schema auf, das es nicht mehr gibt: - seed-test-data.ts schrieb in stages und results (heute: runs und times) und kannte users.role, riders.email/birthday sowie events.created_by/ timekeepers -- alles nicht mehr vorhanden. Teams kannte es gar nicht, ohne Team-Zuordnung greifen sämtliche API-Rules und die Daten wären unsichtbar geblieben. - migrate-schema.ts und migrate-teams.ts waren Einmal-Migrationen. Ihr Ergebnis steckt längst im Schema (events.status, riders.number, times. correction, die teams-Collection, die team-Felder). Ein erneuter Lauf gegen die produktive Instanz wäre bestenfalls wirkungslos. - test-token.ts war ein Wegwerf-Prüfscript für den Superuser-Token. Das Schema ist jetzt in backend/pb_migrations/ versioniert; das ist der Weg für Änderungen. tsx entfällt als Abhängigkeit, es wurde nur von diesen Scripts benutzt. PB_SUPERUSER_TOKEN bleibt in .env -- generate-pocketbase-types braucht ihn. Co-Authored-By: Claude Opus 5 (1M context) --- frontend/README.md | 19 +- frontend/package-lock.json | 44 ---- frontend/package.json | 6 +- frontend/scripts/migrate-schema.ts | 245 --------------------- frontend/scripts/migrate-teams.ts | 329 ----------------------------- frontend/scripts/seed-test-data.ts | 329 ----------------------------- frontend/scripts/test-token.ts | 54 ----- 7 files changed, 7 insertions(+), 1019 deletions(-) delete mode 100644 frontend/scripts/migrate-schema.ts delete mode 100644 frontend/scripts/migrate-teams.ts delete mode 100644 frontend/scripts/seed-test-data.ts delete mode 100644 frontend/scripts/test-token.ts diff --git a/frontend/README.md b/frontend/README.md index f45c6bb..a5167fe 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -38,19 +38,12 @@ Die Variable wird von SvelteKit zur **Buildzeit** eingesetzt Neustart des Dev-Servers bzw. einen neuen Build — ein blosser Neustart des Containers genügt nicht. -`PB_TYPEGEN_URL` und `PB_SUPERUSER_TOKEN` gelten getrennt davon für die -Typgenerierung und die Scripts unter `scripts/`. +`PB_TYPEGEN_URL` und `PB_SUPERUSER_TOKEN` gelten getrennt davon für +`npm run generate-pocketbase-types`. -## Scripts - -Die Dateien unter `scripts/` sind Einmal-Werkzeuge gegen eine PocketBase-Instanz -und laufen über `tsx`. Sie lesen ihre Zugangsdaten aus `.env`. - -> **Achtung, veralteter Stand:** `seed-test-data.ts` (`npm run seed`), -> `migrate-schema.ts` und `migrate-teams.ts` stammen aus einer früheren -> Schema-Generation. `seed-test-data.ts` schreibt in die Collections `stages` -> und `results`, die es im aktuellen Schema **nicht mehr gibt** — der Aufruf -> schlägt fehl. Vor einer Verwendung müssen die Scripts auf das aktuelle Schema -> (`users`, `teams`, `events`, `runs`, `riders`, `times`) angepasst werden. +## Schema Das maßgebliche Schema liegt als Migration in `../backend/pb_migrations/`. +Nach einer Schema-Änderung `npm run generate-pocketbase-types` ausführen — das +aktualisiert `src/lib/types.d.ts`, aus dem der PocketBase-Client seine Typen +bezieht. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8b0a39b..706d431 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -31,7 +31,6 @@ "tailwind-merge": "^3.3.1", "tailwind-variants": "^3.2.2", "tailwindcss": "^4.1.13", - "tsx": "^4.19.2", "tw-animate-css": "^1.4.0", "typescript": "^5.9.2", "vite": "^7.1.7" @@ -2379,19 +2378,6 @@ "node": ">= 0.4" } }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -3914,16 +3900,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -4646,26 +4622,6 @@ "dev": true, "license": "0BSD" }, - "node_modules/tsx": { - "version": "4.20.6", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", - "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.25.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9a0e799..958b259 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,10 +10,7 @@ "prepare": "svelte-kit sync || echo ''", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "generate-pocketbase-types": "set -a && . ./.env && set +a && pocketbase-typegen --url \"$PB_TYPEGEN_URL\" --token \"$PB_SUPERUSER_TOKEN\" --out ./src/lib/types.d.ts", - "seed": "tsx scripts/seed-test-data.ts", - "migrate": "tsx scripts/migrate-schema.ts", - "migrate-teams": "tsx scripts/migrate-teams.ts" + "generate-pocketbase-types": "set -a && . ./.env && set +a && pocketbase-typegen --url \"$PB_TYPEGEN_URL\" --token \"$PB_SUPERUSER_TOKEN\" --out ./src/lib/types.d.ts" }, "devDependencies": { "@internationalized/date": "^3.12.1", @@ -32,7 +29,6 @@ "tailwind-merge": "^3.3.1", "tailwind-variants": "^3.2.2", "tailwindcss": "^4.1.13", - "tsx": "^4.19.2", "tw-animate-css": "^1.4.0", "typescript": "^5.9.2", "vite": "^7.1.7" diff --git a/frontend/scripts/migrate-schema.ts b/frontend/scripts/migrate-schema.ts deleted file mode 100644 index 4ea53ec..0000000 --- a/frontend/scripts/migrate-schema.ts +++ /dev/null @@ -1,245 +0,0 @@ -/** - * Schema-Migration für PocketBase - * - * Erweitert das vorhandene Schema und migriert bestehende Daten: - * - events: + name (Text), + status (Select draft/active/finished) - * Daten: description → name kopieren, status = 'draft' - * - riders: + number (Text), + firstname (Text), + lastname (Text) - * Daten: bestehender name-Wert → number kopieren - * - times: + correction (Number, Sekunden) - * status erweitern: + dnf, dns, dsq - * - * Aufruf: npx tsx scripts/migrate-schema.ts - */ - -import { readFileSync } from 'node:fs' - -// .env minimal selbst parsen -for (const line of readFileSync('.env', 'utf8').split('\n')) { - const m = line.match(/^([A-Z_]+)=(.*)$/) - if (m && !process.env[m[1]]) process.env[m[1]] = m[2] -} - -const API = process.env.PB_TYPEGEN_URL?.replace(/\/$/, '') ?? 'https://api.stammtisch-hersbruck.de' -const TOKEN = process.env.PB_SUPERUSER_TOKEN - -if (!TOKEN) { - console.error('PB_SUPERUSER_TOKEN fehlt in .env') - process.exit(1) -} - -const headers = { - 'Authorization': TOKEN, - 'Content-Type': 'application/json', -} - -async function req(method: string, path: string, body?: unknown) { - const res = await fetch(`${API}${path}`, { - method, - headers, - body: body ? JSON.stringify(body) : undefined, - }) - if (!res.ok) { - const text = await res.text() - throw new Error(`${method} ${path} → ${res.status}: ${text}`) - } - return res.status === 204 ? null : await res.json() -} - -async function getCollection(name: string) { - return req('GET', `/api/collections/${name}`) -} - -async function updateCollection(name: string, body: any) { - return req('PATCH', `/api/collections/${name}`, body) -} - -async function listRecords(collection: string) { - const all: any[] = [] - let page = 1 - while (true) { - const res: any = await req('GET', `/api/collections/${collection}/records?page=${page}&perPage=200`) - all.push(...res.items) - if (page >= res.totalPages || res.totalPages === 0) break - page++ - } - return all -} - -async function updateRecord(collection: string, id: string, data: any) { - return req('PATCH', `/api/collections/${collection}/records/${id}`, data) -} - -function hasField(coll: any, name: string) { - return coll.fields.some((f: any) => f.name === name) -} - -// ---- Migration ---- - -async function migrateEvents() { - console.log('\n== events ==') - const coll = await getCollection('events') - const fields = [...coll.fields] - - if (!hasField(coll, 'name')) { - fields.push({ - name: 'name', - type: 'text', - required: false, - presentable: true, - max: 0, - min: 0, - pattern: '', - autogeneratePattern: '', - }) - console.log(' + Feld "name" (text)') - } else { - console.log(' = Feld "name" existiert bereits') - } - - if (!hasField(coll, 'status')) { - fields.push({ - name: 'status', - type: 'select', - required: false, - presentable: false, - maxSelect: 1, - values: ['draft', 'active', 'finished'], - }) - console.log(' + Feld "status" (select draft/active/finished)') - } else { - console.log(' = Feld "status" existiert bereits') - } - - await updateCollection('events', { fields }) - - // Daten migrieren - const records = await listRecords('events') - for (const r of records) { - const patch: any = {} - if (!r.name && r.description) patch.name = r.description - if (!r.status) patch.status = 'draft' - if (Object.keys(patch).length) { - await updateRecord('events', r.id, patch) - console.log(` → ${r.id}: ${JSON.stringify(patch)}`) - } - } -} - -async function migrateRiders() { - console.log('\n== riders ==') - const coll = await getCollection('riders') - const fields = [...coll.fields] - - if (!hasField(coll, 'number')) { - fields.push({ - name: 'number', - type: 'text', - required: false, - presentable: true, - max: 0, - min: 0, - pattern: '', - autogeneratePattern: '', - }) - console.log(' + Feld "number" (text)') - } else { - console.log(' = Feld "number" existiert bereits') - } - - if (!hasField(coll, 'firstname')) { - fields.push({ - name: 'firstname', - type: 'text', - required: false, - presentable: true, - max: 0, - min: 0, - pattern: '', - autogeneratePattern: '', - }) - console.log(' + Feld "firstname" (text)') - } else { - console.log(' = Feld "firstname" existiert bereits') - } - - if (!hasField(coll, 'lastname')) { - fields.push({ - name: 'lastname', - type: 'text', - required: false, - presentable: true, - max: 0, - min: 0, - pattern: '', - autogeneratePattern: '', - }) - console.log(' + Feld "lastname" (text)') - } else { - console.log(' = Feld "lastname" existiert bereits') - } - - await updateCollection('riders', { fields }) - - // Daten migrieren: name → number, falls number leer - const records = await listRecords('riders') - for (const r of records) { - if (!r.number && r.name) { - await updateRecord('riders', r.id, { number: r.name }) - console.log(` → ${r.id}: number = "${r.name}"`) - } - } -} - -async function migrateTimes() { - console.log('\n== times ==') - const coll = await getCollection('times') - const fields = [...coll.fields] - let changed = false - - if (!hasField(coll, 'correction')) { - fields.push({ - name: 'correction', - type: 'number', - required: false, - presentable: false, - min: null, - max: null, - onlyInt: false, - }) - console.log(' + Feld "correction" (number)') - changed = true - } else { - console.log(' = Feld "correction" existiert bereits') - } - - // status um dnf, dns, dsq erweitern - const statusField = fields.find((f: any) => f.name === 'status') - if (statusField) { - const current: string[] = statusField.values || [] - const target = ['active', 'finished', 'dnf', 'dns', 'dsq'] - const missing = target.filter((v) => !current.includes(v)) - if (missing.length) { - statusField.values = [...current, ...missing] - console.log(` ~ Feld "status" erweitert um: ${missing.join(', ')}`) - changed = true - } else { - console.log(' = Feld "status" hat bereits alle Werte') - } - } - - if (changed) await updateCollection('times', { fields }) -} - -async function main() { - console.log(`Migration gegen ${API}`) - await migrateEvents() - await migrateRiders() - await migrateTimes() - console.log('\n✓ Migration fertig') -} - -main().catch((e) => { - console.error('\n✗ Fehler:', e.message) - process.exit(1) -}) diff --git a/frontend/scripts/migrate-teams.ts b/frontend/scripts/migrate-teams.ts deleted file mode 100644 index 610e43c..0000000 --- a/frontend/scripts/migrate-teams.ts +++ /dev/null @@ -1,329 +0,0 @@ -/** - * Schema-Migration: Teams einführen - * - * - neue Collection 'teams' (name, owner, admins[], users[]) - * - events, riders, runs, times bekommen ein 'team'-Feld - * - listRule/viewRule auf allen vier wird auf team.users.id ?= @request.auth.id umgestellt - * - events.users wird entfernt - * - Migration der Daten: - * Team "Stammtisch Hersbruck" anlegen - * Owner = aktuell eingeloggter User (über .env: PB_OWNER_EMAIL falls gesetzt, sonst erster events.users) - * users = Union aller events.users - * events/riders/runs/times werden alle diesem Team zugeordnet - * - * Aufruf: npm run migrate-teams - */ - -import { readFileSync } from 'node:fs' - -// .env parsen — Zeilen die mit . anfangen werden an die vorherige Variable angehängt -// (Workaround für zerbrochene JWT-Tokens über mehrere Zeilen) -const env = readFileSync('.env', 'utf8').split('\n') -let lastKey = '' -for (const line of env) { - const m = line.match(/^([A-Z_]+)=(.*)$/) - if (m) { - process.env[m[1]] = m[2] - lastKey = m[1] - } else if (lastKey && line.length > 0) { - process.env[lastKey] = (process.env[lastKey] ?? '') + line - } -} - -const API = process.env.PB_TYPEGEN_URL?.replace(/\/$/, '') ?? 'https://api.stammtisch-hersbruck.de' -const TOKEN = process.env.PB_SUPERUSER_TOKEN - -if (!TOKEN) { - console.error('PB_SUPERUSER_TOKEN fehlt') - process.exit(1) -} - -const headers = { Authorization: TOKEN, 'Content-Type': 'application/json' } - -async function req(method: string, path: string, body?: unknown) { - const res = await fetch(`${API}${path}`, { - method, - headers, - body: body ? JSON.stringify(body) : undefined, - }) - if (!res.ok) throw new Error(`${method} ${path} → ${res.status}: ${await res.text()}`) - return res.status === 204 ? null : await res.json() -} - -async function getCollection(name: string) { - try { - return await req('GET', `/api/collections/${name}`) - } catch { - return null - } -} - -async function createCollection(body: any) { - return req('POST', '/api/collections', body) -} - -async function updateCollection(name: string, body: any) { - return req('PATCH', `/api/collections/${name}`, body) -} - -async function listAll(collection: string) { - const all: any[] = [] - let page = 1 - while (true) { - const res: any = await req('GET', `/api/collections/${collection}/records?page=${page}&perPage=200`) - all.push(...res.items) - if (page >= res.totalPages || res.totalPages === 0) break - page++ - } - return all -} - -async function updateRecord(collection: string, id: string, data: any) { - return req('PATCH', `/api/collections/${collection}/records/${id}`, data) -} - -async function createRecord(collection: string, data: any) { - return req('POST', `/api/collections/${collection}/records`, data) -} - -function hasField(coll: any, name: string) { - return coll.fields.some((f: any) => f.name === name) -} - -const USERS_AUTH = '_pb_users_auth_' - -// ---- Schritt 1: teams Collection anlegen ---- - -async function ensureTeamsCollection() { - console.log('\n== teams Collection ==') - const existing = await getCollection('teams') - if (existing) { - console.log(' = teams existiert bereits') - return existing - } - - const body = { - name: 'teams', - type: 'base', - listRule: 'users.id ?= @request.auth.id', - viewRule: 'users.id ?= @request.auth.id', - createRule: '@request.auth.id != ""', - updateRule: 'owner.id ?= @request.auth.id || admins.id ?= @request.auth.id', - deleteRule: 'owner.id ?= @request.auth.id', - fields: [ - { - name: 'name', - type: 'text', - required: true, - presentable: true, - max: 100, - min: 1, - pattern: '', - autogeneratePattern: '', - }, - { - name: 'owner', - type: 'relation', - required: true, - maxSelect: 1, - minSelect: 1, - collectionId: USERS_AUTH, - cascadeDelete: false, - }, - { - name: 'admins', - type: 'relation', - required: false, - maxSelect: 999, - minSelect: 0, - collectionId: USERS_AUTH, - cascadeDelete: false, - }, - { - name: 'users', - type: 'relation', - required: false, - maxSelect: 999, - minSelect: 0, - collectionId: USERS_AUTH, - cascadeDelete: false, - }, - { - name: 'created', - type: 'autodate', - onCreate: true, - onUpdate: false, - }, - { - name: 'updated', - type: 'autodate', - onCreate: true, - onUpdate: true, - }, - ], - } - - const created = await createCollection(body) - console.log(' + Collection "teams" angelegt') - return created -} - -// ---- Schritt 2: team-Feld zu events/riders/runs/times hinzufügen ---- - -async function addTeamField(collName: string) { - const coll = await getCollection(collName) - if (!coll) throw new Error(`Collection ${collName} fehlt`) - - if (hasField(coll, 'team')) { - console.log(` = ${collName}.team existiert bereits`) - return - } - - const fields = [ - ...coll.fields, - { - name: 'team', - type: 'relation', - required: false, - maxSelect: 1, - minSelect: 0, - collectionId: (await getCollection('teams'))!.id, - cascadeDelete: false, - }, - ] - - await updateCollection(collName, { fields }) - console.log(` + ${collName}.team hinzugefügt`) -} - -// ---- Schritt 3: Default-Team anlegen + alle Records zuordnen ---- - -async function migrateData(teamsId: string) { - console.log('\n== Daten-Migration ==') - - // Owner-User suchen - const ownerEmail = process.env.PB_OWNER_EMAIL || 'stammtisch-hersbruck@dne.name' - const usersRes: any = await req( - 'GET', - `/api/collections/${USERS_AUTH}/records?filter=` + encodeURIComponent(`email = "${ownerEmail}"`), - ) - if (!usersRes.items.length) { - throw new Error(`User mit E-Mail "${ownerEmail}" nicht gefunden. Setze PB_OWNER_EMAIL in .env.`) - } - const owner = usersRes.items[0] - console.log(` Owner: ${owner.email} (${owner.id})`) - - // Existierendes Team suchen - const teamsList: any = await req('GET', `/api/collections/teams/records?filter=` + encodeURIComponent(`name = "Stammtisch Hersbruck"`)) - let team: any = teamsList.items[0] - if (!team) { - // alle User aus events.users sammeln - const events = await listAll('events') - const userSet = new Set([owner.id]) - for (const e of events) { - if (Array.isArray(e.users)) for (const u of e.users) userSet.add(u) - } - team = await createRecord('teams', { - name: 'Stammtisch Hersbruck', - owner: owner.id, - admins: [], - users: [...userSet], - }) - console.log(` + Team "Stammtisch Hersbruck" mit ${userSet.size} Mitgliedern angelegt`) - } else { - console.log(` = Team "Stammtisch Hersbruck" existiert (${team.id})`) - } - - // Datensätze ohne team patchen - for (const collName of ['events', 'riders', 'runs', 'times']) { - const records = await listAll(collName) - let touched = 0 - for (const r of records) { - if (!r.team) { - await updateRecord(collName, r.id, { team: team.id }) - touched++ - } - } - console.log(` → ${collName}: ${touched} Records zugeordnet (${records.length - touched} bereits gesetzt)`) - } - - return team -} - -// ---- Schritt 4: Rules auf team-basiert umstellen ---- - -async function updateRules() { - console.log('\n== Rules auf team-basiert umstellen ==') - - const rules = { - events: { - listRule: 'team.users.id ?= @request.auth.id', - viewRule: 'team.users.id ?= @request.auth.id', - createRule: 'team.users.id ?= @request.auth.id', - updateRule: 'team.users.id ?= @request.auth.id', - deleteRule: 'team.users.id ?= @request.auth.id', - }, - riders: { - listRule: 'team.users.id ?= @request.auth.id', - viewRule: 'team.users.id ?= @request.auth.id', - createRule: 'team.users.id ?= @request.auth.id', - updateRule: 'team.users.id ?= @request.auth.id', - deleteRule: 'team.users.id ?= @request.auth.id', - }, - runs: { - listRule: 'team.users.id ?= @request.auth.id', - viewRule: 'team.users.id ?= @request.auth.id', - createRule: 'team.users.id ?= @request.auth.id', - updateRule: 'team.users.id ?= @request.auth.id', - deleteRule: 'team.users.id ?= @request.auth.id', - }, - times: { - listRule: 'team.users.id ?= @request.auth.id', - viewRule: 'team.users.id ?= @request.auth.id', - createRule: 'team.users.id ?= @request.auth.id', - updateRule: 'team.users.id ?= @request.auth.id', - deleteRule: 'team.users.id ?= @request.auth.id', - }, - } - - for (const [coll, r] of Object.entries(rules)) { - await updateCollection(coll, r) - console.log(` ~ ${coll} Rules aktualisiert`) - } -} - -// ---- Schritt 5: events.users entfernen ---- - -async function removeEventsUsers() { - console.log('\n== events.users entfernen ==') - const coll = await getCollection('events') - if (!coll) return - if (!hasField(coll, 'users')) { - console.log(' = events.users existiert nicht (mehr)') - return - } - const fields = coll.fields.filter((f: any) => f.name !== 'users') - await updateCollection('events', { fields }) - console.log(' - events.users entfernt') -} - -// ---- Main ---- - -async function main() { - console.log(`Migration gegen ${API}`) - const teams = await ensureTeamsCollection() - console.log('\n== team-Feld zu Collections hinzufügen ==') - await addTeamField('events') - await addTeamField('riders') - await addTeamField('runs') - await addTeamField('times') - await migrateData(teams.id) - await updateRules() - await removeEventsUsers() - console.log('\n✓ Teams-Migration fertig') -} - -main().catch((e) => { - console.error('\n✗ Fehler:', e.message) - process.exit(1) -}) diff --git a/frontend/scripts/seed-test-data.ts b/frontend/scripts/seed-test-data.ts deleted file mode 100644 index 87c129d..0000000 --- a/frontend/scripts/seed-test-data.ts +++ /dev/null @@ -1,329 +0,0 @@ -/** - * Test Data Seeding Script for PocketBase - * - * This script creates initial test data for the timing app: - * - Admin user - * - Timekeeper users - * - Test riders - * - Sample event with stages - * - * Usage: - * npx tsx scripts/seed-test-data.ts - * - * Environment Variables (from .env): - * PB_TYPEGEN_URL - PocketBase API URL - * PB_TYPEGEN_TOKEN - Authentication token for impersonation - */ - -import PocketBase from 'pocketbase' -import type { TypedPocketBase } from '../src/lib/types.js' - -// Validate required environment variables -const PB_URL = process.env.PB_TYPEGEN_URL -const PB_TOKEN = process.env.PB_TYPEGEN_TOKEN - -if (!PB_URL) { - console.error('❌ Error: PB_TYPEGEN_URL is not set in .env file') - console.error(' Please set PB_TYPEGEN_URL in your .env file') - process.exit(1) -} - -if (!PB_TOKEN) { - console.error('❌ Error: PB_TYPEGEN_TOKEN is not set in .env file') - console.error(' Please set PB_TYPEGEN_TOKEN in your .env file') - process.exit(1) -} - -// Initialize PocketBase with URL from .env -const pb = new PocketBase(PB_URL) as TypedPocketBase - -// Authenticate using token from .env (impersonation) -pb.authStore.save(PB_TOKEN) - -console.log(`🔗 Connecting to PocketBase at: ${PB_URL}`) -console.log(`🔑 Using authentication token from .env\n`) - -// Configuration -const ADMIN_EMAIL = 'admin@stammtisch-hersbruck.de' -const ADMIN_PASSWORD = 'Admin123!secure' - -async function seedData() { - console.log('🌱 Starting test data seeding...\n') - - try { - // 1. Create Admin User - console.log('👤 Creating admin user...') - let adminUser - try { - adminUser = await pb.collection('users').create({ - email: ADMIN_EMAIL, - password: ADMIN_PASSWORD, - passwordConfirm: ADMIN_PASSWORD, - name: 'Administrator', - role: 'admin', - verified: true - }) - console.log(`✅ Admin created: ${adminUser.email}`) - } catch (e: any) { - if (e.message.includes('already exists')) { - console.log('⚠️ Admin user already exists, logging in...') - await pb.collection('users').authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD) - adminUser = pb.authStore.record - } else { - throw e - } - } - - // 2. Create Timekeeper Users - console.log('\n⏱️ Creating timekeeper users...') - const timekeepers = [] - const timekeeperData = [ - { email: 'timer1@stammtisch-hersbruck.de', name: 'Max Zeitnehmer', password: 'Timer123!' }, - { email: 'timer2@stammtisch-hersbruck.de', name: 'Anna Stoppuhr', password: 'Timer123!' }, - { email: 'timer3@stammtisch-hersbruck.de', name: 'Tom Chronograph', password: 'Timer123!' } - ] - - for (const data of timekeeperData) { - try { - const user = await pb.collection('users').create({ - email: data.email, - password: data.password, - passwordConfirm: data.password, - name: data.name, - role: 'timekeeper', - verified: true - }) - timekeepers.push(user) - console.log(`✅ Timekeeper created: ${user.name} (${user.email})`) - } catch (e: any) { - if (e.message.includes('already exists')) { - console.log(`⚠️ Timekeeper ${data.email} already exists, skipping...`) - // Fetch existing user - const existing = await pb.collection('users').getFirstListItem(`email="${data.email}"`) - timekeepers.push(existing) - } else { - console.error(`❌ Error creating ${data.email}:`, e.message) - } - } - } - - // 3. Create Test Riders - console.log('\n🚴 Creating test riders...') - const riders = [] - const riderData = [ - { number: '1', name: 'Max Müller', email: 'max.mueller@example.com', birthday: '1995-03-15' }, - { number: '2', name: 'Anna Schmidt', email: 'anna.schmidt@example.com', birthday: '1992-07-22' }, - { number: '3', name: 'Tom Weber', email: 'tom.weber@example.com', birthday: '1998-11-08' }, - { number: '17', name: 'Lisa Hoffmann', email: 'lisa.hoffmann@example.com', birthday: '2000-01-30' }, - { number: '23', name: 'Felix Wagner', email: 'felix.wagner@example.com', birthday: '1996-05-12' }, - { number: '42', name: 'Sarah Becker', email: 'sarah.becker@example.com', birthday: '1994-09-25' }, - { number: '99', name: 'Lukas Fischer', email: 'lukas.fischer@example.com', birthday: '1999-12-03' }, - { number: '101', name: 'Julia Schulz', email: 'julia.schulz@example.com', birthday: '1997-04-18' } - ] - - for (const data of riderData) { - try { - const rider = await pb.collection('riders').create({ - number: data.number, - name: data.name, - email: data.email, - birthday: data.birthday - }) - riders.push(rider) - console.log(`✅ Rider created: #${rider.number} ${rider.name}`) - } catch (e: any) { - if (e.message.includes('already exists')) { - console.log(`⚠️ Rider #${data.number} already exists, skipping...`) - const existing = await pb.collection('riders').getFirstListItem(`number="${data.number}"`) - riders.push(existing) - } else { - console.error(`❌ Error creating rider #${data.number}:`, e.message) - } - } - } - - // 4. Create Test Event - console.log('\n🏁 Creating test event...') - let event - try { - // Event date: 2 weeks from now - const eventDate = new Date() - eventDate.setDate(eventDate.getDate() + 14) - - const startTime = new Date(eventDate) - startTime.setHours(9, 0, 0, 0) - - const endTime = new Date(eventDate) - endTime.setHours(17, 0, 0, 0) - - event = await pb.collection('events').create({ - name: 'MTB Hersbruck Cup 2025', - location: 'Hersbruck, Deutschland', - start: startTime.toISOString(), - end: endTime.toISOString(), - status: 'draft', - created_by: adminUser?.id, - timekeepers: timekeepers.map(t => t.id) - }) - console.log(`✅ Event created: ${event.name}`) - console.log(` Date: ${new Date(event.start).toLocaleDateString('de-DE')}`) - console.log(` Timekeepers: ${timekeepers.length} assigned`) - } catch (e: any) { - if (e.message.includes('already exists')) { - console.log('⚠️ Event already exists, fetching...') - event = await pb.collection('events').getFirstListItem('name~"MTB Hersbruck"') - } else { - throw e - } - } - - // 5. Create Stages - console.log('\n🏔️ Creating stages...') - const stages = [] - const stageData = [ - { - name: 'Downhill', - location: 'Steinberg Trail', - order: 1, - start_number: 1, - end_number: 50, - status: 'pending' - }, - { - name: 'Cross-Country', - location: 'Wald-Rundkurs', - order: 2, - start_number: 51, - end_number: 100, - status: 'pending' - }, - { - name: 'Enduro Stage 1', - location: 'Berg-Passage', - order: 3, - start_number: 101, - end_number: 150, - status: 'pending' - } - ] - - for (const data of stageData) { - try { - // Stage times based on event date - const stageStart = new Date(event.start) - stageStart.setHours(stageStart.getHours() + (data.order - 1) * 2) - - const stageEnd = new Date(stageStart) - stageEnd.setHours(stageEnd.getHours() + 2) - - const stage = await pb.collection('stages').create({ - event: event.id, - name: data.name, - location: data.location, - start: stageStart.toISOString(), - end: stageEnd.toISOString(), - order: data.order, - start_number: data.start_number, - end_number: data.end_number, - status: data.status - }) - stages.push(stage) - console.log(`✅ Stage created: ${stage.name} (Startnummern: ${data.start_number}-${data.end_number})`) - } catch (e: any) { - console.error(`❌ Error creating stage ${data.name}:`, e.message) - } - } - - // 6. Create Sample Results (optional - for demo) - console.log('\n⏱️ Creating sample results...') - - // Create 2 finished results for demonstration - if (stages.length > 0 && riders.length >= 3 && timekeepers.length > 0) { - const firstStage = stages[0] - const sampleResults = [ - { - rider: riders[0], - duration: 185.5, // 3:05.5 - status: 'finished' - }, - { - rider: riders[1], - duration: 192.3, // 3:12.3 - status: 'finished' - }, - { - rider: riders[2], - duration: 0, // currently running - status: 'running' - } - ] - - for (const data of sampleResults) { - try { - const now = new Date() - const startTime = new Date(now.getTime() - (data.duration * 1000) - 60000) // Started some time ago - const endTime = data.status === 'finished' ? new Date(startTime.getTime() + data.duration * 1000) : undefined - - const result = await pb.collection('results').create({ - stage: firstStage.id, - rider: data.rider.id, - start: startTime.toISOString(), - end: endTime?.toISOString() || null, - status: data.status, - started_by: timekeepers[0].id, - stopped_by: data.status === 'finished' ? timekeepers[0].id : null, - correction: 0 - }) - - const timeStr = data.status === 'finished' - ? `${Math.floor(data.duration / 60)}:${(data.duration % 60).toFixed(1).padStart(4, '0')}` - : 'RUNNING' - console.log(`✅ Result created: #${data.rider.number} ${data.rider.name} - ${timeStr}`) - } catch (e: any) { - console.error(`❌ Error creating result:`, e.message) - } - } - } - - // Summary - console.log('\n' + '='.repeat(50)) - console.log('✅ Test data seeding completed!') - console.log('='.repeat(50)) - console.log('\n📊 Summary:') - console.log(` Users: ${1 + timekeepers.length} (1 admin, ${timekeepers.length} timekeepers)`) - console.log(` Riders: ${riders.length}`) - console.log(` Events: 1`) - console.log(` Stages: ${stages.length}`) - console.log(` Results: 3 (2 finished, 1 running)`) - - console.log('\n🔑 Login Credentials:') - console.log(` Admin: ${ADMIN_EMAIL} / ${ADMIN_PASSWORD}`) - console.log(` Timekeeper: timer1@stammtisch-hersbruck.de / Timer123!`) - console.log(` Timekeeper: timer2@stammtisch-hersbruck.de / Timer123!`) - console.log(` Timekeeper: timer3@stammtisch-hersbruck.de / Timer123!`) - - console.log('\n🚀 Next steps:') - console.log(' 1. Run: npm run generate-pocketbase-types') - console.log(' 2. Start development: npm run dev') - console.log(' 3. Login with one of the credentials above') - console.log('') - - } catch (error: any) { - console.error('\n❌ Error during seeding:', error) - if (error.response) { - console.error('Response:', error.response) - } - process.exit(1) - } -} - -// Run the seeding -seedData() - .then(() => { - console.log('✅ Seeding completed successfully!') - process.exit(0) - }) - .catch((error) => { - console.error('❌ Seeding failed:', error) - process.exit(1) - }) \ No newline at end of file diff --git a/frontend/scripts/test-token.ts b/frontend/scripts/test-token.ts deleted file mode 100644 index 9eb51f2..0000000 --- a/frontend/scripts/test-token.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Test-Script: prüft was der PB_SUPERUSER_TOKEN tatsächlich darf. - * Aufruf: npx tsx scripts/test-token.ts - */ -import { readFileSync } from 'node:fs' - -const env = readFileSync('.env', 'utf8').split('\n') -let lastKey = '' -for (const line of env) { - const m = line.match(/^([A-Z_]+)=(.*)$/) - if (m) { - process.env[m[1]] = m[2] - lastKey = m[1] - } else if (lastKey && line.length > 0) { - process.env[lastKey] = (process.env[lastKey] ?? '') + line - } -} - -const TOKEN = process.env.PB_SUPERUSER_TOKEN ?? '' -const API = 'https://api.stammtisch-hersbruck.de' - -// JWT-Payload dekodieren -const [, payloadB64] = TOKEN.split('.') -if (payloadB64) { - const payload = JSON.parse(Buffer.from(payloadB64, 'base64').toString('utf8')) - console.log('Token-Payload:', payload) - console.log('Token-Länge:', TOKEN.length) -} - -async function tryEndpoint(label: string, path: string, init?: RequestInit) { - const res = await fetch(`${API}${path}`, { - ...init, - headers: { Authorization: TOKEN, 'Content-Type': 'application/json', ...(init?.headers ?? {}) }, - }) - const text = await res.text() - console.log(`\n${label}: ${res.status}`) - try { - console.log(JSON.stringify(JSON.parse(text), null, 2).slice(0, 200)) - } catch { - console.log(text.slice(0, 200)) - } -} - -await tryEndpoint('GET /api/collections', '/api/collections?perPage=1') -await tryEndpoint('GET /api/collections/_superusers', '/api/collections/_superusers') -await tryEndpoint('GET /api/collections/events', '/api/collections/events') -await tryEndpoint('POST /api/collections (Probe)', '/api/collections', { - method: 'POST', - body: JSON.stringify({ - name: '__probe_temp__', - type: 'base', - fields: [{ name: 'foo', type: 'text' }], - }), -})