/// // Die öffentliche Seite der Einladungslinks. // // Warum überhaupt eigene Routen? Wer einem Team über einen Link beitritt, ist // noch nicht eingeloggt und muss dabei drei Dinge tun, die keine // Collection-Regel erlauben darf: // // 1. ein Konto anlegen — `users.createRule` verlangt einen Login, sonst // wäre es eine offene Registrierung für jedermann; // 2. sich in `teams.users` eintragen — das darf sonst nur die Teamleitung; // 3. einen Fahrer im Kader anlegen — dito. // // Ein Hook läuft an den Regeln vorbei und kann deshalb genau diese drei // Schritte erlauben, aber nur zusammen und nur gegen einen gültigen Token. // Die Collection `team_invites` selbst bleibt für Nicht-Mitglieder unsichtbar. // // Routen: // GET /api/team-invites/{token} — öffentlich, zeigt das Team // POST /api/team-invites/{token}/accept — öffentlich, Konto + Beitritt // POST /api/team-invites/{token}/join — eingeloggt, nur Beitritt // // Jeder Handler holt sich seine Helfer selbst per require(): PocketBase führt // Route-Handler in einer eigenen JS-Laufzeit aus, der Dateikopf hier ist dort // nicht sichtbar. // --- Wofür gilt der Link? --------------------------------------------------- // // Öffentlich, weil die Seite den Teamnamen zeigen soll, bevor jemand seine // Daten einträgt. Mehr als Name und Logo gibt sie nicht preis; die Einladung // selbst — Zähler, Ablauf, wer sie erzeugt hat — bleibt drin. routerAdd('GET', '/api/team-invites/{token}', (e) => { const lib = require(`${__hooks}/team_invites_lib.js`) const invite = lib.findValidInvite(e.app, e.request.pathValue('token')) if (!invite) { throw new NotFoundError('Diese Einladung gibt es nicht mehr.') } return e.json(200, { team: lib.teamPayload(e.app, invite) }) }) // --- Konto anlegen und beitreten ------------------------------------------- routerAdd('POST', '/api/team-invites/{token}/accept', (e) => { const lib = require(`${__hooks}/team_invites_lib.js`) const data = new DynamicModel({ email: '', password: '', passwordConfirm: '', name: '', }) e.bindBody(data) const email = (data.email || '').trim() const name = (data.name || '').trim() if (!name) { throw new BadRequestError('Bitte einen Namen angeben.') } if (email.indexOf('@') < 1) { throw new BadRequestError('Bitte eine gültige E-Mail-Adresse angeben.') } if ((data.password || '').length < 8) { throw new BadRequestError('Das Passwort braucht mindestens 8 Zeichen.') } if (data.password !== data.passwordConfirm) { throw new BadRequestError('Die beiden Passwörter stimmen nicht überein.') } let result e.app.runInTransaction((txApp) => { // Innerhalb der Transaktion noch einmal nachsehen: Zwischen Anzeige // der Seite und dem Absenden kann die Einladung abgelaufen, // aufgebraucht oder gelöscht worden sein. const invite = lib.findValidInvite(txApp, e.request.pathValue('token')) if (!invite) { throw new BadRequestError('Diese Einladung gilt nicht mehr.') } // Ohne die Vorabprüfung meldete PocketBase nur „Value must be unique". // Der Hinweis auf den zweiten Weg ist hier mehr wert als die // Fehlermeldung. let existing = null try { existing = txApp.findAuthRecordByEmail('users', email) } catch (err) { existing = null } if (existing) { throw new BadRequestError( 'Zu dieser E-Mail-Adresse gibt es schon ein Konto. ' + 'Melde dich damit an und öffne den Link danach erneut.', ) } const user = new Record(txApp.findCollectionByNameOrId('users')) user.setEmail(email) user.setPassword(data.password) user.setVerified(false) user.set('emailVisibility', false) user.set('name', name) txApp.save(user) result = lib.joinTeam(txApp, invite, user, name) }) // Kein Auth-Token in der Antwort: Die App meldet sich anschließend ganz // normal mit E-Mail und Passwort an und läuft damit durch dieselbe // Anmeldung wie jeder andere auch. return e.json(200, { ok: true, team: result.teamId }) }) // --- Als bereits angemeldeter Nutzer beitreten ------------------------------ routerAdd('POST', '/api/team-invites/{token}/join', (e) => { const lib = require(`${__hooks}/team_invites_lib.js`) const data = new DynamicModel({ name: '' }) e.bindBody(data) const user = e.auth if (!user) { throw new UnauthorizedError() } let result e.app.runInTransaction((txApp) => { const invite = lib.findValidInvite(txApp, e.request.pathValue('token')) if (!invite) { throw new BadRequestError('Diese Einladung gilt nicht mehr.') } // Fällt der Name weg, stünde der Beitretende ohne Namen im Kader. Der // Kontoname ist die bessere Näherung als gar nichts. const name = (data.name || '').trim() || user.getString('name') result = lib.joinTeam(txApp, invite, user, name) }) return e.json(200, { ok: true, team: result.teamId }) }, $apis.requireAuth())