/// // Einladungslinks: Ein Team gibt einen Link heraus, über den sich Fahrer // selbst ein Konto anlegen und dem Team beitreten. // // Bisher ging das nur andersherum — die Teamleitung legte jedem Fahrer von // Hand ein Konto an und gab das Passwort weiter. Bei zwanzig Leuten in einer // WhatsApp-Gruppe ist ein Link die kürzere Strecke. // // Der Link ist das Geheimnis. Deshalb: // // 1) `token` wird von PocketBase erzeugt (autogeneratePattern) und ist // eindeutig. Ein mitgeschickter Token wird über die createRule abgelehnt, // sonst könnte sich jemand einen leicht zu ratenden aussuchen. // // 2) Lesen, anlegen und löschen darf nur, wer das Team führt. Es gibt // bewusst keine updateRule: Ein Link ändert sich nicht, er wird gelöscht // und neu erzeugt. Den Zähler `uses` schreibt der Hook, der an den Regeln // vorbei arbeitet. // // 3) Die öffentliche Seite der Einladung läuft nicht über diese Collection, // sondern über die Routen in pb_hooks/team_invites.pb.js. Ohne Login ist // hier nichts sichtbar — auch nicht mit Token. // // `expires` leer heißt: läuft nicht ab. `max_uses` 0 heißt: beliebig oft. // Der Login-Vorspann ist nicht bloß Gürtel und Hosenträger: Ist `admins` // leer, vergleicht PocketBase eine leere Relation mit dem leeren // `@request.auth.id` einer anonymen Anfrage — und das ist wahr. Ohne den // Vorspann stünde jede Einladung eines Teams ohne Admins offen im Netz. // Siehe 1754501700_rules_require_login.js. const ADMIN_RULE = '@request.auth.id != ""' + ' && (team.owner.id ?= @request.auth.id || team.admins.id ?= @request.auth.id)' migrate((app) => { const teams = app.findCollectionByNameOrId('teams') const users = app.findCollectionByNameOrId('users') try { app.findCollectionByNameOrId('team_invites') return } catch { // Noch nicht da — wird gleich angelegt. } const invites = new Collection({ id: 'pbc_team_invites', type: 'base', name: 'team_invites', listRule: ADMIN_RULE, viewRule: ADMIN_RULE, // Der Token kommt vom Server. `:isset = false` weist einen // mitgeschickten ab, statt ihn stillschweigend zu übernehmen. createRule: ADMIN_RULE + ' && @request.body.token:isset = false', updateRule: null, deleteRule: ADMIN_RULE, fields: [ { id: 'relation_invite_team', name: 'team', type: 'relation', required: true, maxSelect: 1, collectionId: teams.id, cascadeDelete: true, }, { id: 'text_invite_token', name: 'token', type: 'text', required: true, min: 32, max: 32, autogeneratePattern: '[a-zA-Z0-9]{32}', }, { id: 'relation_invite_created_by', name: 'created_by', type: 'relation', maxSelect: 1, collectionId: users.id, cascadeDelete: false, }, { id: 'date_invite_expires', name: 'expires', type: 'date' }, { id: 'number_invite_max_uses', name: 'max_uses', type: 'number', min: 0, onlyInt: true }, { id: 'number_invite_uses', name: 'uses', type: 'number', min: 0, onlyInt: true }, { id: 'autodate_created', name: 'created', type: 'autodate', onCreate: true, onUpdate: false }, { id: 'autodate_updated', name: 'updated', type: 'autodate', onCreate: true, onUpdate: true }, ], indexes: [ 'CREATE UNIQUE INDEX `idx_team_invites_token` ON `team_invites` (`token`)', ], }) app.save(invites) }, (app) => { app.delete(app.findCollectionByNameOrId('team_invites')) })