fix: Fahrer fuer Teammitglieder, die keinen mehr haben

Bis zuletzt loeschte die Anwendung einen Fahrer, ohne sein Konto aus
teams.users zu nehmen. Zurueck blieb ein Konto ohne Fahrer — im Kader
eine Zeile, aus der kein Weg zurueckfuehrte: Ein bestehendes Konto
nachtraeglich an einen Fahrer zu haengen gibt es bewusst nicht, weil
dieselbe Person sonst zweimal in der Liste stehen koennte.

riders.remove nimmt die Mitgliedschaft inzwischen mit; diese Migration
raeumt auf, was vorher liegengeblieben ist, und stellt die Regel her,
auf die sich die Oberflaeche verlassen darf: Wer im Team ist, steht im
Kader.

Kein Rueckweg: Die hier angelegten Fahrer sind hinterher nicht mehr von
denen zu unterscheiden, die die Teamleitung selbst angelegt hat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
This commit is contained in:
Daniel Michelberger 2026-09-07 22:11:44 +02:00
parent 041bed923b
commit 3120bbfdbf

View file

@ -0,0 +1,81 @@
/// <reference path="../pb_data/types.d.ts" />
// Jedes Teammitglied bekommt einen Fahrer.
//
// Bis 1754501800 löschte die Anwendung einen Fahrer, ohne sein Konto aus
// `teams.users` zu nehmen. Zurück blieb ein Konto ohne Fahrer — im Kader eine
// Zeile ohne Namen, aus der kein Weg zurückführte: Ein bestehendes Konto
// nachträglich an einen Fahrer zu hängen gibt es bewusst nicht, weil dieselbe
// Person sonst zweimal in der Liste stehen könnte. Der Eintrag war damit
// unreparierbar.
//
// Das Leck ist zu (riders.remove nimmt die Mitgliedschaft mit). Diese
// Migration räumt auf, was es hinterlassen hat, und stellt damit die Regel
// her, auf die sich die Oberfläche künftig verlassen darf:
//
// Wer im Team ist, steht im Kader.
//
// Der Name kommt vom Konto: erst der Kontoname, sonst der Teil der
// E-Mail-Adresse vor dem @. Beides kann später von der Teamleitung geändert
// werden — der Fahrername ist ab dann die einzige Wahrheit, der Kontoname
// taucht in der Oberfläche nirgends mehr auf.
migrate((app) => {
const teams = app.findRecordsByFilter('teams', 'id != ""', '', 0, 0)
let created = 0
for (const team of teams) {
// getStringSlice statt get: `get` gäbe bei einer Mehrfach-Relation ein
// Go-Slice zurück, auf dem sich weder includes noch map verlässlich
// verhalten.
const memberIds = team.getStringSlice('users') || []
if (memberIds.length === 0) continue
const taken = new Set()
for (const rider of app.findRecordsByFilter(
'riders',
'team = {:team} && user != ""',
'',
0,
0,
{ team: team.id },
)) {
taken.add(rider.getString('user'))
}
for (const userId of memberIds) {
if (taken.has(userId)) continue
let user
try {
user = app.findRecordById('users', userId)
} catch (err) {
// Konto gelöscht, die Relation zeigt ins Leere. Ein Fahrer für
// ein Konto, das es nicht gibt, wäre kein Gewinn.
continue
}
const email = user.getString('email')
const name =
user.getString('name').trim() ||
(email.indexOf('@') > 0 ? email.slice(0, email.indexOf('@')) : '') ||
'Ohne Namen'
const rider = new Record(app.findCollectionByNameOrId('riders'))
rider.set('team', team.id)
rider.set('user', userId)
rider.set('name', name)
app.save(rider)
taken.add(userId)
created++
}
}
console.log(`riders_for_orphan_members: ${created} Fahrer angelegt`)
}, (app) => {
// Kein Rückweg. Die hier angelegten Fahrer sind hinterher nicht mehr von
// denen zu unterscheiden, die die Teamleitung danach selbst angelegt hat —
// sie zu löschen träfe mit einiger Wahrscheinlichkeit die falschen, samt
// ihrer Teilnahmen und Zeiten. Ein Fahrer zu viel ist der kleinere Schaden.
})