Kein fertiger Stand, sondern ein Zwischenstand: Hier wurde im September 2026 angefangen, die App auf „Kurbeler" umzubenennen — Wortmarke, Icons, brand.ts, Benachrichtigungen, das Benutzerverzeichnis. Weitergegangen ist es dann im eigenen Repo `~/Dokumente/Projekte/kurbeler`, das die vollständige Historie dieses Repos mitgenommen hat. Committet wird das hier nicht, weil es gebraucht würde, sondern damit es nicht als Haufen unversionierter Dateien im Arbeitsverzeichnis verrottet. Wer in zwei Jahren nachsieht, findet so eine Geschichte statt eines Rätsels — und das Stammtisch-Wappen, das nur hier je existiert hat. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LTw6xVYgjMy9AQtfs1Gdyu
112 lines
3.4 KiB
JavaScript
112 lines
3.4 KiB
JavaScript
/// <reference path="../pb_data/types.d.ts" />
|
|
|
|
// Alles, was in dieser Anwendung eine Mail verschickt, geht hier durch.
|
|
//
|
|
// Eigene Datei, weil PocketBase jeden Route- und Event-Handler in einer
|
|
// eigenen JS-Laufzeit ausführt: Was neben routerAdd steht, sieht der Handler
|
|
// nicht. Er holt sich seine Helfer per require().
|
|
//
|
|
// Drei Dinge stehen hier, die sonst in jeder Mail neu erfunden würden:
|
|
// woher der Name der App kommt, woher die Adresse des Frontends kommt, und
|
|
// wie aus einer Vorlage samt Daten eine verschickte Mail wird.
|
|
|
|
const DEFAULT_APP_NAME = 'Kurbeler'
|
|
|
|
/**
|
|
* Name der Anwendung, wie er in Mails auftaucht.
|
|
*
|
|
* Über `APP_NAME` überschreibbar — eine Testinstanz soll in der Betreffzeile
|
|
* erkennbar sein, ohne dass jemand den Code anfasst.
|
|
*/
|
|
function appName() {
|
|
return $os.getenv('APP_NAME') || DEFAULT_APP_NAME
|
|
}
|
|
|
|
/**
|
|
* Adresse des Frontends, ohne Schrägstrich am Ende.
|
|
*
|
|
* Das ist NICHT `settings().meta.appURL`: Die zeigt auf PocketBase selbst,
|
|
* und ein Einladungslink dorthin führte auf die API statt auf die Seite, die
|
|
* das Konto anlegt.
|
|
*
|
|
* Und es ist ausdrücklich nicht der `Origin`-Header des Aufrufers. Der steht
|
|
* unter der Kontrolle dessen, der die Anfrage stellt — ein Link in einer
|
|
* Mail, den ein Fremder auf seinen eigenen Server zeigen lassen kann, ist
|
|
* eine Phishing-Vorlage mit unserem Absender darauf.
|
|
*
|
|
* Fehlt die Variable, gibt es keine Mail. Lieber ein Fehler, den die
|
|
* Teamleitung sieht, als ein Link ins Leere, den sie nie zu Gesicht bekommt.
|
|
*/
|
|
function appURL() {
|
|
const raw = ($os.getenv('APP_URL') || '').trim()
|
|
if (!raw) {
|
|
return ''
|
|
}
|
|
|
|
return raw.replace(/\/+$/, '')
|
|
}
|
|
|
|
/** Eine Vorlage aus pb_hooks/views/ mit Daten füllen. */
|
|
function render(view, data) {
|
|
return $template.loadFiles(`${__hooks}/views/${view}`).render(data)
|
|
}
|
|
|
|
/**
|
|
* Absender aus den PocketBase-Einstellungen. Dort wird auch SMTP eingerichtet;
|
|
* beides gehört ins Admin-UI und nicht in den Code.
|
|
*/
|
|
function sender(app) {
|
|
const meta = app.settings().meta
|
|
|
|
return {
|
|
address: meta.senderAddress,
|
|
name: meta.senderName || appName(),
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mail verschicken. Wirft weiter, wenn der Versand scheitert — ob das den
|
|
* Aufrufer scheitern lässt, entscheidet er selbst: Eine Einladung ohne Mail
|
|
* ist wertlos und muss scheitern, eine Benachrichtigung darf im Stillen
|
|
* verloren gehen, statt das Speichern eines Events zu verhindern.
|
|
*/
|
|
function send(app, to, subject, html) {
|
|
const message = new MailerMessage({
|
|
from: sender(app),
|
|
to: [{ address: to.address, name: to.name || '' }],
|
|
subject: subject,
|
|
html: html,
|
|
})
|
|
|
|
app.newMailClient().send(message)
|
|
}
|
|
|
|
/** Vorlage rendern und verschicken — der übliche Weg. */
|
|
function sendView(app, to, subject, view, data) {
|
|
send(app, to, subject, render(view, data))
|
|
}
|
|
|
|
/**
|
|
* Ein Datum, wie es in einer Mail stehen soll: „30.09.2026".
|
|
*
|
|
* PocketBase liefert „2026-09-30 10:00:00.000Z"; das direkt hineinzuschreiben
|
|
* sähe aus wie ein Datenbankfeld und nicht wie ein Satz.
|
|
*/
|
|
function formatDay(value) {
|
|
const day = (value || '').slice(0, 10)
|
|
if (day.length !== 10) {
|
|
return ''
|
|
}
|
|
|
|
const parts = day.split('-')
|
|
return `${parts[2]}.${parts[1]}.${parts[0]}`
|
|
}
|
|
|
|
module.exports = {
|
|
appName: appName,
|
|
appURL: appURL,
|
|
render: render,
|
|
send: send,
|
|
sendView: sendView,
|
|
formatDay: formatDay,
|
|
}
|