feat: pro Mandant editierbare E-Mail-Vorlagen
Zweistufig wie der Werkzeugkatalog: Betreiber pflegt den plattformweiten Standard (/betreiber/email-vorlagen), jeder Mandant kann ihn für sich übersteuern (/verwaltung/email-vorlagen) - ResolveEmailVorlage nutzt die eigene Vorlage falls vorhanden, sonst fällt sie auf den Plattform- Standard zurück. Passwort-Zurücksetzen ist die einzige aktuell existierende E-Mail und nutzt jetzt diese Vorlage statt Hardcoding. Bug beim Live-Verifizieren gefunden: UNIQUE(account_id, typ) verhindert bei NULLABLE account_id keine Duplikate (NULL != NULL in SQL) - jedes Speichern des Plattform-Standards erzeugte eine neue Zeile statt sie zu aktualisieren. Fix: zwei partielle Unique-Indizes statt eines gemeinsamen Constraints, mit Regressionstest abgesichert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
4
internal/store/migrations/0022_email_vorlage.down.sql
Normal file
4
internal/store/migrations/0022_email_vorlage.down.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE email_vorlage NO FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE email_vorlage DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY IF EXISTS tenant_isolation ON email_vorlage;
|
||||
DROP TABLE email_vorlage;
|
||||
55
internal/store/migrations/0022_email_vorlage.up.sql
Normal file
55
internal/store/migrations/0022_email_vorlage.up.sql
Normal file
@@ -0,0 +1,55 @@
|
||||
-- Editierbare E-Mail-Vorlagen, zweistufig wie der Werkzeugkatalog:
|
||||
-- account_id NULL = plattformweiter Standard (nur vom Betreiber
|
||||
-- editierbar, z. B. künftige E-Mails, die die Plattform selbst an
|
||||
-- Mandanten-Admins schickt), account_id gesetzt = mandantenspezifische
|
||||
-- Übersteuerung (vom Mandanten-Admin editierbar, z. B. eigener Ton/
|
||||
-- Branding für eine E-Mail, die an die eigenen Mitarbeiter geht).
|
||||
-- "typ" identifiziert, welche vom System versendete E-Mail gemeint ist
|
||||
-- (aktuell nur "passwort_zuruecksetzen" — die einzige E-Mail, die das
|
||||
-- System bisher verschickt, siehe internal/mail). Text darf Platzhalter
|
||||
-- wie "{{link}}" enthalten, die beim Versand ersetzt werden (siehe
|
||||
-- internal/web/email_vorlage_handlers.go).
|
||||
CREATE TABLE email_vorlage (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
account_id UUID REFERENCES account(id),
|
||||
typ TEXT NOT NULL,
|
||||
betreff TEXT NOT NULL,
|
||||
text TEXT NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ACHTUNG: ein einfaches UNIQUE (account_id, typ) würde NICHT reichen —
|
||||
-- SQL behandelt NULL nie als gleich zu NULL, ein normaler UNIQUE-
|
||||
-- Constraint hätte also beliebig viele Plattform-Standard-Zeilen
|
||||
-- (account_id IS NULL) je typ zugelassen. Zwei partielle Unique-Indizes
|
||||
-- statt eines gemeinsamen Constraints, dafür braucht UpsertEmailVorlage
|
||||
-- zwei unterschiedliche ON-CONFLICT-Ziele (siehe internal/store/email_vorlage.go).
|
||||
CREATE UNIQUE INDEX email_vorlage_plattform_uidx ON email_vorlage (typ) WHERE account_id IS NULL;
|
||||
CREATE UNIQUE INDEX email_vorlage_mandant_uidx ON email_vorlage (account_id, typ) WHERE account_id IS NOT NULL;
|
||||
|
||||
-- Plattformweiter Standard für die einzige aktuell existierende
|
||||
-- E-Mail — ohne diese Zeile gäbe es nichts, worauf ResolveEmailVorlage
|
||||
-- zurückfallen könnte, solange ein Mandant keine eigene Vorlage hat.
|
||||
INSERT INTO email_vorlage (account_id, typ, betreff, text) VALUES (
|
||||
NULL,
|
||||
'passwort_zuruecksetzen',
|
||||
'Deklarix — Passwort zurücksetzen',
|
||||
'Hallo,' || E'\n\n' ||
|
||||
'über diesen Link kannst du dein Deklarix-Passwort zurücksetzen (gültig 1 Stunde):' || E'\n' ||
|
||||
'{{link}}' || E'\n\n' ||
|
||||
'Falls du das nicht angefordert hast, ignoriere diese E-Mail.'
|
||||
);
|
||||
|
||||
-- Wie werkzeug (account_id NULLABLE): NULL-Zeilen sind für alle lesbar,
|
||||
-- aber nur vom Betreiber schreibbar; ein Mandant darf nur seine eigene
|
||||
-- account_id-Zeile anlegen/ändern.
|
||||
CREATE POLICY tenant_isolation ON email_vorlage FOR ALL USING (
|
||||
account_id IS NULL
|
||||
OR account_id = NULLIF(current_setting('app.account_id', true), '')::uuid
|
||||
OR current_setting('app.is_betreiber', true) = 'true'
|
||||
) WITH CHECK (
|
||||
(account_id IS NULL AND current_setting('app.is_betreiber', true) = 'true')
|
||||
OR account_id = NULLIF(current_setting('app.account_id', true), '')::uuid
|
||||
);
|
||||
ALTER TABLE email_vorlage ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE email_vorlage FORCE ROW LEVEL SECURITY;
|
||||
Reference in New Issue
Block a user