feat(youtube): add auto status transition hook

Automatically transitions YouTube content status when conditions are met
(e.g., upload_scheduled -> published when videoId is present) and sends
notifications to assigned users.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martin Porwoll 2026-02-14 13:42:25 +00:00
parent 9e7b433cd0
commit 8ae5841cc1
3 changed files with 111 additions and 1 deletions

View file

@ -9,6 +9,7 @@ import {
} from '../lib/youtubeAccess'
import { createTasksOnStatusChange } from '../hooks/youtubeContent/createTasksOnStatusChange'
import { downloadThumbnail } from '../hooks/youtubeContent/downloadThumbnail'
import { autoStatusTransitions } from '../hooks/youtubeContent/autoStatusTransitions'
// TODO: ScriptSectionBlock causes admin UI rendering issues
// import { ScriptSectionBlock } from '../blocks/ScriptSectionBlock'
@ -39,7 +40,7 @@ export const YouTubeContent: CollectionConfig = {
delete: isYouTubeManager,
},
hooks: {
afterChange: [createTasksOnStatusChange, downloadThumbnail],
afterChange: [createTasksOnStatusChange, downloadThumbnail, autoStatusTransitions],
beforeChange: [
// Auto-Slug generieren
({ data }) => {

View file

@ -0,0 +1,77 @@
import type { CollectionAfterChangeHook } from 'payload'
import { NotificationService } from '@/lib/jobs/NotificationService'
interface TransitionContext {
currentStatus: string
youtubeVideoId?: string | null
hasAllChecklistsComplete: boolean
}
/**
* Determines the next status based on current state and conditions
*/
export function getNextStatus(context: TransitionContext): string | null {
const { currentStatus, youtubeVideoId } = context
// Upload completed → published
if (currentStatus === 'upload_scheduled' && youtubeVideoId) {
return 'published'
}
return null
}
/**
* Checks if a manual transition should be suggested
*/
export function shouldTransitionStatus(
status: string,
context: { hasVideoFile?: boolean },
): boolean {
if (status === 'approved' && context.hasVideoFile) return true
return false
}
/**
* Hook: Automatische Status-Übergänge nach Änderungen
*/
export const autoStatusTransitions: CollectionAfterChangeHook = async ({
doc,
previousDoc,
req,
operation,
}) => {
if (operation !== 'update') return doc
const nextStatus = getNextStatus({
currentStatus: doc.status,
youtubeVideoId: doc.youtube?.videoId,
hasAllChecklistsComplete: false,
})
if (nextStatus && nextStatus !== doc.status) {
console.log(`[autoStatusTransitions] ${doc.id}: ${doc.status}${nextStatus}`)
await req.payload.update({
collection: 'youtube-content',
id: doc.id,
data: { status: nextStatus },
depth: 0,
})
// Notification
if (doc.assignedTo) {
const assignedToId = typeof doc.assignedTo === 'object' ? doc.assignedTo.id : doc.assignedTo
const notificationService = new NotificationService(req.payload)
await notificationService.createNotification({
recipientId: assignedToId,
type: 'system',
title: `Video-Status automatisch geändert: ${nextStatus}`,
link: `/admin/collections/youtube-content/${doc.id}`,
relatedVideoId: doc.id,
})
}
}
return doc
}

View file

@ -0,0 +1,32 @@
import { describe, it, expect } from 'vitest'
import { getNextStatus, shouldTransitionStatus } from '@/hooks/youtubeContent/autoStatusTransitions'
describe('Auto Status Transitions', () => {
it('should transition to published when upload is complete', () => {
const result = getNextStatus({
currentStatus: 'upload_scheduled',
youtubeVideoId: 'VID123',
hasAllChecklistsComplete: false,
})
expect(result).toBe('published')
})
it('should not transition if no video ID on upload_scheduled', () => {
const result = getNextStatus({
currentStatus: 'upload_scheduled',
youtubeVideoId: null,
hasAllChecklistsComplete: false,
})
expect(result).toBeNull()
})
it('should transition approved to upload_scheduled when video file exists', () => {
const result = shouldTransitionStatus('approved', { hasVideoFile: true })
expect(result).toBe(true)
})
it('should not suggest transition for published status', () => {
const result = shouldTransitionStatus('published', { hasVideoFile: true })
expect(result).toBe(false)
})
})