mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 20:54:11 +00:00
Video Feature Implementation: - Add Videos and VideoCategories collections with multi-tenant support - Extend VideoBlock with library/upload/embed sources and playback options - Add featuredVideo group to Posts collection with processed embed URLs Hooks & Validation: - Add processFeaturedVideo hook for URL parsing and privacy mode embedding - Add createSlugValidationHook for tenant-scoped slug uniqueness - Add video-utils library (parseVideoUrl, generateEmbedUrl, formatDuration) Testing: - Add 84 unit tests for video-utils (URL parsing, duration, embed generation) - Add 14 integration tests for Videos collection CRUD and slug validation Database: - Migration for videos, video_categories tables with locales - Migration for Posts featuredVideo processed fields - Update payload internal tables for new collections 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
88 lines
2.3 KiB
TypeScript
88 lines
2.3 KiB
TypeScript
/**
|
|
* Featured Video Processing Hook
|
|
*
|
|
* Verarbeitet featuredVideo.embedUrl in Posts:
|
|
* - Extrahiert Video-ID aus URL
|
|
* - Generiert normalisierte Embed-URL mit Privacy-Mode
|
|
*/
|
|
|
|
import type { CollectionBeforeChangeHook } from 'payload'
|
|
import { parseVideoUrl, generateEmbedUrl } from '../lib/video'
|
|
|
|
interface FeaturedVideoData {
|
|
enabled?: boolean
|
|
source?: 'library' | 'embed' | 'upload'
|
|
embedUrl?: string
|
|
video?: number | string
|
|
uploadedVideo?: number | string
|
|
autoplay?: boolean
|
|
muted?: boolean
|
|
replaceImage?: boolean
|
|
// Processed fields (added by this hook)
|
|
processedEmbedUrl?: string
|
|
extractedVideoId?: string
|
|
platform?: string
|
|
thumbnailUrl?: string
|
|
}
|
|
|
|
interface PostData {
|
|
featuredVideo?: FeaturedVideoData
|
|
[key: string]: unknown
|
|
}
|
|
|
|
/**
|
|
* Hook zum Verarbeiten von featuredVideo Embed-URLs
|
|
*
|
|
* - Extrahiert Video-ID und Plattform aus der URL
|
|
* - Generiert normalisierte Embed-URL mit Privacy-Mode (youtube-nocookie)
|
|
* - Speichert Thumbnail-URL für Fallback
|
|
*/
|
|
export const processFeaturedVideo: CollectionBeforeChangeHook<PostData> = async ({
|
|
data,
|
|
operation,
|
|
}) => {
|
|
// Nur wenn featuredVideo existiert und aktiviert ist
|
|
if (!data?.featuredVideo?.enabled) {
|
|
return data
|
|
}
|
|
|
|
const featuredVideo = data.featuredVideo
|
|
|
|
// Nur für embed source verarbeiten
|
|
if (featuredVideo.source !== 'embed' || !featuredVideo.embedUrl) {
|
|
return data
|
|
}
|
|
|
|
const embedUrl = featuredVideo.embedUrl.trim()
|
|
|
|
// URL parsen
|
|
const videoInfo = parseVideoUrl(embedUrl)
|
|
|
|
if (!videoInfo || videoInfo.platform === 'unknown') {
|
|
// URL konnte nicht geparst werden - unverändert lassen
|
|
console.warn(`[processFeaturedVideo] Could not parse video URL: ${embedUrl}`)
|
|
return data
|
|
}
|
|
|
|
// Video-Metadaten speichern
|
|
featuredVideo.extractedVideoId = videoInfo.videoId || undefined
|
|
featuredVideo.platform = videoInfo.platform
|
|
featuredVideo.thumbnailUrl = videoInfo.thumbnailUrl || undefined
|
|
|
|
// Embed-URL mit Privacy-Mode und Playback-Optionen generieren
|
|
const processedUrl = generateEmbedUrl(videoInfo, {
|
|
autoplay: featuredVideo.autoplay ?? false,
|
|
muted: featuredVideo.muted ?? true,
|
|
privacyMode: true, // Immer Privacy-Mode für DSGVO
|
|
showRelated: false, // Keine verwandten Videos
|
|
})
|
|
|
|
if (processedUrl) {
|
|
featuredVideo.processedEmbedUrl = processedUrl
|
|
}
|
|
|
|
return {
|
|
...data,
|
|
featuredVideo,
|
|
}
|
|
}
|