Remove obsolete test scripts and add new API controllers for dashboard and game management

- Deleted test scripts: test_jellyfin_execution.php, test_stash.php, test_xbvr.php, test_xbvr_sync.php, vite.config.js
- Added DashboardController for fetching dashboard statistics and recent activity
- Added GameController for managing games, including fetching all games, game details, and games by category
- Introduced various check scripts to validate database structures and data integrity for adult videos, games, gender data, posters, and TV show actors
This commit is contained in:
Lars Behrends
2026-01-18 01:42:03 +01:00
parent b728b0c72d
commit eb1ec1153d
29 changed files with 2685 additions and 2454 deletions

View File

@@ -15,6 +15,139 @@ class Actor extends Model
'metadata' => 'array'
];
/**
* Get all actors with filtering and pagination
*/
public function findAll(array $filters = [], int $limit = null, int $offset = 0): array
{
$sql = "
SELECT a.*,
COUNT(DISTINCT am.movie_id) as movie_count,
COUNT(DISTINCT te.tv_show_id) as tv_show_count,
COUNT(DISTINCT aav.adult_video_id) as adult_video_count
FROM actors a
LEFT JOIN actor_movie am ON a.id = am.actor_id
LEFT JOIN actor_tv_episode ate ON a.id = ate.actor_id
LEFT JOIN tv_episodes te ON ate.tv_episode_id = te.id
LEFT JOIN actor_adult_video aav ON a.id = aav.actor_id
";
$params = [];
$whereClauses = [];
// Search filter
if (!empty($filters['search'])) {
$whereClauses[] = "a.name LIKE :search";
$params['search'] = "%{$filters['search']}%";
}
// Gender filter
if (!empty($filters['gender'])) {
$whereClauses[] = "JSON_UNQUOTE(JSON_EXTRACT(a.metadata, '$.gender')) = :gender";
$params['gender'] = strtoupper($filters['gender']);
}
// Adult filter
if (isset($filters['adult'])) {
if ($filters['adult']) {
$whereClauses[] = "aav.adult_video_id IS NOT NULL";
} else {
$whereClauses[] = "aav.adult_video_id IS NULL";
}
}
// Combine WHERE clauses
if (!empty($whereClauses)) {
$sql .= " WHERE " . implode(' AND ', $whereClauses);
}
$sql .= " GROUP BY a.id";
// Add sorting
$sortBy = $filters['sort'] ?? 'name';
$sortOrder = $filters['order'] ?? 'asc';
switch ($sortBy) {
case 'name':
$sql .= " ORDER BY a.name {$sortOrder}";
break;
case 'age':
$sql .= " ORDER BY JSON_EXTRACT(a.metadata, '$.age') {$sortOrder}";
break;
case 'media_count':
$sql .= " ORDER BY (COUNT(DISTINCT am.movie_id) + COUNT(DISTINCT te.tv_show_id) + COUNT(DISTINCT aav.adult_video_id)) {$sortOrder}";
break;
case 'movie_count':
$sql .= " ORDER BY COUNT(DISTINCT am.movie_id) {$sortOrder}";
break;
case 'tv_show_count':
$sql .= " ORDER BY COUNT(DISTINCT te.tv_show_id) {$sortOrder}";
break;
case 'adult_count':
$sql .= " ORDER BY COUNT(DISTINCT aav.adult_video_id) {$sortOrder}";
break;
default:
$sql .= " ORDER BY a.name ASC";
}
if ($limit) {
$sql .= " LIMIT :limit OFFSET :offset";
$params['limit'] = $limit;
$params['offset'] = $offset;
}
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Count actors with filters
*/
public function count(array $filters = []): int
{
$sql = "
SELECT COUNT(DISTINCT a.id) as total
FROM actors a
LEFT JOIN actor_movie am ON a.id = am.actor_id
LEFT JOIN actor_tv_episode ate ON a.id = ate.actor_id
LEFT JOIN tv_episodes te ON ate.tv_episode_id = te.id
LEFT JOIN actor_adult_video aav ON a.id = aav.actor_id
";
$params = [];
$whereClauses = [];
// Search filter
if (!empty($filters['search'])) {
$whereClauses[] = "a.name LIKE :search";
$params['search'] = "%{$filters['search']}%";
}
// Gender filter
if (!empty($filters['gender'])) {
$whereClauses[] = "JSON_UNQUOTE(JSON_EXTRACT(a.metadata, '$.gender')) = :gender";
$params['gender'] = strtoupper($filters['gender']);
}
// Adult filter
if (isset($filters['adult'])) {
if ($filters['adult']) {
$whereClauses[] = "aav.adult_video_id IS NOT NULL";
} else {
$whereClauses[] = "aav.adult_video_id IS NULL";
}
}
// Combine WHERE clauses
if (!empty($whereClauses)) {
$sql .= " WHERE " . implode(' AND ', $whereClauses);
}
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
$result = $stmt->fetch(\PDO::FETCH_ASSOC);
return (int)$result['total'];
}
/**
* Get all movies this actor is associated with
*/
@@ -29,24 +162,83 @@ class Actor extends Model
ORDER BY m.release_date DESC, m.title ASC
");
$stmt->execute(['actor_id' => $this->id]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
$movies = $stmt->fetchAll(\PDO::FETCH_ASSOC);
// Process poster URLs for each movie
foreach ($movies as &$movie) {
// Use poster_url field directly if available
if (!empty($movie['poster_url'])) {
// Keep the existing poster_url as-is since it's already in the correct format
}
// Also process metadata for additional fields
if (!empty($movie['metadata'])) {
$metadata = json_decode($movie['metadata'], true);
// Extract poster aspect ratio from metadata if available
if (!empty($metadata['poster_aspect_ratio'])) {
$movie['poster_aspect_ratio'] = $metadata['poster_aspect_ratio'];
}
// If no poster_url in main field, try to get it from metadata
if (empty($movie['poster_url'])) {
if (!empty($metadata['local_cover_path'])) {
$movie['poster_url'] = $metadata['local_cover_path'];
} elseif (!empty($metadata['cover_url'])) {
$movie['poster_url'] = $metadata['cover_url'];
}
}
}
}
return $movies;
}
/**
* Get all TV shows this actor is associated with
* Get all TV shows this actor is associated with (via episodes)
*/
public function tvShows()
{
$stmt = $this->pdo->prepare("
SELECT ts.*, s.display_name as source_name
SELECT DISTINCT ts.*, s.display_name as source_name
FROM tv_shows ts
JOIN sources s ON ts.source_id = s.id
JOIN actor_tv_show ats ON ts.id = ats.tv_show_id
WHERE ats.actor_id = :actor_id
JOIN tv_episodes te ON ts.id = te.tv_show_id
JOIN actor_tv_episode ate ON te.id = ate.tv_episode_id
WHERE ate.actor_id = :actor_id
ORDER BY ts.first_air_date DESC, ts.title ASC
");
$stmt->execute(['actor_id' => $this->id]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
$tvShows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
// Process poster URLs for each TV show
foreach ($tvShows as &$tvShow) {
// Use poster_url field directly if available
if (!empty($tvShow['poster_url'])) {
// Keep the existing poster_url as-is since it's already in the correct format
}
// Also process metadata for additional fields
if (!empty($tvShow['metadata'])) {
$metadata = json_decode($tvShow['metadata'], true);
// Extract poster aspect ratio from metadata if available
if (!empty($metadata['poster_aspect_ratio'])) {
$tvShow['poster_aspect_ratio'] = $metadata['poster_aspect_ratio'];
}
// If no poster_url in main field, try to get it from metadata
if (empty($tvShow['poster_url'])) {
if (!empty($metadata['local_cover_path'])) {
$tvShow['poster_url'] = $metadata['local_cover_path'];
} elseif (!empty($metadata['cover_url'])) {
$tvShow['poster_url'] = $metadata['cover_url'];
}
}
}
}
return $tvShows;
}
/**
@@ -63,7 +255,41 @@ class Actor extends Model
ORDER BY av.release_date DESC, av.title ASC
");
$stmt->execute(['actor_id' => $this->id]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
$adultVideos = $stmt->fetchAll(\PDO::FETCH_ASSOC);
// Process poster URLs for each adult video
foreach ($adultVideos as &$adultVideo) {
// Use poster_url field directly if available
if (!empty($adultVideo['poster_url'])) {
// Keep the existing poster_url as-is since it's already in the correct format
}
// Also process metadata for additional fields
if (!empty($adultVideo['metadata'])) {
$metadata = json_decode($adultVideo['metadata'], true);
// Extract poster aspect ratio from metadata if available
if (!empty($metadata['poster_aspect_ratio'])) {
$adultVideo['poster_aspect_ratio'] = $metadata['poster_aspect_ratio'];
}
// If no poster_url in main field, try to get it from metadata
if (empty($adultVideo['poster_url'])) {
if (!empty($metadata['local_cover_path'])) {
$adultVideo['poster_url'] = $metadata['local_cover_path'];
} elseif (!empty($metadata['cover_url'])) {
$adultVideo['poster_url'] = $metadata['cover_url'];
}
}
// Add screenshot URL if available
if (!empty($metadata['screenshot_url'])) {
$adultVideo['screenshot_url'] = $metadata['screenshot_url'];
}
}
}
return $adultVideos;
}
/**
@@ -74,12 +300,13 @@ class Actor extends Model
$stmt = $this->pdo->prepare("
SELECT
COUNT(DISTINCT am.movie_id) as movie_count,
COUNT(DISTINCT ats.tv_show_id) as tv_show_count,
COUNT(DISTINCT te.tv_show_id) as tv_show_count,
COUNT(DISTINCT aav.adult_video_id) as adult_video_count,
COUNT(DISTINCT am.movie_id) + COUNT(DISTINCT ats.tv_show_id) + COUNT(DISTINCT aav.adult_video_id) as total_media_count
COUNT(DISTINCT am.movie_id) + COUNT(DISTINCT te.tv_show_id) + COUNT(DISTINCT aav.adult_video_id) as total_media_count
FROM actors a
LEFT JOIN actor_movie am ON a.id = am.actor_id
LEFT JOIN actor_tv_show ats ON a.id = ats.actor_id
LEFT JOIN actor_tv_episode ate ON a.id = ate.actor_id
LEFT JOIN tv_episodes te ON ate.tv_episode_id = te.id
LEFT JOIN actor_adult_video aav ON a.id = aav.actor_id
WHERE a.id = :actor_id
");
@@ -196,8 +423,8 @@ class Actor extends Model
// Build ORDER BY clause
$orderBy = match ($sort) {
'name_desc' => 'name DESC',
'media_desc' => '(SELECT COUNT(*) FROM actor_movie WHERE actor_id = actors.id) + (SELECT COUNT(*) FROM actor_tv_show WHERE actor_id = actors.id) + (SELECT COUNT(*) FROM actor_adult_video WHERE actor_id = actors.id) DESC',
'media_asc' => '(SELECT COUNT(*) FROM actor_movie WHERE actor_id = actors.id) + (SELECT COUNT(*) FROM actor_tv_show WHERE actor_id = actors.id) + (SELECT COUNT(*) FROM actor_adult_video WHERE actor_id = actors.id) ASC',
'media_desc' => '(SELECT COUNT(*) FROM actor_movie WHERE actor_id = actors.id) + (SELECT COUNT(DISTINCT te.tv_show_id) FROM actor_tv_episode ate JOIN tv_episodes te ON ate.tv_episode_id = te.id WHERE ate.actor_id = actors.id) + (SELECT COUNT(*) FROM actor_adult_video WHERE actor_id = actors.id) DESC',
'media_asc' => '(SELECT COUNT(*) FROM actor_movie WHERE actor_id = actors.id) + (SELECT COUNT(DISTINCT te.tv_show_id) FROM actor_tv_episode ate JOIN tv_episodes te ON ate.tv_episode_id = te.id WHERE ate.actor_id = actors.id) + (SELECT COUNT(*) FROM actor_adult_video WHERE actor_id = actors.id) ASC',
default => 'name ASC'
};
@@ -206,7 +433,7 @@ class Actor extends Model
SELECT
a.*,
(SELECT COUNT(*) FROM actor_movie WHERE actor_id = a.id) as movie_count,
(SELECT COUNT(*) FROM actor_tv_show WHERE actor_id = a.id) as tv_show_count,
(SELECT COUNT(DISTINCT te.tv_show_id) FROM actor_tv_episode ate JOIN tv_episodes te ON ate.tv_episode_id = te.id WHERE ate.actor_id = a.id) as tv_show_count,
(SELECT COUNT(*) FROM actor_adult_video WHERE actor_id = a.id) as adult_video_count
FROM actors a
{$whereClause}
@@ -279,10 +506,11 @@ class Actor extends Model
private function getActorTvShows(int $actorId): array
{
$stmt = $this->pdo->prepare("
SELECT ts.id, ts.title
SELECT DISTINCT ts.id, ts.title
FROM tv_shows ts
JOIN actor_tv_show ats ON ts.id = ats.tv_show_id
WHERE ats.actor_id = ?
JOIN tv_episodes te ON ts.id = te.tv_show_id
JOIN actor_tv_episode ate ON te.id = ate.tv_episode_id
WHERE ate.actor_id = ?
ORDER BY ts.title
");
$stmt->execute([$actorId]);

View File

@@ -230,7 +230,7 @@ class AdultVideo extends Model
*/
public function updateCastField(): bool
{
$actors = $this->actors();
$actors = $this->actors($this->id);
$actorNames = array_column($actors, 'name');
$castString = implode(', ', $actorNames);
@@ -239,6 +239,194 @@ class AdultVideo extends Model
]);
}
/**
* Find single adult video by ID with metadata processing
*/
public function find(int $id): ?array
{
$stmt = $this->pdo->prepare("
SELECT av.*, s.display_name as source_name
FROM adult_videos av
JOIN sources s ON av.source_id = s.id
WHERE av.id = :id
");
$stmt->execute(['id' => $id]);
$video = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$video) {
return null;
}
// Process metadata to extract additional fields
if (!empty($video['metadata'])) {
$metadata = json_decode($video['metadata'], true);
// Extract poster aspect ratio from metadata if available
if (!empty($metadata['poster_aspect_ratio'])) {
$video['poster_aspect_ratio'] = $metadata['poster_aspect_ratio'];
}
// Use local cover path if available, otherwise fall back to original URL
if (!empty($metadata['local_cover_path'])) {
$video['poster_url'] = $metadata['local_cover_path'];
} elseif (!empty($metadata['cover_url'])) {
$video['poster_url'] = $metadata['cover_url'];
}
// Add screenshot URL if available
if (!empty($metadata['screenshot_url'])) {
$video['screenshot_url'] = $metadata['screenshot_url'];
}
// Add actors data if available
if (!empty($metadata['actors'])) {
$video['actors'] = $metadata['actors'];
}
}
return $video;
}
/**
* Get all adult videos with filtering and pagination
*/
public function findAll(array $filters = [], int $limit = null, int $offset = 0): array
{
$sql = "
SELECT av.*, s.display_name as source_name
FROM adult_videos av
JOIN sources s ON av.source_id = s.id
";
$params = [];
$whereClauses = [];
// Search filter
if (!empty($filters['search'])) {
$whereClauses[] = "(av.title LIKE :search OR av.overview LIKE :search)";
$params['search'] = "%{$filters['search']}%";
}
// Genre filter
if (!empty($filters['genre'])) {
$whereClauses[] = "av.genre = :genre";
$params['genre'] = $filters['genre'];
}
// Year filter
if (!empty($filters['year'])) {
$whereClauses[] = "YEAR(av.release_date) = :year";
$params['year'] = $filters['year'];
}
// Source filter
if (!empty($filters['sources'])) {
$sources = is_array($filters['sources']) ? $filters['sources'] : [$filters['sources']];
$placeholders = [];
foreach ($sources as $index => $source) {
$placeholders[] = ":source_{$index}";
$params["source_{$index}"] = $source;
}
$whereClauses[] = "s.display_name IN (" . implode(',', $placeholders) . ")";
}
// Combine WHERE clauses
if (!empty($whereClauses)) {
$sql .= " WHERE " . implode(' AND ', $whereClauses);
}
// Add ordering and pagination
$sql .= " ORDER BY av.release_date DESC, av.title ASC";
if ($limit) {
$sql .= " LIMIT :limit OFFSET :offset";
$params['limit'] = $limit;
$params['offset'] = $offset;
}
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
$results = $stmt->fetchAll(\PDO::FETCH_ASSOC);
// Process metadata to extract additional fields
foreach ($results as &$video) {
if (!empty($video['metadata'])) {
$metadata = json_decode($video['metadata'], true);
// Extract poster aspect ratio from metadata if available
if (!empty($metadata['poster_aspect_ratio'])) {
$video['poster_aspect_ratio'] = $metadata['poster_aspect_ratio'];
}
// Use local cover path if available, otherwise fall back to original URL
if (!empty($metadata['local_cover_path'])) {
$video['poster_url'] = $metadata['local_cover_path'];
} elseif (!empty($metadata['cover_url'])) {
$video['poster_url'] = $metadata['cover_url'];
}
// Add actors data if available
if (!empty($metadata['actors'])) {
$video['actors'] = $metadata['actors'];
}
}
}
return $results;
}
/**
* Count adult videos with filters
*/
public function count(array $filters = []): int
{
$sql = "
SELECT COUNT(*) as total
FROM adult_videos av
JOIN sources s ON av.source_id = s.id
";
$params = [];
$whereClauses = [];
// Search filter
if (!empty($filters['search'])) {
$whereClauses[] = "(av.title LIKE :search OR av.overview LIKE :search)";
$params['search'] = "%{$filters['search']}%";
}
// Genre filter
if (!empty($filters['genre'])) {
$whereClauses[] = "av.genre = :genre";
$params['genre'] = $filters['genre'];
}
// Year filter
if (!empty($filters['year'])) {
$whereClauses[] = "YEAR(av.release_date) = :year";
$params['year'] = $filters['year'];
}
// Source filter
if (!empty($filters['sources'])) {
$sources = is_array($filters['sources']) ? $filters['sources'] : [$filters['sources']];
$placeholders = [];
foreach ($sources as $index => $source) {
$placeholders[] = ":source_{$index}";
$params["source_{$index}"] = $source;
}
$whereClauses[] = "s.display_name IN (" . implode(',', $placeholders) . ")";
}
// Combine WHERE clauses
if (!empty($whereClauses)) {
$sql .= " WHERE " . implode(' AND ', $whereClauses);
}
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
$result = $stmt->fetch(\PDO::FETCH_ASSOC);
return (int)$result['total'];
}
/**
* Get available genres for filtering
*/

View File

@@ -356,62 +356,93 @@ class TvShow extends Model
public function getSeasonsWithEpisodes(): array
{
// Get all episodes for this TV show, grouped by season
$stmt = $this->pdo->prepare("
SELECT season_number,
COUNT(*) as episode_count,
SUM(CASE WHEN watched = 1 THEN 1 ELSE 0 END) as watched_episodes
FROM tv_episodes
WHERE tv_show_id = :tv_show_id
GROUP BY season_number
ORDER BY season_number ASC
");
$stmt->execute(['tv_show_id' => $this->id]);
$seasonStats = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$seasons = [];
// For each season, get the episodes and create a season object
foreach ($seasonStats as $stat) {
$seasonNumber = $stat['season_number'];
// Get episodes for this season
try {
// Get all episodes for this TV show, grouped by season
$stmt = $this->pdo->prepare("
SELECT e.*
FROM tv_episodes e
WHERE e.tv_show_id = :tv_show_id AND e.season_number = :season_number
ORDER BY e.episode_number ASC
SELECT season_number,
COUNT(*) as episode_count,
SUM(CASE WHEN watched = 1 THEN 1 ELSE 0 END) as watched_episodes
FROM tv_episodes
WHERE tv_show_id = :tv_show_id
GROUP BY season_number
ORDER BY season_number ASC
");
$stmt->execute([
'tv_show_id' => $this->id,
'season_number' => $seasonNumber
]);
$episodes = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$stmt->execute(['tv_show_id' => $this->id]);
$seasonStats = $stmt->fetchAll(\PDO::FETCH_ASSOC);
// Add actors for each episode
foreach ($episodes as &$episode) {
$episodeStmt = $this->pdo->prepare("
SELECT a.*
FROM actors a
JOIN actor_tv_episode ate ON a.id = ate.actor_id
WHERE ate.tv_episode_id = :tv_episode_id
ORDER BY a.name ASC
$seasons = [];
// For each season, get the episodes and create a season object
foreach ($seasonStats as $stat) {
$seasonNumber = $stat['season_number'];
// Get episodes for this season
$stmt = $this->pdo->prepare("
SELECT e.*
FROM tv_episodes e
WHERE e.tv_show_id = :tv_show_id AND e.season_number = :season_number
ORDER BY e.episode_number ASC
");
$episodeStmt->execute(['tv_episode_id' => $episode['id']]);
$episode['actors'] = $episodeStmt->fetchAll(\PDO::FETCH_ASSOC);
$stmt->execute([
'tv_show_id' => $this->id,
'season_number' => $seasonNumber
]);
$episodes = $stmt->fetchAll(\PDO::FETCH_ASSOC);
// Add actors for each episode
foreach ($episodes as &$episode) {
try {
$episodeStmt = $this->pdo->prepare("
SELECT a.*
FROM actors a
JOIN actor_tv_episode ate ON a.id = ate.actor_id
WHERE ate.tv_episode_id = :tv_episode_id
ORDER BY a.name ASC
");
$episodeStmt->execute(['tv_episode_id' => $episode['id']]);
$episode['actors'] = $episodeStmt->fetchAll(\PDO::FETCH_ASSOC);
} catch (\Exception $e) {
$episode['actors'] = [];
}
}
// Create a season object (simulating the old seasons table structure)
$seasons[] = [
'id' => null, // No seasons table, so no ID
'season_number' => $seasonNumber,
'episode_count' => (int)$stat['episode_count'],
'watched_episodes' => (int)$stat['watched_episodes'],
'episodes' => $episodes
];
}
// Create a season object (simulating the old seasons table structure)
$seasons[] = [
'id' => null, // No seasons table, so no ID
'season_number' => $seasonNumber,
'episode_count' => (int)$stat['episode_count'],
'watched_episodes' => (int)$stat['watched_episodes'],
'episodes' => $episodes
];
return $seasons;
} catch (\Exception $e) {
// Return empty array if tables don't exist or query fails
return [];
}
}
return $seasons;
/**
* Get all actors for this TV show (from all episodes)
*/
public function getActors(): array
{
try {
$stmt = $this->pdo->prepare("
SELECT DISTINCT a.*
FROM actors a
JOIN actor_tv_episode ate ON a.id = ate.actor_id
JOIN tv_episodes te ON ate.tv_episode_id = te.id
WHERE te.tv_show_id = :tv_show_id
ORDER BY a.name ASC
");
$stmt->execute(['tv_show_id' => $this->id]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
} catch (\Exception $e) {
// Return empty array if tables don't exist or query fails
return [];
}
}
/**
@@ -471,6 +502,147 @@ public static function getAvailableGenres(\PDO $pdo): array
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
/**
* Get all available sources from TV shows
*/
public static function getAvailableSources(\PDO $pdo): array
{
$stmt = $pdo->query("
SELECT DISTINCT s.display_name
FROM tv_shows t
JOIN sources s ON t.source_id = s.id
WHERE t.source_id IS NOT NULL
ORDER BY s.display_name
");
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
/**
* Get all TV shows with filtering and pagination
*/
public function findAll(array $filters = [], int $limit = null, int $offset = 0): array
{
$sql = "
SELECT t.*, s.display_name as source_name
FROM tv_shows t
LEFT JOIN sources s ON t.source_id = s.id
";
$params = [];
$whereClauses = [];
// Search filter
if (!empty($filters['search'])) {
$whereClauses[] = "(t.title LIKE :search OR t.overview LIKE :search)";
$params['search'] = "%{$filters['search']}%";
}
// Genre filter
if (!empty($filters['genre'])) {
$whereClauses[] = "t.genre = :genre";
$params['genre'] = $filters['genre'];
}
// Year filter
if (!empty($filters['year'])) {
$whereClauses[] = "YEAR(t.first_air_date) = :year";
$params['year'] = $filters['year'];
}
// Source filter
if (!empty($filters['sources'])) {
$sources = is_array($filters['sources']) ? $filters['sources'] : [$filters['sources']];
$placeholders = [];
foreach ($sources as $index => $source) {
$placeholders[] = ":source_{$index}";
$params["source_{$index}"] = $source;
}
$whereClauses[] = "s.display_name IN (" . implode(',', $placeholders) . ")";
}
// Combine WHERE clauses
if (!empty($whereClauses)) {
$sql .= " WHERE " . implode(' AND ', $whereClauses);
}
// Add ordering and pagination
$sql .= " ORDER BY t.first_air_date DESC, t.title ASC";
if ($limit) {
$sql .= " LIMIT :limit OFFSET :offset";
$params['limit'] = $limit;
$params['offset'] = $offset;
}
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
$results = $stmt->fetchAll(\PDO::FETCH_ASSOC);
// Process metadata to extract additional fields
foreach ($results as &$tvShow) {
if (!empty($tvShow['metadata'])) {
$metadata = json_decode($tvShow['metadata'], true);
// Extract additional fields from metadata if available
// This can be expanded based on your metadata structure
}
}
return $results;
}
/**
* Count TV shows with filters
*/
public function count(array $filters = []): int
{
$sql = "
SELECT COUNT(*) as total
FROM tv_shows t
LEFT JOIN sources s ON t.source_id = s.id
";
$params = [];
$whereClauses = [];
// Search filter
if (!empty($filters['search'])) {
$whereClauses[] = "(t.title LIKE :search OR t.overview LIKE :search)";
$params['search'] = "%{$filters['search']}%";
}
// Genre filter
if (!empty($filters['genre'])) {
$whereClauses[] = "t.genre = :genre";
$params['genre'] = $filters['genre'];
}
// Year filter
if (!empty($filters['year'])) {
$whereClauses[] = "YEAR(t.first_air_date) = :year";
$params['year'] = $filters['year'];
}
// Source filter
if (!empty($filters['sources'])) {
$sources = is_array($filters['sources']) ? $filters['sources'] : [$filters['sources']];
$placeholders = [];
foreach ($sources as $index => $source) {
$placeholders[] = ":source_{$index}";
$params["source_{$index}"] = $source;
}
$whereClauses[] = "s.display_name IN (" . implode(',', $placeholders) . ")";
}
// Combine WHERE clauses
if (!empty($whereClauses)) {
$sql .= " WHERE " . implode(' AND ', $whereClauses);
}
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
$result = $stmt->fetch(\PDO::FETCH_ASSOC);
return (int)$result['total'];
}
/**
* Get all available years from TV shows' first_air_date
*/