The SocialPlatforms collection config had apiConfig.oauthEndpoint and
apiConfig.tokenValidityDays fields that were never migrated to the DB.
This caused a DrizzleQueryError when resolving social_platforms
relationships (e.g. creating community-interactions), since the SQL
query referenced non-existent columns.
Applied to both dev and production databases.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes two high-severity Dependabot alerts for minimatch ReDoS
vulnerabilities (nested extglobs + GLOBSTAR backtracking).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Telegram media bot implementation plan and prompt
- sensualmoment.de design prototypes (color scheme, prototype, design doc)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migrates all WordPress content to Payload CMS blocks: 10 pages, 3 testimonials,
navigation, contact form, social links, site settings, and 1 blog post.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add html-embed-block for raw HTML/iframe embedding in Pages layout.
Update seed script with hero blocks for impressum/datenschutz and
alfright.eu iframe via html-embed-block for privacy policy page.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update home page layout with blog preview (posts-list-block) and
contact form (contact-form-block) sections. Replace image-text-block
with text-block since no media exists for tenant 13 yet.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The custom /api/posts route intercepted all post queries but only
supported listing parameters (category, type, page). Frontend detail
pages sending where[slug][equals]=X got all posts back, always
showing the latest post regardless of which article was clicked.
Now parses slug from both ?slug=X and ?where[slug][equals]=X format.
Replaced getPostsByCategory with direct payload.find using properly
typed Where conditions. Detail queries (with slug) include content
and readingTime in the response.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8 boudoir photography journal posts covering topics like first
shooting experience, self-love, posing tips, behind the scenes,
gifting, body positivity, preparation, and photo albums.
Idempotent: skips existing posts based on slug + tenant match.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Custom API routes at /api/posts, /api/search, and /api/search/suggestions
used payload.find() with overrideAccess:true (default) and optional tenant
filtering. Without a ?tenant= parameter, ALL data from ALL tenants was
returned — causing cross-tenant data leaks (e.g. sensualmoment.de Journal
showing blogwoman articles).
Now all three routes require a tenant parameter (400 error without it).
Also accepts where[tenant][equals] format for compatibility with
payload-contracts API clients. Removed debug logging from tenantAccess.ts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document the OWASP CRS 3.3.7 WAF on production nginx, including
exclusion rule IDs and the diagnostic curl method to distinguish
ModSecurity blocks from Payload 403 responses.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The 403 "Forbidden" on production was caused by ModSecurity WAF
(OWASP CRS 3.3.7) blocking PATCH/POST requests at the nginx layer,
not by Next.js server actions CSRF. Nginx proxy_set_header Host $host
ensures Origin and Host always match, making allowedOrigins redundant.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The SEO Settings global had `read: ({ req: { user } }) => Boolean(user)`
which requires authentication. During admin panel server-side rendering
(after saves), the user context is not propagated to global reads,
causing a Forbidden error that crashes the entire page render.
SEO data is not sensitive, so public read access is appropriate.
Also removes temporary debug logging.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Next.js has its own CSRF protection for server actions, separate from
Payload's csrf config. Without allowedOrigins, server actions from the
admin panel behind a reverse proxy are rejected because the Origin header
(cms.c2sgmbh.de) doesn't match the Host header (localhost:3001).
Also removes temporary debug logging from multiTenant access check.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add pl.c2sgmbh.de and cms.c2sgmbh.de to cors and csrf arrays
to fix Forbidden error on PATCH requests from these domains
- Add saveToJWT: true to isSuperAdmin field so multiTenantPlugin
correctly grants super admins access to all tenants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add structured data (icon, features[], detailSections[]) to all 6 services
so the frontend can render benefits grids and checklists from CMS data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously, tenantScopedPublicRead only resolved the tenant from the Host
header, which fails when frontend API clients call cms.c2sgmbh.de (the CMS
hostname doesn't match any tenant domain). Now falls back to extracting the
tenant ID from the where[tenant][equals] query parameter. The returned access
filter still enforces tenant isolation.
Also adds seed script for zweitmeinung (tenant 12) with all content:
site settings, 2 service categories, 6 services, 24 FAQs, navigation,
4 social links, and contact form.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add http://10.10.181.104:3000 (sv-frontend staging) to allow cross-origin
form submissions from the staging frontend to the CMS API.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- FormSubmissionsOverrides: fields must be a function (not array) for
the form-builder plugin to merge them with defaultFields
- setSubmissionTenant: add overrideAccess for unauthenticated submissions
- sendFormNotification: handle populated form object (extract ID),
add overrideAccess for tenant SMTP lookup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add forms + form-submissions to multiTenantPlugin with tenant scoping
- Inject tenant field into forms via formOverrides
- Reorder plugins: formBuilderPlugin before multiTenantPlugin (fixes warning)
- Refactor ContactFormBlock: form relationship replaces hardcoded recipientEmail
- Add setSubmissionTenant hook to auto-copy tenant from form to submission
- Add tenant field (read-only) to FormSubmissionsOverrides
- Migration: tenant_id on forms/form_submissions, form_id on contact block
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove redundant NODE_OPTIONS override (heap limit now in package.json)
- Add CRON_SECRET placeholder for pre-test build step
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Next.js builds run in NODE_ENV=production which triggers env
validation requiring CRON_SECRET (added by security hardening).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The codebase grew past the 2GB heap limit with security/monitoring
additions. cross-env in build script overrides CI NODE_OPTIONS, so
the limit must be set in package.json itself.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Build was OOM-ing in CI with default Node heap limit. Added
NODE_OPTIONS with 4GB heap. Also ran Prettier on monitoring files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>