mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 16:14:12 +00:00
- Add Products collection with comprehensive fields (pricing, inventory, SEO, CTA) - Add ProductCategories collection with hierarchical structure - Implement CI/CD pipeline with GitHub Actions (lint, typecheck, test, build, e2e) - Add access control test utilities and unit tests - Fix Posts API to include category field for backwards compatibility - Update ESLint config with ignores for migrations and admin components - Add centralized access control functions in src/lib/access - Add db-direct.sh utility script for database access 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
254 lines
7.7 KiB
TypeScript
254 lines
7.7 KiB
TypeScript
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<string, unknown> }) => {
|
|
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<string, unknown> }) => {
|
|
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',
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|