Damit sind alle offenen Punkte aus TODO.md gebaut. Fahrer gehoeren ab jetzt dem Team, nicht mehr einem Event. Wer bei einem Termin dabei ist, steht in event_participants - dort haengt auch die Startnummer, denn die gilt je Event: Derselbe Fahrer kann in zwei Rennen zwei Nummern haben und beim Stammtisch gar keine. Die vorhandenen Daten wandern in der Migration mit. Events tragen zwei Schalter. timing blendet Runs und Zeitnahme ein - der woechentliche Stammtisch braucht sie nicht. participation regelt, wie man dazukommt: offen (jeder traegt sich selbst ein), bestaetigung (Anmeldung, Ersteller oder Team-Admin bestaetigt) oder geschlossen. Durchgesetzt wird das serverseitig, nicht nur im Formular. Serien erzeugen echte Einzeltermine statt einer Wiederholungsregel, aus der Termine nur gerechnet werden. Nur so laesst sich beantworten, wer am 12. kommt, und nur so faellt eine einzelne Woche aus. Gerechnet wird auf Kalendertagen: ueber die Zeitumstellung hinweg kippte ein Termin sonst auf den Vortag - dafuer gibt es einen Test. Ausserdem: Teammitglieder duerfen den Zustand eines Trails melden. Die Regel gibt genau die drei Statusfelder frei und schliesst jedes andere aus; ohne das haette man Mitgliedern das ganze Objekt oeffnen muessen. Und bestehende Konten lassen sich wieder per E-Mail ins Team holen, wofuer users.listRule sich oeffnet. Die neuen Regeln sind ungetestet - weder Docker noch eine lokale PocketBase-Instanz standen zur Verfuegung. TODO.md sagt, was nach dem Deploy durchzuspielen ist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
93 lines
3.3 KiB
JavaScript
93 lines
3.3 KiB
JavaScript
/// <reference path="../pb_data/types.d.ts" />
|
|
|
|
// Events werden zu Ausfahrten, Rennen, Stammtischen — je nach Schaltern.
|
|
//
|
|
// `timing` blendet Runs und Zeitnahme ein. Der wöchentliche Stammtisch
|
|
// braucht das nicht, das Rennen schon; ein zweites Konzept neben Events wäre
|
|
// dafür zu viel gewesen.
|
|
//
|
|
// `participation` legt fest, wie man teilnimmt:
|
|
// offen — jedes Teammitglied trägt sich selbst ein
|
|
// bestaetigung — Anmeldung möglich, Ersteller oder Team-Admin bestätigt
|
|
// geschlossen — nur Ersteller oder Team-Admin trägt ein
|
|
//
|
|
// Bestehende Events bekommen `timing = true` und `geschlossen`: Genau so
|
|
// wurden sie bisher benutzt.
|
|
|
|
const MANAGER =
|
|
'event.created_by.id ?= @request.auth.id' +
|
|
' || event.team.owner.id ?= @request.auth.id' +
|
|
' || event.team.admins.id ?= @request.auth.id'
|
|
|
|
// Sich selbst eintragen: nur den eigenen Datensatz, nur mit dem Status, den
|
|
// der Modus vorsieht. Ohne die Statusprüfung könnte man sich im
|
|
// Bestätigungsmodus gleich selbst bestätigen.
|
|
//
|
|
// Geprüft wird auf den Feldern des entstehenden Datensatzes statt über
|
|
// @request.body: Eine Relation aus dem Request-Body heraus weiterzuverfolgen
|
|
// (@request.body.rider.user) ist nicht verlässlich.
|
|
const SELF_SIGNUP =
|
|
'event.team.users.id ?= @request.auth.id' +
|
|
' && rider.user.id ?= @request.auth.id' +
|
|
' && (' +
|
|
'(event.participation = "offen" && status = "zugesagt")' +
|
|
' || (event.participation = "bestaetigung" && status = "angefragt")' +
|
|
')'
|
|
|
|
// Die eigene Teilnahme darf man ändern — aber nicht in eine Bestätigung.
|
|
const SELF_UPDATE =
|
|
'rider.user.id ?= @request.auth.id' +
|
|
' && @request.body.rider:isset = false' +
|
|
' && @request.body.event:isset = false' +
|
|
' && @request.body.number:isset = false' +
|
|
' && @request.body.status != "zugesagt"'
|
|
|
|
migrate((app) => {
|
|
const events = app.findCollectionByNameOrId('events')
|
|
|
|
if (!events.fields.getByName('timing')) {
|
|
events.fields.add(new Field({
|
|
id: 'bool_event_timing',
|
|
name: 'timing',
|
|
type: 'bool',
|
|
}))
|
|
}
|
|
|
|
if (!events.fields.getByName('participation')) {
|
|
events.fields.add(new Field({
|
|
id: 'select_event_participation',
|
|
name: 'participation',
|
|
type: 'select',
|
|
maxSelect: 1,
|
|
values: ['offen', 'bestaetigung', 'geschlossen'],
|
|
}))
|
|
}
|
|
|
|
app.save(events)
|
|
|
|
for (const event of app.findRecordsByFilter('events', 'id != ""', '', 0, 0)) {
|
|
event.set('timing', true)
|
|
event.set('participation', 'geschlossen')
|
|
app.save(event)
|
|
}
|
|
|
|
const participants = app.findCollectionByNameOrId('event_participants')
|
|
participants.createRule = `${MANAGER} || (${SELF_SIGNUP})`
|
|
participants.updateRule = `${MANAGER} || (${SELF_UPDATE})`
|
|
// Absagen heißt Status ändern, nicht löschen — sonst verschwindet die
|
|
// Historie einer Anmeldung.
|
|
participants.deleteRule = MANAGER
|
|
app.save(participants)
|
|
}, (app) => {
|
|
const events = app.findCollectionByNameOrId('events')
|
|
|
|
events.fields.removeByName('timing')
|
|
events.fields.removeByName('participation')
|
|
app.save(events)
|
|
|
|
const participants = app.findCollectionByNameOrId('event_participants')
|
|
participants.createRule = MANAGER
|
|
participants.updateRule = MANAGER
|
|
participants.deleteRule = MANAGER
|
|
app.save(participants)
|
|
})
|