YouTube Models
Die YouTube-Domäne umfasst Video-Metadaten, tägliche Video-Statistiken, Video-Scores, Sync-Tracking, Related Channels, Research-Queries und den Pending-Import-Mechanismus.
YouTubeVideo
Metadaten eines YouTube-Videos. Verwendet video_id (string) als Primary Key statt auto-increment.
Tabelle: youtube_videos
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
video_id | string | PK | YouTube Video-ID (z.B. dQw4w9WgXcQ) |
social_profile_id | bigint (FK) | nein | Zugehöriges Channel-Profil |
channel_id | string | ja | YouTube Channel-ID |
title | string | ja | Video-Titel |
description | text | ja | Video-Beschreibung |
published_at | datetime | ja | Veröffentlichungsdatum |
duration_iso | string | ja | ISO 8601 Duration (z.B. PT4M33S) |
duration_seconds | integer | ja | Dauer in Sekunden |
definition | string | ja | Auflösung (hd, sd) |
caption | string | ja | Untertitel verfügbar |
category_id | string | ja | YouTube-Kategorie-ID |
live_status | string | ja | Livestream-Status |
live_streaming_details | json | ja | Livestream-Metadaten |
tags | json | ja | Video-Tags (Array) |
thumbnails | json | ja | Thumbnail-URLs (verschiedene Größen) |
thumbnail_url | string | ja | Standard-Thumbnail-URL |
thumbnail_path | string | ja | R2 Thumbnail-Pfad (Original) |
thumbnail_path_medium | string | ja | R2 Thumbnail-Pfad (Medium-Variante) |
thumbnail_path_thumbnail | string | ja | R2 Thumbnail-Pfad (Thumb-Variante) |
thumbnail_blurhash | text | ja | BlurHash-Placeholder fuer Thumbnail (LQIP) |
thumbnail_hash | string | ja | Thumbnail Content-Hash |
privacy_status | string | ja | public, unlisted, private |
embeddable | boolean | ja | Einbettbar |
made_for_kids | boolean | ja | Made for Kids |
data | json | ja | Rohdaten der YouTube API |
is_removed | boolean | ja | Video gelöscht/entfernt |
removed_at | datetime | ja | Zeitpunkt der Entfernung |
is_short | boolean | ja | Admin-Markierung als YouTube Short (null = nicht markiert) |
is_short_marked_at | datetime | ja | Zeitpunkt der Admin-Markierung |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Relations:
| Methode | Typ | Related Model | FK | Beschreibung |
|---|---|---|---|---|
profile() | belongsTo | SocialProfile | social_profile_id | Zugehöriger Channel |
dailyMetrics() | hasMany | YouTubeVideoDailyMetric | youtube_video_id | Tägliche Statistiken |
scores() | hasMany | YouTubeVideoScore | youtube_video_id | Score-Verlauf |
latestScore() | hasOne | YouTubeVideoScore | youtube_video_id | Aktueller Score (whereNotNull('score')->orderByDesc('date')) |
publishingStats() | hasMany | YouTubePublishingWeeklyStat | social_profile_id | Publishing-Statistiken pro Wochentag |
Casts:
'published_at' => 'datetime',
'live_streaming_details' => 'array',
'tags' => 'array',
'thumbnails' => 'array',
'data' => 'array',
'embeddable' => 'boolean',
'made_for_kids' => 'boolean',
'is_removed' => 'boolean',
'removed_at' => 'datetime',
'is_short' => 'boolean',
'is_short_marked_at' => 'datetime',
Methoden:
| Methode | Return | Beschreibung |
|---|---|---|
isEffectiveShort() | bool | Ist dieses Video ein YouTube Short? Admin-Markierung (is_short) hat Prioritaet, sonst Auto-Erkennung (duration_seconds ≤ 60) |
getDisplayThumbnailUrl($size) | string | Beste verfuegbare Thumbnail-URL mit 5-stufiger Fallback-Kette: R2-Variante → R2-Original → YouTube-URL → Thumbnails-JSON → Placeholder-SVG. Sizes: thumb, sm, medium, md, original, lg |
Location: app/Models/YouTubeVideo.php
YouTubeVideoDailyMetric
Tägliche Statistiken eines YouTube-Videos (Views, Likes, Kommentare, Favorites).
Tabelle: youtube_video_daily_metrics
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | bigint | PK | Primary Key |
youtube_video_id | string (FK) | nein | Zugehörige Video-ID |
date | date | nein | Datum des Snapshots |
view_count | bigint | ja | Aufrufe |
like_count | bigint | ja | Likes |
comment_count | bigint | ja | Kommentare |
favorite_count | bigint | ja | Favorites |
raw | json | ja | Rohdaten |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Relations:
| Methode | Typ | Related Model | FK | Beschreibung |
|---|---|---|---|---|
video() | belongsTo | YouTubeVideo | youtube_video_id | Zugehöriges Video |
Casts: date → date, raw → array
Location: app/Models/YouTubeVideoDailyMetric.php
YouTubeVideoScore
Berechneter Score (0–100) für ein Video. Gewichtung: 40% View Performance + 25% Engagement + 20% Growth + 15% Freshness. Differenziert zwischen regulären Videos und Shorts.
Tabelle: youtube_video_scores
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | bigint | PK | Primary Key |
youtube_video_id | string (FK) | nein | Zugehörige Video-ID |
social_profile_id | bigint (FK) | nein | Zugehöriger Channel |
date | date | nein | Berechnungsdatum |
score | integer | nein | Gesamtscore (0–100) |
status | string | nein | pending, preliminary, stable, no_data |
is_short | boolean | nein | YouTube Short |
view_performance_score | integer | ja | View-Performance-Komponente (40%) |
engagement_score | integer | ja | Engagement-Komponente (25%) |
growth_score | integer | ja | Wachstums-Komponente (20%) |
freshness_score | integer | ja | Aktualitäts-Komponente (15%) |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Relations:
| Methode | Typ | Related Model | FK | Beschreibung |
|---|---|---|---|---|
video() | belongsTo | YouTubeVideo | youtube_video_id | Zugehöriges Video |
profile() | belongsTo | SocialProfile | social_profile_id | Zugehöriger Channel |
Casts:
'date' => 'date',
'score' => 'integer',
'view_performance_score' => 'integer',
'engagement_score' => 'integer',
'growth_score' => 'integer',
'freshness_score' => 'integer',
'is_short' => 'boolean',
Location: app/Models/YouTubeVideoScore.php
YouTubeVideoSync
Tracking-Status der Video-Synchronisation pro Channel. Steuert die automatische Video-Pipeline und erfasst Fail-Streaks für Fehlerbehandlung.
Tabelle: youtube_video_syncs
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | bigint | PK | Primary Key |
social_profile_id | bigint (FK) | nein | Zugehoeriger Channel |
requested_by | bigint (FK) | ja | User, der Sync ausgeloest hat |
uploads_playlist_id | string | ja | YouTube Uploads-Playlist-ID |
last_sync_at | datetime | ja | Letzter Sync-Zeitpunkt |
auto_sync_enabled | boolean | nein | Automatischer Sync aktiviert (default: false) |
last_fail_date | date | ja | Datum des letzten Sync-Fehlers |
consecutive_fail_days | integer | ja | Aufeinanderfolgende Fehler-Tage |
rss_etag | string | ja | ETag fuer RSS Conditional Polling (304 Not Modified) |
rss_last_modified | string | ja | Last-Modified Header fuer RSS |
rss_last_poll_at | datetime | ja | Letztes RSS-Feed-Polling |
last_new_video_detected_at | datetime | ja | Letztes neues Video erkannt (WebSub/RSS) |
video_tracking_start_date | date | ja | PRO-Startdatum fuer unbegrenztes Video-Tracking |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Relations:
| Methode | Typ | Related Model | FK | Beschreibung |
|---|---|---|---|---|
profile() | belongsTo | SocialProfile | social_profile_id | Zugehöriger Channel |
requester() | belongsTo | User | requested_by | Anfordernder User |
Casts:
'last_sync_at' => 'datetime',
'auto_sync_enabled' => 'boolean',
'last_fail_date' => 'date',
'consecutive_fail_days' => 'integer',
'rss_last_poll_at' => 'datetime',
'last_new_video_detected_at' => 'datetime',
'video_tracking_start_date' => 'date',
Location: app/Models/YouTubeVideoSync.php
YouTubeRelatedChannel
Verwandte YouTube-Channels mit Relevanz-Score. Wird per youtube:auto-fill-related-channels (alle 5 Min) befüllt.
Tabelle: youtube_related_channels
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | bigint | PK | Primary Key |
social_profile_id | bigint (FK) | nein | Quell-Channel |
related_social_profile_id | bigint (FK) | nein | Verwandter Channel |
relevance_score | integer | ja | Relevanz (0–100) |
search_query | string | ja | Verwendete Suchabfrage |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Relations:
| Methode | Typ | Related Model | FK | Beschreibung |
|---|---|---|---|---|
sourceProfile() | belongsTo | SocialProfile | social_profile_id | Quell-Channel |
relatedProfile() | belongsTo | SocialProfile | related_social_profile_id | Verwandter Channel |
Casts: relevance_score → integer
Ausschluss-Filter: Bei Anzeige in der UI immer den Related-Channel gegen notExcluded() filtern, damit keine gesperrten/sanitized/archivierten Profile erscheinen.
Location: app/Models/YouTubeRelatedChannel.php
YouTubeResearchQuery
Gespeicherte YouTube-Recherche-Abfragen mit Ergebnissen. Für das Admin-Feature "YouTube Research".
Tabelle: youtube_research_queries
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | bigint | PK | Primary Key |
query | string | nein | Suchbegriff |
topic_id | string | ja | YouTube Topic-ID |
result_count | integer | ja | Anzahl Ergebnisse |
result_urls | json | ja | Gefundene Channel-URLs |
response_payload | json | ja | YouTube API Response |
error_payload | json | ja | Fehlerdaten bei API-Fehler |
error_message | text | ja | Fehlermeldung |
imported_at | datetime | ja | Importiert am |
batch_id | uuid | ja | Batch-Identifier (gruppiert Batch-Suchen) |
relevance_language | string | ja | ISO 639-1 Sprache fuer Suche |
region_code | string | ja | ISO 3166-1 Region fuer Suche |
min_subscribers | integer | ja | Min. Subscriber-Filter |
min_videos | integer | ja | Min. Video-Filter |
filtered_count | integer | ja | Ergebnis-Anzahl nach Qualitaetsfilter |
status | string | ja | pending, running, completed, failed |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Casts: result_urls → array, response_payload → array, error_payload → array, result_count → integer, imported_at → datetime, min_subscribers → integer, min_videos → integer, filtered_count → integer
Location: app/Models/YouTubeResearchQuery.php
YouTubeResearchKeywordCooldown
14-Tage-Cooldown fuer Keywords, die in der Research Batch-Suche als Suggestions verwendet wurden. Verhindert wiederholte Vorschlaege derselben Keywords.
Tabelle: youtube_research_keyword_cooldowns
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | bigint | PK | Primary Key |
keyword | string | nein | Keyword (unique) |
used_at | datetime | nein | Letzter Verwendungszeitpunkt |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Casts: used_at → datetime
Location: app/Models/YouTubeResearchKeywordCooldown.php
PendingYouTubeChannelImport
Queue für entdeckte YouTube-Channels, die noch importiert werden müssen. Unterstützt Retry-Logik mit konfiguriertem Maximum.
Tabelle: pending_you_tube_channel_imports
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | bigint | PK | Primary Key |
source_social_profile_id | bigint (FK) | nein | Entdeckt über diesen Channel |
youtube_channel_id | string | nein | YouTube Channel-ID zum Importieren |
relevance_score | integer | ja | Relevanz-Score |
search_query | string | ja | Verwendete Suchabfrage |
retry_count | integer | nein | Bisherige Retry-Versuche |
last_retry_at | datetime | ja | Letzter Retry-Zeitpunkt |
last_error | text | ja | Letzte Fehlermeldung |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Relations:
| Methode | Typ | Related Model | FK | Beschreibung |
|---|---|---|---|---|
sourceProfile() | belongsTo | SocialProfile | source_social_profile_id | Entdeckender Channel |
Casts: relevance_score → integer, retry_count → integer, last_retry_at → datetime
Konstanten: MAX_RETRIES = 5, MAX_AGE_DAYS = 7
Methoden: canRetry(), isExpired(), recordFailure()
Location: app/Models/PendingYouTubeChannelImport.php
YouTubePublishingWeeklyStat
Aggregierte Publishing-Statistiken pro YouTube-Kanal pro Wochentag (Plan 13). Wird woechentlich durch youtube:aggregate-publishing-stats neu berechnet.
Tabelle: youtube_publishing_weekly_stats
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | bigint | PK | Primary Key |
social_profile_id | bigint (FK) | nein | Zugehoeriger Channel |
weekday | tinyint | nein | ISO-Wochentag (1=Montag, 7=Sonntag) |
video_count | integer | nein | Anzahl regulaere Videos |
shorts_count | integer | nein | Anzahl YouTube Shorts |
vfr_median | decimal(10,4) | ja | VFR-Median (Views/Followers) |
best_hour_utc | tinyint | ja | Beste Uploadstunde (UTC, 0-23) |
hour_distribution | jsonb | ja | Upload-Verteilung pro Stunde {hour: count} |
engagement_rate_median | decimal(10,6) | ja | Median Engagement-Rate (Likes+Comments)/Followers |
like_to_view_ratio_median | decimal(10,6) | ja | Median Like-to-View-Ratio |
prev_video_count | integer | ja | Video-Anzahl der Vorwoche (Trend E11) |
prev_vfr_median | decimal(10,4) | ja | VFR der Vorwoche (Trend E11) |
vfr_p25 | decimal(10,4) | ja | VFR 25. Perzentil (Konfidenz E12) |
vfr_p75 | decimal(10,4) | ja | VFR 75. Perzentil (Konfidenz E12) |
week_start | date | ja | Montag der berechneten Woche |
sample_size | integer | nein | Gesamtanzahl analysierter Videos |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Unique Constraint: (social_profile_id, weekday) — eine Zeile pro Kanal pro Wochentag.
Relations:
| Methode | Typ | Related Model | FK | Beschreibung |
|---|---|---|---|---|
profile() | belongsTo | SocialProfile | social_profile_id | Zugehoeriger Channel |
Accessors:
| Accessor | Return | Beschreibung |
|---|---|---|
weekday_name | string | Deutscher Wochentag (Montag, Dienstag, ...) |
weekday_short | string | Kurz-Wochentag (Mo, Di, Mi, ...) |
Casts:
'weekday' => 'integer',
'video_count' => 'integer',
'shorts_count' => 'integer',
'vfr_median' => 'float',
'best_hour_utc' => 'integer',
'hour_distribution' => 'array',
'engagement_rate_median' => 'float',
'like_to_view_ratio_median' => 'float',
'prev_video_count' => 'integer',
'prev_vfr_median' => 'float',
'vfr_p25' => 'float',
'vfr_p75' => 'float',
'week_start' => 'date',
'sample_size' => 'integer',
Location: app/Models/YouTubePublishingWeeklyStat.php