cms.c2sgmbh/src/collections/YouTubeChannels.ts
Martin Porwoll 2872f32635 feat: auto-download YouTube channel images on create/update
Adds channelThumbnailUrl field to store YouTube API URL.
afterChange hook downloads image to Payload Media when branding.logo is empty.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 12:30:48 +00:00

232 lines
6.2 KiB
TypeScript

// src/collections/YouTubeChannels.ts
import type { CollectionConfig } from 'payload'
import { isYouTubeManager, hasYouTubeAccess } from '../lib/youtubeAccess'
import { downloadChannelImage } from '../hooks/youtubeChannels/downloadChannelImage'
/**
* YouTubeChannels Collection
*
* Verwaltet YouTube-Kanäle mit Branding, Content-Serien und Metriken.
* Teil des YouTube Operations Hub.
*/
export const YouTubeChannels: CollectionConfig = {
slug: 'youtube-channels',
labels: {
singular: 'YouTube-Kanal',
plural: 'YouTube-Kanäle',
},
admin: {
useAsTitle: 'name',
group: 'YouTube',
defaultColumns: ['name', 'youtubeHandle', 'status', 'language'],
description: 'YouTube-Kanäle und ihre Konfiguration',
},
access: {
read: hasYouTubeAccess,
create: isYouTubeManager,
update: isYouTubeManager,
delete: isYouTubeManager,
},
hooks: {
afterChange: [downloadChannelImage],
},
fields: [
{
name: 'name',
type: 'text',
required: true,
localized: true,
label: 'Kanalname',
admin: {
description: 'z.B. "BlogWoman by Caroline Porwoll"',
},
},
{
name: 'slug',
type: 'text',
required: true,
unique: true,
label: 'Slug',
admin: {
description: 'Interner Kurzname (z.B. "blogwoman", "corporate-de")',
},
},
{
name: 'youtubeChannelId',
type: 'text',
required: true,
label: 'YouTube Channel ID',
admin: {
description: 'Die YouTube Channel ID (z.B. "UCxxxxxxxxxxxxx")',
},
},
{
name: 'youtubeHandle',
type: 'text',
label: 'YouTube Handle',
admin: {
description: 'z.B. "@blogwoman" oder "@zweitmeinu.ng"',
},
},
{
name: 'language',
type: 'select',
required: true,
label: 'Sprache',
options: [
{ label: 'Deutsch', value: 'de' },
{ label: 'Englisch', value: 'en' },
],
admin: {
position: 'sidebar',
},
},
{
name: 'category',
type: 'select',
required: true,
label: 'Kategorie',
options: [
{ label: 'Lifestyle', value: 'lifestyle' },
{ label: 'Corporate', value: 'corporate' },
{ label: 'Business B2B', value: 'b2b' },
],
admin: {
position: 'sidebar',
},
},
{
name: 'status',
type: 'select',
required: true,
defaultValue: 'active',
label: 'Status',
options: [
{ label: 'Aktiv', value: 'active' },
{ label: 'In Planung', value: 'planned' },
{ label: 'Pausiert', value: 'paused' },
{ label: 'Archiviert', value: 'archived' },
],
admin: {
position: 'sidebar',
},
},
{
name: 'channelThumbnailUrl',
type: 'text',
label: 'Kanal-Thumbnail URL',
admin: {
readOnly: true,
description: 'Profil-Thumbnail URL von YouTube (automatisch befüllt)',
position: 'sidebar',
},
},
// Branding
{
name: 'branding',
type: 'group',
label: 'Branding',
fields: [
{
name: 'primaryColor',
type: 'text',
label: 'Primärfarbe (Hex)',
admin: { description: 'z.B. #1278B3' },
validate: (value: string | undefined | null) => {
if (!value) return true
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
if (!hexRegex.test(value)) {
return 'Bitte einen gültigen Hex-Farbcode eingeben (z.B. #1278B3)'
}
return true
},
},
{
name: 'secondaryColor',
type: 'text',
label: 'Sekundärfarbe (Hex)',
validate: (value: string | undefined | null) => {
if (!value) return true
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
if (!hexRegex.test(value)) {
return 'Bitte einen gültigen Hex-Farbcode eingeben'
}
return true
},
},
{
name: 'logo',
type: 'upload',
relationTo: 'media',
label: 'Logo',
},
{
name: 'thumbnailTemplate',
type: 'upload',
relationTo: 'media',
label: 'Thumbnail-Vorlage',
},
],
},
// Content-Serien sind jetzt in der YtSeries Collection verwaltet
// Siehe: YouTube → YouTube-Serien
// Veröffentlichungsplan
{
name: 'publishingSchedule',
type: 'group',
label: 'Veröffentlichungsplan',
fields: [
{
name: 'defaultDays',
type: 'select',
hasMany: true,
label: 'Standard-Tage',
options: [
{ label: 'Montag', value: 'monday' },
{ label: 'Dienstag', value: 'tuesday' },
{ label: 'Mittwoch', value: 'wednesday' },
{ label: 'Donnerstag', value: 'thursday' },
{ label: 'Freitag', value: 'friday' },
{ label: 'Samstag', value: 'saturday' },
{ label: 'Sonntag', value: 'sunday' },
],
},
{
name: 'defaultTime',
type: 'text',
label: 'Standard-Uhrzeit',
admin: { description: 'z.B. "12:00"' },
},
{
name: 'shortsPerWeek',
type: 'number',
defaultValue: 4,
label: 'Shorts pro Woche',
},
{
name: 'longformPerWeek',
type: 'number',
defaultValue: 1,
label: 'Longform pro Woche',
},
],
},
// Metriken (via YouTube API Sync)
{
name: 'currentMetrics',
type: 'group',
label: 'Aktuelle Metriken',
admin: {
description: 'Automatisch via YouTube API aktualisiert',
},
fields: [
{ name: 'subscriberCount', type: 'number', label: 'Abonnenten', admin: { readOnly: true } },
{ name: 'totalViews', type: 'number', label: 'Gesamtaufrufe', admin: { readOnly: true } },
{ name: 'videoCount', type: 'number', label: 'Anzahl Videos', admin: { readOnly: true } },
{ name: 'lastSyncedAt', type: 'date', label: 'Letzter Sync', admin: { readOnly: true } },
],
},
],
timestamps: true,
}