feat: upgrade Payload CMS 3.69.0 → 3.76.1

Upgrade all 11 @payloadcms/* packages to 3.76.1, gaining fixes from
PRs #15404 (user.collection property for multi-tenant access control)
and #15499 (tenant selector uses beforeNav slot).

Fix afterLogin audit hook deadlock: payload.create() inside the hook
caused a transaction deadlock with PgBouncer in transaction mode under
Payload 3.76.1's stricter transaction handling. Changed to fire-and-forget
pattern to prevent login hangs.

Note: Next.js 15.5.9 peer dependency warning exists but build/runtime
work correctly. Consider upgrading Next.js to 16.x or downgrading to
15.4.11 in a follow-up.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martin Porwoll 2026-02-13 11:07:46 +00:00
parent 9f575963ed
commit 304e54f9e2
5 changed files with 661 additions and 305 deletions

View file

@ -28,16 +28,16 @@
},
"dependencies": {
"@anthropic-ai/sdk": "^0.71.2",
"@payloadcms/db-postgres": "3.69.0",
"@payloadcms/next": "3.69.0",
"@payloadcms/plugin-form-builder": "3.69.0",
"@payloadcms/plugin-multi-tenant": "3.69.0",
"@payloadcms/plugin-nested-docs": "3.69.0",
"@payloadcms/plugin-redirects": "3.69.0",
"@payloadcms/plugin-seo": "3.69.0",
"@payloadcms/richtext-lexical": "3.69.0",
"@payloadcms/translations": "3.69.0",
"@payloadcms/ui": "3.69.0",
"@payloadcms/db-postgres": "3.76.1",
"@payloadcms/next": "3.76.1",
"@payloadcms/plugin-form-builder": "3.76.1",
"@payloadcms/plugin-multi-tenant": "3.76.1",
"@payloadcms/plugin-nested-docs": "3.76.1",
"@payloadcms/plugin-redirects": "3.76.1",
"@payloadcms/plugin-seo": "3.76.1",
"@payloadcms/richtext-lexical": "3.76.1",
"@payloadcms/translations": "3.76.1",
"@payloadcms/ui": "3.76.1",
"@types/pdfkit": "^0.17.4",
"bullmq": "^5.65.1",
"cross-env": "^7.0.3",
@ -49,7 +49,7 @@
"next": "15.5.9",
"node-cron": "^4.2.1",
"nodemailer": "^7.0.11",
"payload": "3.69.0",
"payload": "3.76.1",
"payload-oapi": "^0.2.5",
"pdfkit": "^0.17.2",
"react": "19.2.3",

File diff suppressed because it is too large Load diff

View file

@ -28,7 +28,7 @@ import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0
import { CommunityNavLinks as CommunityNavLinks_4431db66fbe96916dda5fdbfa979ee1e } from '@/components/admin/CommunityNavLinks'
import { TenantSelector as TenantSelector_d6d5f193a167989e2ee7d14202901e62 } from '@payloadcms/plugin-multi-tenant/rsc'
import { TenantSelectionProvider as TenantSelectionProvider_d6d5f193a167989e2ee7d14202901e62 } from '@payloadcms/plugin-multi-tenant/rsc'
import { CollectionCards as CollectionCards_ab83ff7e88da8d3530831f296ec4756a } from '@payloadcms/ui/rsc'
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'
export const importMap = {
"@payloadcms/plugin-multi-tenant/client#TenantField": TenantField_1d0591e3cf4f332c83a86da13a0de59a,
@ -61,5 +61,5 @@ export const importMap = {
"@/components/admin/CommunityNavLinks#CommunityNavLinks": CommunityNavLinks_4431db66fbe96916dda5fdbfa979ee1e,
"@payloadcms/plugin-multi-tenant/rsc#TenantSelector": TenantSelector_d6d5f193a167989e2ee7d14202901e62,
"@payloadcms/plugin-multi-tenant/rsc#TenantSelectionProvider": TenantSelectionProvider_d6d5f193a167989e2ee7d14202901e62,
"@payloadcms/ui/rsc#CollectionCards": CollectionCards_ab83ff7e88da8d3530831f296ec4756a
"@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1
}

View file

@ -31,7 +31,12 @@ interface AuthUser {
export const auditAfterLogin: CollectionAfterLoginHook = async ({ user, req }) => {
const typedUser = user as AuthUser
await logLoginSuccess(req.payload, typedUser.id, typedUser.email, req)
// Fire-and-forget: Audit-Log darf Login nicht blockieren
// In Payload 3.76.1+ verursacht ein awaited payload.create() innerhalb
// des afterLogin-Hooks einen Deadlock mit PgBouncer (Transaction-Mode)
logLoginSuccess(req.payload, typedUser.id, typedUser.email, req).catch((err) => {
console.error(`[Audit:Auth] Failed to log login for ${typedUser.email}:`, err)
})
console.log(`[Audit:Auth] Login success for ${typedUser.email}`)

View file

@ -114,6 +114,7 @@ export interface Config {
'community-interactions': CommunityInteraction;
'community-templates': CommunityTemplate;
'community-rules': CommunityRule;
'report-schedules': ReportSchedule;
'cookie-configurations': CookieConfiguration;
'cookie-inventory': CookieInventory;
'consent-logs': ConsentLog;
@ -179,6 +180,7 @@ export interface Config {
'community-interactions': CommunityInteractionsSelect<false> | CommunityInteractionsSelect<true>;
'community-templates': CommunityTemplatesSelect<false> | CommunityTemplatesSelect<true>;
'community-rules': CommunityRulesSelect<false> | CommunityRulesSelect<true>;
'report-schedules': ReportSchedulesSelect<false> | ReportSchedulesSelect<true>;
'cookie-configurations': CookieConfigurationsSelect<false> | CookieConfigurationsSelect<true>;
'cookie-inventory': CookieInventorySelect<false> | CookieInventorySelect<true>;
'consent-logs': ConsentLogsSelect<false> | ConsentLogsSelect<true>;
@ -206,9 +208,7 @@ export interface Config {
'seo-settings': SeoSettingsSelect<false> | SeoSettingsSelect<true>;
};
locale: 'de' | 'en';
user: User & {
collection: 'users';
};
user: User;
jobs: {
tasks: unknown;
workflows: unknown;
@ -277,6 +277,7 @@ export interface User {
}[]
| null;
password?: string | null;
collection: 'users';
}
/**
* YouTube-Kanäle und ihre Konfiguration
@ -6551,6 +6552,10 @@ export interface YtNotification {
| 'video_published'
| 'comment'
| 'mention'
| 'token_expiring'
| 'token_expired'
| 'token_refresh_failed'
| 'token_refreshed'
| 'system';
title: string;
message?: string | null;
@ -6560,12 +6565,118 @@ export interface YtNotification {
link?: string | null;
relatedVideo?: (number | null) | YoutubeContent;
relatedTask?: (number | null) | YtTask;
/**
* Verknüpfter Social Account (für Token-Benachrichtigungen)
*/
relatedAccount?: (number | null) | SocialAccount;
read?: boolean | null;
readAt?: string | null;
emailSent?: boolean | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "social-accounts".
*/
export interface SocialAccount {
id: number;
platform: number | SocialPlatform;
/**
* Für Zuordnung zu Brand/Kanal
*/
linkedChannel?: (number | null) | YoutubeChannel;
displayName: string;
accountHandle?: string | null;
/**
* YouTube Channel ID, LinkedIn URN, etc.
*/
externalId?: string | null;
accountUrl?: string | null;
isActive?: boolean | null;
/**
* Sensible Daten nur für Super-Admins sichtbar
*/
credentials?: {
/**
* OAuth Access Token
*/
accessToken?: string | null;
refreshToken?: string | null;
tokenExpiresAt?: string | null;
/**
* Für API-Key basierte Auth
*/
apiKey?: string | null;
};
stats?: {
followers?: number | null;
totalPosts?: number | null;
lastSyncedAt?: string | null;
};
syncSettings?: {
autoSyncEnabled?: boolean | null;
syncIntervalMinutes?: number | null;
syncComments?: boolean | null;
/**
* Nicht alle Plattformen unterstützen DM-API
*/
syncDMs?: boolean | null;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "social-platforms".
*/
export interface SocialPlatform {
id: number;
name: string;
slug: string;
icon?: string | null;
color?: string | null;
isActive?: boolean | null;
apiStatus?: ('connected' | 'limited' | 'disconnected' | 'development') | null;
apiConfig?: {
apiType?: ('youtube_v3' | 'linkedin' | 'instagram_graph' | 'facebook_graph' | 'meta_graph' | 'custom') | null;
baseUrl?: string | null;
authType?: ('oauth2' | 'api_key' | 'bearer') | null;
/**
* Relativer API-Pfad für OAuth-Initiation (z.B. /api/youtube/auth)
*/
oauthEndpoint?: string | null;
scopes?:
| {
scope?: string | null;
id?: string | null;
}[]
| null;
/**
* Wie lange ist der Access Token gültig? (YouTube: unbegrenzt mit Refresh, Meta: 60 Tage)
*/
tokenValidityDays?: number | null;
};
interactionTypes?:
| {
type: string;
label: string;
icon?: string | null;
canReply?: boolean | null;
id?: string | null;
}[]
| null;
rateLimits?: {
requestsPerMinute?: number | null;
requestsPerDay?: number | null;
/**
* YouTube: 10.000/Tag
*/
quotaUnitsPerDay?: number | null;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "yt-monthly-goals".
@ -6661,100 +6772,6 @@ export interface YtChecklistTemplate {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "social-platforms".
*/
export interface SocialPlatform {
id: number;
name: string;
slug: string;
icon?: string | null;
color?: string | null;
isActive?: boolean | null;
apiStatus?: ('connected' | 'limited' | 'disconnected' | 'development') | null;
apiConfig?: {
apiType?: ('youtube_v3' | 'linkedin' | 'instagram_graph' | 'facebook_graph' | 'custom') | null;
baseUrl?: string | null;
authType?: ('oauth2' | 'api_key' | 'bearer') | null;
scopes?:
| {
scope?: string | null;
id?: string | null;
}[]
| null;
};
interactionTypes?:
| {
type: string;
label: string;
icon?: string | null;
canReply?: boolean | null;
id?: string | null;
}[]
| null;
rateLimits?: {
requestsPerMinute?: number | null;
requestsPerDay?: number | null;
/**
* YouTube: 10.000/Tag
*/
quotaUnitsPerDay?: number | null;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "social-accounts".
*/
export interface SocialAccount {
id: number;
platform: number | SocialPlatform;
/**
* Für Zuordnung zu Brand/Kanal
*/
linkedChannel?: (number | null) | YoutubeChannel;
displayName: string;
accountHandle?: string | null;
/**
* YouTube Channel ID, LinkedIn URN, etc.
*/
externalId?: string | null;
accountUrl?: string | null;
isActive?: boolean | null;
/**
* Sensible Daten nur für Super-Admins sichtbar
*/
credentials?: {
/**
* OAuth Access Token
*/
accessToken?: string | null;
refreshToken?: string | null;
tokenExpiresAt?: string | null;
/**
* Für API-Key basierte Auth
*/
apiKey?: string | null;
};
stats?: {
followers?: number | null;
totalPosts?: number | null;
lastSyncedAt?: string | null;
};
syncSettings?: {
autoSyncEnabled?: boolean | null;
syncIntervalMinutes?: number | null;
syncComments?: boolean | null;
/**
* Nicht alle Plattformen unterstützen DM-API
*/
syncDMs?: boolean | null;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "community-interactions".
@ -6980,6 +6997,64 @@ export interface CommunityRule {
updatedAt: string;
createdAt: string;
}
/**
* Automatische Community-Reports per E-Mail
*
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "report-schedules".
*/
export interface ReportSchedule {
id: number;
/**
* Interner Name für diesen Report-Zeitplan
*/
name: string;
/**
* Deaktivierte Reports werden nicht automatisch versendet
*/
enabled?: boolean | null;
frequency: 'daily' | 'weekly' | 'monthly';
/**
* Für wöchentliche Reports
*/
dayOfWeek?: ('monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'sunday') | null;
/**
* Für monatliche Reports (1-28)
*/
dayOfMonth?: number | null;
/**
* Format: HH:MM (24-Stunden)
*/
time: string;
timezone: 'Europe/Berlin' | 'Europe/London' | 'America/New_York' | 'America/Los_Angeles' | 'Asia/Tokyo' | 'UTC';
/**
* Art der Daten im Report
*/
reportType: 'overview' | 'sentiment_analysis' | 'response_metrics' | 'content_performance' | 'full_report';
/**
* Leer = alle aktiven Kanäle. Oder spezifische Kanäle auswählen.
*/
channels?: (number | SocialAccount)[] | null;
/**
* Wie viele Tage zurück sollen analysiert werden?
*/
periodDays?: number | null;
format: 'pdf' | 'excel' | 'html_email';
recipients: {
email: string;
/**
* Optional
*/
name?: string | null;
id?: string | null;
}[];
lastSentAt?: string | null;
nextScheduledAt?: string | null;
sendCount?: number | null;
lastError?: string | null;
updatedAt: string;
createdAt: string;
}
/**
* Cookie-Banner Konfiguration pro Tenant
*
@ -7697,6 +7772,10 @@ export interface PayloadLockedDocument {
relationTo: 'community-rules';
value: number | CommunityRule;
} | null)
| ({
relationTo: 'report-schedules';
value: number | ReportSchedule;
} | null)
| ({
relationTo: 'cookie-configurations';
value: number | CookieConfiguration;
@ -11599,6 +11678,7 @@ export interface YtNotificationsSelect<T extends boolean = true> {
link?: T;
relatedVideo?: T;
relatedTask?: T;
relatedAccount?: T;
read?: T;
readAt?: T;
emailSent?: T;
@ -11770,12 +11850,14 @@ export interface SocialPlatformsSelect<T extends boolean = true> {
apiType?: T;
baseUrl?: T;
authType?: T;
oauthEndpoint?: T;
scopes?:
| T
| {
scope?: T;
id?: T;
};
tokenValidityDays?: T;
};
interactionTypes?:
| T
@ -11991,6 +12073,36 @@ export interface CommunityRulesSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "report-schedules_select".
*/
export interface ReportSchedulesSelect<T extends boolean = true> {
name?: T;
enabled?: T;
frequency?: T;
dayOfWeek?: T;
dayOfMonth?: T;
time?: T;
timezone?: T;
reportType?: T;
channels?: T;
periodDays?: T;
format?: T;
recipients?:
| T
| {
email?: T;
name?: T;
id?: T;
};
lastSentAt?: T;
nextScheduledAt?: T;
sendCount?: T;
lastError?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "cookie-configurations_select".