cms.c2sgmbh/src/payload.config.ts

474 lines
15 KiB
TypeScript

import { buildConfig } from 'payload'
import { postgresAdapter } from '@payloadcms/db-postgres'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import { multiTenantPlugin } from '@payloadcms/plugin-multi-tenant'
import { seoPlugin } from '@payloadcms/plugin-seo'
import { nestedDocsPlugin } from '@payloadcms/plugin-nested-docs'
import { formBuilderPlugin } from '@payloadcms/plugin-form-builder'
import { redirectsPlugin } from '@payloadcms/plugin-redirects'
import { openapi, swaggerUI } from 'payload-oapi'
import { de } from '@payloadcms/translations/languages/de'
import { en } from '@payloadcms/translations/languages/en'
import path from 'path'
import { fileURLToPath } from 'url'
import sharp from 'sharp'
// Security: Validate required environment variables at startup
import { env } from './lib/envValidation'
import { Users } from './collections/Users'
import { Media } from './collections/Media'
import { Tenants } from './collections/Tenants'
import { Pages } from './collections/Pages'
import { Posts } from './collections/Posts'
import { Categories } from './collections/Categories'
import { SocialLinks } from './collections/SocialLinks'
import { Testimonials } from './collections/Testimonials'
import { FAQs } from './collections/FAQs'
import { Team } from './collections/Team'
import { ServiceCategories } from './collections/ServiceCategories'
import { Services } from './collections/Services'
import { NewsletterSubscribers } from './collections/NewsletterSubscribers'
// Portfolio Collections
import { PortfolioCategories } from './collections/PortfolioCategories'
import { Portfolios } from './collections/Portfolios'
// Video Collections
import { VideoCategories } from './collections/VideoCategories'
import { Videos } from './collections/Videos'
// Product Collections
import { ProductCategories } from './collections/ProductCategories'
import { Products } from './collections/Products'
// Timeline Collection
import { Timelines } from './collections/Timelines'
// Workflow Collection
import { Workflows } from './collections/Workflows'
// Blogging Collections
import { Tags } from './collections/Tags'
import { Authors } from './collections/Authors'
// New Feature Collections
import { Locations } from './collections/Locations'
import { Partners } from './collections/Partners'
import { Jobs } from './collections/Jobs'
import { Downloads } from './collections/Downloads'
import { Events } from './collections/Events'
// Tenant-specific Collections
import { Bookings } from './collections/Bookings'
import { Certifications } from './collections/Certifications'
import { Projects } from './collections/Projects'
// BlogWoman Collections - ENABLED
import { Favorites } from './collections/Favorites'
import { Series } from './collections/Series'
// YouTube Operations Hub Collections
import { YouTubeChannels } from './collections/YouTubeChannels'
import { YouTubeContent } from './collections/YouTubeContent'
import { YtTasks } from './collections/YtTasks'
import { YtNotifications } from './collections/YtNotifications'
// YouTube Operations Hub v2 Collections
import { YtBatches } from './collections/YtBatches'
import { YtMonthlyGoals } from './collections/YtMonthlyGoals'
import { YtScriptTemplates } from './collections/YtScriptTemplates'
import { YtChecklistTemplates } from './collections/YtChecklistTemplates'
import { YtSeries } from './collections/YtSeries'
// Community Management Collections
import { SocialPlatforms } from './collections/SocialPlatforms'
import { SocialAccounts } from './collections/SocialAccounts'
import { CommunityInteractions } from './collections/CommunityInteractions'
import { CommunityTemplates } from './collections/CommunityTemplates'
import { CommunityRules } from './collections/CommunityRules'
import { ReportSchedules } from './collections/ReportSchedules'
// Debug: Minimal test collection - DISABLED (nur für Tests)
// import { TestMinimal } from './collections/TestMinimal'
// Consent Management Collections
import { CookieConfigurations } from './collections/CookieConfigurations'
import { CookieInventory } from './collections/CookieInventory'
import { ConsentLogs } from './collections/ConsentLogs'
import { PrivacyPolicySettings } from './collections/PrivacyPolicySettings'
// Tenant-specific Settings Collections (converted from Globals)
import { SiteSettings } from './collections/SiteSettings'
import { Navigations } from './collections/Navigations'
// Global Settings (system-wide, not tenant-specific)
import { SEOSettings } from './globals/SEOSettings'
// Hooks
import { sendFormNotification } from './hooks/sendFormNotification'
import { formSubmissionBeforeChange } from './hooks/formSubmissionHooks'
import { setSubmissionTenant } from './hooks/setSubmissionTenant'
// Form Submissions Overrides
import { formSubmissionOverrides } from './collections/FormSubmissionsOverrides'
// Email
import { multiTenantEmailAdapter } from './lib/email/payload-email-adapter'
// Email Logs
import { EmailLogs } from './collections/EmailLogs'
// Audit Logs
import { AuditLogs } from './collections/AuditLogs'
// Monitoring Collections
import { MonitoringSnapshots } from './collections/MonitoringSnapshots'
import { MonitoringLogs } from './collections/MonitoringLogs'
import { MonitoringAlertRules } from './collections/MonitoringAlertRules'
import { MonitoringAlertHistory } from './collections/MonitoringAlertHistory'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export default buildConfig({
serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL || 'https://pl.porwoll.tech',
admin: {
user: Users.slug,
components: {
afterNavLinks: [
'@/components/admin/CommunityNavLinks#CommunityNavLinks',
'@/components/admin/YouTubeAnalyticsNavLinks#YouTubeAnalyticsNavLinks',
'@/components/admin/MonitoringNavLinks#MonitoringNavLinks',
],
views: {
TenantDashboard: {
Component: '@/components/admin/TenantDashboardView#TenantDashboardView',
path: '/tenant-dashboard',
},
YouTubeAnalyticsDashboard: {
Component: '@/components/admin/YouTubeAnalyticsDashboardView#YouTubeAnalyticsDashboardView',
path: '/youtube-analytics',
},
ContentCalendar: {
Component: '@/components/admin/ContentCalendarView#ContentCalendarView',
path: '/content-calendar',
},
MonitoringDashboard: {
Component: '@/components/admin/MonitoringDashboardView#MonitoringDashboardView',
path: '/monitoring',
},
},
},
},
// Multi-Tenant Email Adapter
email: multiTenantEmailAdapter,
// Admin Panel Internationalization (UI translations)
i18n: {
supportedLanguages: { de, en },
fallbackLanguage: 'de',
},
// Content Localization (multilingual content)
localization: {
locales: [
{
label: 'Deutsch',
code: 'de',
},
{
label: 'English',
code: 'en',
},
],
defaultLocale: 'de',
fallback: true,
},
// CORS Konfiguration für externe Frontends
cors: [
'http://localhost:3000',
'http://localhost:3001',
'http://10.10.180.153:3000',
'http://10.10.180.153:3001',
'http://10.10.181.104:3000',
'https://dev.zh3.de',
'https://porwoll.de',
'https://www.porwoll.de',
'https://pl.porwoll.tech',
'https://pl.c2sgmbh.de',
'https://cms.c2sgmbh.de',
],
// CSRF Protection
csrf: [
'http://localhost:3000',
'http://localhost:3001',
'http://10.10.180.153:3000',
'http://10.10.180.153:3001',
'http://10.10.181.104:3000',
'https://dev.zh3.de',
'https://porwoll.de',
'https://www.porwoll.de',
'https://pl.porwoll.tech',
'https://pl.c2sgmbh.de',
'https://cms.c2sgmbh.de',
],
collections: [
Users,
Media,
Tenants,
Pages,
Posts,
Categories,
SocialLinks,
Testimonials,
FAQs,
Team,
ServiceCategories,
Services,
NewsletterSubscribers,
// Portfolio
PortfolioCategories,
Portfolios,
// Videos
VideoCategories,
Videos,
// Products
ProductCategories,
Products,
// Timelines & Workflows
Timelines,
Workflows,
// Blogging
Tags,
Authors,
// New Feature Collections
Locations,
Partners,
Jobs,
Downloads,
Events,
// Tenant-specific Collections
Bookings,
Certifications,
Projects,
// BlogWoman Collections - ENABLED
Favorites,
Series,
// YouTube Operations Hub
YouTubeChannels,
YouTubeContent,
YtTasks,
YtNotifications,
// YouTube Operations Hub v2
YtBatches,
YtMonthlyGoals,
YtScriptTemplates,
YtChecklistTemplates,
YtSeries,
// Community Management
SocialPlatforms,
SocialAccounts,
CommunityInteractions,
CommunityTemplates,
CommunityRules,
ReportSchedules,
// Debug: Minimal test collection - DISABLED
// TestMinimal,
// Consent Management
CookieConfigurations,
CookieInventory,
ConsentLogs,
PrivacyPolicySettings,
// System
EmailLogs,
AuditLogs,
// Monitoring
MonitoringSnapshots,
MonitoringLogs,
MonitoringAlertRules,
MonitoringAlertHistory,
// Tenant-specific Settings (converted from Globals)
SiteSettings,
Navigations,
],
globals: [SEOSettings],
editor: lexicalEditor(),
secret: env.PAYLOAD_SECRET,
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
db: postgresAdapter({
pool: {
connectionString: env.DATABASE_URI,
},
// push: false - Migrationen für Schema-Änderungen verwenden
push: false,
}),
// Sharp für Bildoptimierung
sharp,
plugins: [
// formBuilderPlugin MUSS vor multiTenantPlugin stehen, da es die forms/form-submissions
// Collections erstellt, die multiTenantPlugin dann mit Tenant-Scoping erweitert.
formBuilderPlugin({
fields: {
text: true,
textarea: true,
select: true,
email: true,
state: false,
country: false,
checkbox: true,
number: true,
message: true,
payment: false,
},
// Fix für TypeScript Types Generation - das Plugin braucht explizite relationTo Angaben
redirectRelationships: ['pages'],
formOverrides: {
admin: {
group: 'Formulare',
},
labels: {
singular: 'Formular',
plural: 'Formulare',
},
fields: ({ defaultFields }) => [
...defaultFields,
{
name: 'tenant',
type: 'relationship',
relationTo: 'tenants',
required: true,
admin: { position: 'sidebar' },
},
],
},
formSubmissionOverrides: {
...(formSubmissionOverrides as Record<string, unknown>),
hooks: {
beforeChange: [setSubmissionTenant, formSubmissionBeforeChange],
afterChange: [sendFormNotification],
},
} as Parameters<typeof formBuilderPlugin>[0]['formSubmissionOverrides'],
}),
multiTenantPlugin({
tenantsSlug: 'tenants',
collections: {
media: {},
pages: {},
posts: {},
categories: {},
'social-links': {},
// Type assertion für neue Collections bis payload-types.ts regeneriert wird
...({
testimonials: {},
faqs: {},
team: {},
'service-categories': {},
services: {},
'newsletter-subscribers': {},
// Portfolio Collections
'portfolio-categories': {},
portfolios: {},
// Video Collections
'video-categories': {},
videos: {},
// Product Collections
'product-categories': {},
products: {},
// Timeline & Workflow Collections
timelines: {},
workflows: {},
// Blogging Collections
tags: {},
authors: {},
// New Feature Collections
locations: {},
partners: {},
jobs: {},
downloads: {},
events: {},
// Tenant-specific Collections
bookings: {},
certifications: {},
projects: {},
// BlogWoman Collections - ENABLED
favorites: {},
series: {},
// Debug: Minimal test collection - DISABLED
// 'test-minimal': {},
// Form Builder Plugin Collections - customTenantField: true weil tenant via formOverrides injiziert wird
forms: { customTenantField: true },
'form-submissions': { customTenantField: true },
// Consent Management Collections - customTenantField: true weil sie bereits ein tenant-Feld haben
'cookie-configurations': { customTenantField: true },
'cookie-inventory': { customTenantField: true },
'consent-logs': { customTenantField: true },
'privacy-policy-settings': { customTenantField: true },
// Tenant-specific Settings (converted from Globals)
'site-settings': {},
navigations: {},
} as Record<string, { customTenantField?: boolean }>),
},
// Super Admins haben Zugriff auf alle Tenants
userHasAccessToAllTenants: (user) => {
const result = Boolean(user?.isSuperAdmin)
console.log('[DEBUG:MultiTenant] userHasAccessToAllTenants:', {
userId: user?.id,
email: user?.email,
isSuperAdmin: user?.isSuperAdmin,
result,
tenants: user?.tenants,
userKeys: user ? Object.keys(user) : 'no user',
})
return result
},
debug: true,
// Deutsche Übersetzungen für den Tenant-Selector
i18n: {
translations: {
de: {
'nav-tenantSelector-label': 'Nach Tenant filtern',
'assign-tenant-button-label': 'Tenant zuweisen',
'assign-tenant-modal-title': '"{{title}}" zuweisen',
'field-assignedTenant-label': 'Zugewiesener Tenant',
},
en: {
'nav-tenantSelector-label': 'Filter by Tenant',
'assign-tenant-button-label': 'Assign Tenant',
'assign-tenant-modal-title': 'Assign "{{title}}"',
'field-assignedTenant-label': 'Assigned Tenant',
},
},
},
}),
seoPlugin({
collections: [],
uploadsCollection: 'media',
generateTitle: ({ doc }) => `${doc?.title || ''} | Website`,
generateDescription: ({ doc }) => doc?.excerpt || '',
}),
nestedDocsPlugin({
collections: [],
generateLabel: (_, doc) => doc.title as string,
generateURL: (docs) => docs.reduce((url, doc) => `${url}/${doc.slug}`, ''),
}),
redirectsPlugin({
collections: ['pages'],
overrides: {
admin: {
group: 'Einstellungen',
},
},
}),
// OpenAPI Documentation
openapi({
openapiVersion: '3.1',
metadata: {
title: 'Payload CMS API',
version: '1.0.0',
description: 'Multi-Tenant CMS API für porwoll.de, complexcaresolutions.de, gunshin.de und zweitmein.ng',
},
}),
// Swagger UI unter /api/docs
swaggerUI({}),
],
})