Die SvelteKit-App liegt unter frontend/, das Backend unter backend/ als eigenständige PocketBase-Instanz mit Dockerfile, docker-compose und versioniertem Schema. - PocketBase-URL über PUBLIC_PB_URL konfigurierbar, Default bleibt die produktive Instanz https://api.stammtisch-hersbruck.de - Schema als Snapshot-Migration der sechs fachlichen Collections (users, teams, events, runs, riders, times), abgezogen von der produktiven Instanz. Verifiziert: ein Erststart gegen leere pb_data legt alle sechs an, Felder und API-Rules stimmen überein. - pb_data und pb_migrations als Bind-Mounts, damit Daten persistieren und im Admin-UI erzeugte Migrationen im Repo landen - Veraltete Dokumentation entfernt: pocketbase_schema.json nannte Collections (stages, results, organizers), die es nicht gibt Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
116 lines
3.6 KiB
TypeScript
116 lines
3.6 KiB
TypeScript
import { getContext, setContext } from 'svelte'
|
|
import { api } from './pocketbase.svelte'
|
|
import { app } from './app.svelte'
|
|
import { getTeamContext } from './teams.svelte'
|
|
import type { EventsResponse } from '$lib/types'
|
|
|
|
const KEY = Symbol('event')
|
|
|
|
export class EventStore {
|
|
records = $state<EventsResponse[]>([])
|
|
loading = $state(false)
|
|
error = $state<string | null>(null)
|
|
|
|
private unsubscribe: (() => void) | null = null
|
|
private teams = getTeamContext()
|
|
|
|
/** Records gefiltert nach aktivem Team */
|
|
get scoped(): EventsResponse[] {
|
|
const teamId = this.teams.activeId
|
|
if (!teamId) return []
|
|
return this.records.filter((r) => r.team === teamId)
|
|
}
|
|
|
|
async load() {
|
|
this.loading = true
|
|
this.error = null
|
|
try {
|
|
this.records = await api.collection('events').getFullList({
|
|
sort: '-created',
|
|
requestKey: null,
|
|
})
|
|
} catch (e: any) {
|
|
this.error = e.message ?? 'Fehler beim Laden der Events'
|
|
console.error(e)
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
}
|
|
|
|
subscribe() {
|
|
if (this.unsubscribe) return
|
|
api.collection('events').subscribe('*', (e) => {
|
|
const idx = this.records.findIndex((r) => r.id === e.record.id)
|
|
if (e.action === 'create' && idx === -1) this.records = [e.record, ...this.records]
|
|
else if (e.action === 'update' && idx !== -1) this.records[idx] = e.record
|
|
else if (e.action === 'delete' && idx !== -1) this.records = this.records.filter((r) => r.id !== e.record.id)
|
|
}).then((unsub) => {
|
|
this.unsubscribe = unsub
|
|
}).catch((e) => console.warn('events subscribe failed:', e))
|
|
}
|
|
|
|
async create(data: Partial<EventsResponse>) {
|
|
const teamId = this.teams.activeId
|
|
if (!teamId) throw new Error('Kein aktives Team')
|
|
const rec = await api.collection('events').create({ ...data, team: teamId })
|
|
return rec
|
|
}
|
|
|
|
async edit(id: string, data: Partial<EventsResponse>) {
|
|
return await api.collection('events').update(id, data)
|
|
}
|
|
|
|
remove(record: EventsResponse) {
|
|
return new Promise<void>((resolve, reject) => {
|
|
app.confirm.request({
|
|
title: 'Event löschen?',
|
|
text: `Möchten Sie das Event „${record.name || record.description}" wirklich löschen?`,
|
|
yes: async () => {
|
|
try {
|
|
await api.collection('events').delete(record.id)
|
|
app.confirm.close()
|
|
resolve()
|
|
} catch (e) {
|
|
app.confirm.close()
|
|
reject(e)
|
|
}
|
|
},
|
|
no: () => {
|
|
app.confirm.close()
|
|
resolve()
|
|
},
|
|
})
|
|
})
|
|
}
|
|
|
|
getById(id: string) {
|
|
return this.records.find((r) => r.id === id)
|
|
}
|
|
|
|
get active() {
|
|
return this.scoped.filter((r) => r.status === 'active')
|
|
}
|
|
get drafts() {
|
|
return this.scoped.filter((r) => r.status === 'draft')
|
|
}
|
|
get finished() {
|
|
return this.scoped.filter((r) => r.status === 'finished')
|
|
}
|
|
|
|
destroy() {
|
|
if (this.unsubscribe) {
|
|
this.unsubscribe()
|
|
this.unsubscribe = null
|
|
}
|
|
}
|
|
}
|
|
|
|
export function setEventContext() {
|
|
const store = new EventStore()
|
|
setContext(KEY, store)
|
|
return store
|
|
}
|
|
|
|
export function getEventContext(): EventStore {
|
|
return getContext<EventStore>(KEY)
|
|
}
|