From eeff82470177eccf55daa5a5505ba7c046897dff Mon Sep 17 00:00:00 2001 From: Lars Behrends Date: Sun, 12 Apr 2026 02:07:59 +0200 Subject: [PATCH] Add Jellyfin mappings and optimize cast queries Performance and settings updates: - Add a new jellyfin_library_mappings column to the settings table and wire it into the Settings model (update handling and default value). This enables storing Jellyfin library mappings in settings. - Optimize Cast::list by loading all cast filmography in a single joined query and grouping results per cast to avoid N+1 queries. - Remove per-item cast/staff loading in Media model to avoid repeated queries during list/search operations. - Remove game-specific enrichment from MediaController search to stop extra game info lookups during search responses. These changes reduce repeated DB calls and centralize Jellyfin mapping storage. --- api/controllers/MediaController.php | 12 +--------- api/database.php | 1 + api/models/Cast.php | 37 ++++++++++++++++++++++------- api/models/Media.php | 12 +--------- api/models/Settings.php | 8 +++++-- 5 files changed, 38 insertions(+), 32 deletions(-) diff --git a/api/controllers/MediaController.php b/api/controllers/MediaController.php index 3e04443..6d5e9b7 100644 --- a/api/controllers/MediaController.php +++ b/api/controllers/MediaController.php @@ -278,17 +278,7 @@ class MediaController { $limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 20; $result = $this->media->search($filters, $page, $limit); - - // Game-spezifische Daten für Games laden - foreach ($result['items'] as &$item) { - if ($item['type'] === 'Game') { - $gameInfo = $this->game->getGameInfoForList($item['id']); - if ($gameInfo) { - $item = array_merge($item, $gameInfo); - } - } - } - + return ['success' => true, 'data' => $result]; } diff --git a/api/database.php b/api/database.php index 4c9e1fc..b218125 100644 --- a/api/database.php +++ b/api/database.php @@ -357,6 +357,7 @@ class Database { auto_play_trailers BOOLEAN DEFAULT 0, language TEXT DEFAULT 'en', theme TEXT DEFAULT 'system', + jellyfin_library_mappings JSON, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) diff --git a/api/models/Cast.php b/api/models/Cast.php index 4291049..8df83fa 100644 --- a/api/models/Cast.php +++ b/api/models/Cast.php @@ -49,16 +49,37 @@ class Cast extends BaseModel { $items = $this->findAll($conditions, 'createdAt DESC', $limit, $offset); $total = $this->count($conditions); - - // Add filmography to each cast member - foreach ($items as &$item) { - $item['filmography'] = $this->getMediaForCast($item['id']); - // Extract unique media types - $mediaTypes = array_unique(array_column($item['filmography'], 'category')); - $item['media_types'] = array_values($mediaTypes); + // Load filmography for all cast members in a single query + if (!empty($items)) { + $castIds = array_column($items, 'id'); + $placeholders = str_repeat('?,', count($castIds) - 1) . '?'; + + $stmt = $this->pdo->prepare(" + SELECT mc.cast_id, m.id, m.title, m.year, m.poster, m.category, m.type, mc.role, mc.characterName + FROM media m + INNER JOIN media_cast mc ON m.id = mc.media_id + WHERE mc.cast_id IN ($placeholders) + ORDER BY m.year DESC + "); + $stmt->execute($castIds); + $allFilmography = $stmt->fetchAll(); + + // Group filmography by cast_id + $filmographyByCast = []; + foreach ($allFilmography as $film) { + $castId = $film['cast_id']; + unset($film['cast_id']); + $filmographyByCast[$castId][] = $film; + } + + // Attach filmography to each cast member + foreach ($items as &$item) { + $item['filmography'] = $filmographyByCast[$item['id']] ?? []; + $mediaTypes = array_unique(array_column($item['filmography'], 'category')); + $item['media_types'] = array_values($mediaTypes); + } } - return [ 'items' => $items, 'total' => $total, diff --git a/api/models/Media.php b/api/models/Media.php index 8fcc1fd..4f66f1a 100644 --- a/api/models/Media.php +++ b/api/models/Media.php @@ -64,12 +64,7 @@ class Media extends BaseModel { $stmt = $this->pdo->prepare($query); $stmt->execute($params); $items = $stmt->fetchAll(); - - // Cast-Mitglieder für jedes Medium laden - foreach ($items as &$item) { - $item['staff'] = $this->getCastForMedia($item['id']); - } - + // Count Query $countQuery = "SELECT COUNT(*) FROM {$this->table} WHERE 1=1"; $countParams = []; @@ -93,11 +88,6 @@ class Media extends BaseModel { } else { $items = $this->findAll($conditions, 'createdAt DESC', $limit, $offset); $total = $this->count($conditions); - - // Cast-Mitglieder für jedes Medium laden - foreach ($items as &$item) { - $item['staff'] = $this->getCastForMedia($item['id']); - } } return [ diff --git a/api/models/Settings.php b/api/models/Settings.php index c031df0..104df43 100644 --- a/api/models/Settings.php +++ b/api/models/Settings.php @@ -51,10 +51,14 @@ class Settings extends BaseModel { if (isset($data['language'])) { $updateData['language'] = $data['language']; } - + if (isset($data['theme'])) { $updateData['theme'] = $data['theme']; } + + if (isset($data['jellyfin_library_mappings'])) { + $updateData['jellyfin_library_mappings'] = $data['jellyfin_library_mappings']; + } // Check if settings row exists $existing = $this->findById(1); @@ -72,7 +76,7 @@ class Settings extends BaseModel { 'auto_play_trailers' => 0, 'language' => 'en', 'theme' => 'system', - 'theme' => 'system' + 'jellyfin_library_mappings' => '' ]; $mergedData = array_merge($defaultData, $updateData); $this->create($mergedData);