import type { CollectionConfig, FieldHook } from 'payload' import { invalidateEmailCacheHook } from '../hooks/invalidateEmailCache' import { auditTenantAfterChange, auditTenantAfterDelete } from '../hooks/auditTenantChanges' import { neverReadable } from '../lib/access' /** * Validiert SMTP Host Format */ const validateSmtpHost: FieldHook = ({ value, siblingData }) => { // Nur validieren wenn useCustomSmtp aktiv ist und ein Wert vorhanden const emailData = siblingData as { useCustomSmtp?: boolean } if (!emailData?.useCustomSmtp) return value if (!value) return value // Basis-Validierung: Keine Protokoll-Präfixe erlaubt if (value.includes('://')) { throw new Error('SMTP Host ohne Protokoll angeben (z.B. smtp.example.com)') } return value } /** * Validiert SMTP Port */ const validateSmtpPort: FieldHook = ({ value, siblingData }) => { const emailData = siblingData as { useCustomSmtp?: boolean } if (!emailData?.useCustomSmtp) return value const port = Number(value) if (port && (port < 1 || port > 65535)) { throw new Error('Port muss zwischen 1 und 65535 liegen') } return value } export const Tenants: CollectionConfig = { slug: 'tenants', admin: { useAsTitle: 'name', }, hooks: { afterChange: [invalidateEmailCacheHook, auditTenantAfterChange], afterDelete: [auditTenantAfterDelete], }, fields: [ { name: 'name', type: 'text', required: true, }, { name: 'slug', type: 'text', required: true, unique: true, }, { name: 'domains', type: 'array', fields: [ { name: 'domain', type: 'text', required: true, }, ], }, // E-Mail Konfiguration { name: 'email', type: 'group', label: 'E-Mail Konfiguration', admin: { description: 'SMTP-Einstellungen für diesen Tenant. Leer = globale Einstellungen.', }, fields: [ { type: 'row', fields: [ { name: 'fromAddress', type: 'email', label: 'Absender E-Mail', admin: { placeholder: 'info@domain.de', width: '50%', description: 'Tipp: Verwenden Sie eine E-Mail-Adresse der Domain, für die SPF/DKIM konfiguriert ist.', }, }, { name: 'fromName', type: 'text', label: 'Absender Name', admin: { placeholder: 'Firmenname', width: '50%', }, }, ], }, { name: 'replyTo', type: 'email', label: 'Antwort-Adresse (Reply-To)', admin: { placeholder: 'kontakt@domain.de (optional)', }, }, // SPF/DKIM Info-Block { name: 'emailDeliverabilityInfo', type: 'ui', admin: { components: { Field: '@/components/admin/EmailDeliverabilityInfo#EmailDeliverabilityInfo', }, }, }, { name: 'useCustomSmtp', type: 'checkbox', label: 'Eigenen SMTP-Server verwenden', defaultValue: false, admin: { description: 'Aktivieren Sie diese Option, um einen eigenen SMTP-Server statt der globalen Einstellungen zu verwenden.', }, }, { name: 'smtp', type: 'group', label: 'SMTP Einstellungen', admin: { condition: (_, siblingData) => siblingData?.useCustomSmtp, description: 'Hinweis: Stellen Sie sicher, dass SPF- und DKIM-Einträge für Ihre Domain konfiguriert sind, um eine optimale E-Mail-Zustellung zu gewährleisten.', }, fields: [ { type: 'row', fields: [ { name: 'host', type: 'text', label: 'SMTP Host', required: true, admin: { placeholder: 'smtp.example.com', width: '50%', description: 'Hostname ohne Protokoll (z.B. smtp.gmail.com)', }, hooks: { beforeValidate: [validateSmtpHost], }, validate: (value: string | undefined | null, { siblingData }: { siblingData: Record }) => { const emailData = siblingData as { useCustomSmtp?: boolean } if (emailData?.useCustomSmtp && !value) { return 'SMTP Host ist erforderlich' } return true }, }, { name: 'port', type: 'number', label: 'Port', defaultValue: 587, admin: { width: '25%', description: '587 (STARTTLS) oder 465 (SSL)', }, hooks: { beforeValidate: [validateSmtpPort], }, min: 1, max: 65535, }, { name: 'secure', type: 'checkbox', label: 'SSL/TLS (Port 465)', defaultValue: false, admin: { width: '25%', description: 'Für Port 465 aktivieren', }, }, ], }, { type: 'row', fields: [ { name: 'user', type: 'text', label: 'SMTP Benutzername', required: true, admin: { width: '50%', description: 'Meist die E-Mail-Adresse', }, validate: (value: string | undefined | null, { siblingData }: { siblingData: Record }) => { const smtpData = siblingData as { host?: string } // Nur validieren wenn host gesetzt ist (d.h. SMTP aktiv) if (smtpData?.host && !value) { return 'SMTP Benutzername ist erforderlich' } return true }, }, { name: 'pass', type: 'text', label: 'SMTP Passwort', admin: { width: '50%', description: 'Leer lassen um bestehendes Passwort zu behalten', }, access: { read: neverReadable, // Passwort nie in API-Response }, hooks: { beforeChange: [ ({ value, originalDoc }) => { // Behalte altes Passwort wenn Feld leer if (!value && originalDoc?.email?.smtp?.pass) { return originalDoc.email.smtp.pass } return value }, ], }, }, ], }, // Test-Email Button { name: 'testEmailButton', type: 'ui', admin: { components: { Field: '@/components/admin/TestEmailButton#TestEmailButton', }, }, }, ], }, ], }, ], }