pdo = $pdo; $this->gameModel = new Game($pdo); $this->movieModel = new Movie($pdo); $this->tvShowModel = new TvShow($pdo); $this->adultModel = new AdultVideo($pdo); $this->musicArtistModel = new MusicArtist($pdo); $this->actorModel = new Actor($pdo); } // List all games with pagination public function listGames(Request $request, Response $response): Response { try { $pagination = $this->getPaginationParams($request); $filters = $this->getFiltersFromRequest($request); // Build the main query $sql = " SELECT g.id, g.title, g.image_url as poster_url, g.banner_url as backdrop_url, g.rating, g.release_date, g.platform, g.developer, g.genres_json as genres, g.playtime_minutes, g.completion_status, g.last_played_at as last_played, g.community_score, g.critic_score, s.display_name as source_name, g.created_at FROM games g LEFT JOIN sources s ON g.source_id = s.id WHERE 1=1 "; $params = []; // Add filters if (!empty($filters['genre'])) { $sql .= " AND JSON_CONTAINS(g.genres_json, :genre)"; $params[':genre'] = json_encode($filters['genre']); } if (!empty($filters['year'])) { $sql .= " AND YEAR(g.release_date) = :year"; $params[':year'] = $filters['year']; } if (!empty($filters['search'])) { $sql .= " AND (g.title LIKE :search OR g.developer LIKE :search)"; $params[':search'] = '%' . $filters['search'] . '%'; } if (!empty($filters['platform'])) { $sql .= " AND g.platform = :platform"; $params[':platform'] = $filters['platform']; } if (!empty($filters['developer'])) { $sql .= " AND g.developer = :developer"; $params[':developer'] = $filters['developer']; } if (!empty($filters['completion_status']) && $filters['completion_status'] !== 'all') { $sql .= " AND g.completion_status = :completion_status"; $params[':completion_status'] = $filters['completion_status']; } if (!empty($filters['source_name'])) { $sql .= " AND s.display_name = :source_name"; $params[':source_name'] = $filters['source_name']; } if (!empty($filters['rating'])) { // Parse rating range (e.g., "8-9") if (strpos($filters['rating'], '-') !== false) { list($minRating, $maxRating) = explode('-', $filters['rating']); $sql .= " AND g.rating BETWEEN :min_rating AND :max_rating"; $params[':min_rating'] = (float)$minRating; $params[':max_rating'] = (float)$maxRating; } } // Add sorting $sortBy = $filters['sort'] ?? 'title'; $sortOrder = $filters['order'] ?? 'asc'; switch ($sortBy) { case 'title': $sql .= " ORDER BY g.title " . $sortOrder; break; case 'release_date': $sql .= " ORDER BY g.release_date " . $sortOrder; break; case 'rating': $sql .= " ORDER BY g.rating " . $sortOrder; break; case 'playtime_hours': $sql .= " ORDER BY g.playtime_minutes " . $sortOrder; break; case 'completion_status': $sql .= " ORDER BY g.completion_status " . $sortOrder; break; case 'created_at': $sql .= " ORDER BY g.created_at " . $sortOrder; break; default: $sql .= " ORDER BY g.title ASC"; } // Add pagination $sql .= " LIMIT :limit OFFSET :offset"; $params[':limit'] = $pagination['per_page']; $params[':offset'] = $pagination['offset']; $stmt = $this->pdo->prepare($sql); $stmt->execute($params); $games = []; while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { // Parse genres if stored as JSON if (!empty($row['genres'])) { $genres = json_decode($row['genres'], true); $row['genres'] = is_array($genres) ? $genres : []; } else { $row['genres'] = []; } // Convert playtime minutes to hours for frontend if (!empty($row['playtime_minutes'])) { $row['playtime_hours'] = round($row['playtime_minutes'] / 60, 1); } else { $row['playtime_hours'] = null; } unset($row['playtime_minutes']); // Set default completion status if (empty($row['completion_status'])) { $row['completion_status'] = 'UNPLAYED'; } $games[] = $row; } // Get total count for pagination $countSql = " SELECT COUNT(*) as total FROM games g LEFT JOIN sources s ON g.source_id = s.id WHERE 1=1 "; $countParams = []; // Add same filters for count if (!empty($filters['genre'])) { $countSql .= " AND JSON_CONTAINS(g.genres_json, :genre)"; $countParams[':genre'] = json_encode($filters['genre']); } if (!empty($filters['year'])) { $countSql .= " AND YEAR(g.release_date) = :year"; $countParams[':year'] = $filters['year']; } if (!empty($filters['search'])) { $countSql .= " AND (g.title LIKE :search OR g.developer LIKE :search)"; $countParams[':search'] = '%' . $filters['search'] . '%'; } if (!empty($filters['platform'])) { $countSql .= " AND g.platform = :platform"; $countParams[':platform'] = $filters['platform']; } if (!empty($filters['developer'])) { $countSql .= " AND g.developer = :developer"; $countParams[':developer'] = $filters['developer']; } if (!empty($filters['completion_status']) && $filters['completion_status'] !== 'all') { $countSql .= " AND g.completion_status = :completion_status"; $countParams[':completion_status'] = $filters['completion_status']; } if (!empty($filters['source_name'])) { $countSql .= " AND s.display_name = :source_name"; $countParams[':source_name'] = $filters['source_name']; } if (!empty($filters['rating'])) { if (strpos($filters['rating'], '-') !== false) { list($minRating, $maxRating) = explode('-', $filters['rating']); $countSql .= " AND g.rating BETWEEN :min_rating AND :max_rating"; $countParams[':min_rating'] = (float)$minRating; $countParams[':max_rating'] = (float)$maxRating; } } $countStmt = $this->pdo->prepare($countSql); $countStmt->execute($countParams); $total = $countStmt->fetch(\PDO::FETCH_ASSOC)['total']; return $this->success($response, [ 'items' => $games, 'pagination' => [ 'total' => $total, 'per_page' => $pagination['per_page'], 'current_page' => $pagination['page'], 'last_page' => ceil($total / $pagination['per_page']) ] ]); } catch (\Exception $e) { return $this->error($response, 'Failed to fetch games: ' . $e->getMessage(), 500); } } // Get grouped games (merged across platforms) public function getGamesGroupedByPlatform(Request $request, Response $response): Response { try { $pagination = $this->getPaginationParams($request); $filters = $this->getFiltersFromRequest($request); // Simple grouping by title since game_key might not be populated $sql = " SELECT MIN(id) as id, title, COUNT(*) as platform_count, GROUP_CONCAT(DISTINCT platform ORDER BY platform) as platforms, MAX(image_url) as image_url, MAX(last_played_at) as last_played_at, SUM(playtime_minutes) as total_playtime, MAX(completion_percentage) as max_completion, MAX(release_date) as release_date, MAX(added_at) as added_at FROM games WHERE 1=1 "; $params = []; if (!empty($filters['search'])) { $sql .= " AND title LIKE :search"; $params[':search'] = "%{$filters['search']}%"; } if (!empty($filters['platform'])) { $sql .= " AND platform = :platform"; $params[':platform'] = $filters['platform']; } // Add sorting $sortBy = $filters['sort'] ?? 'title_asc'; $sortOptions = [ 'title_asc' => 'title ASC', 'title_desc' => 'title DESC', 'year_asc' => 'release_date ASC NULLS LAST', 'year_desc' => 'release_date DESC NULLS LAST', 'playtime_asc' => 'total_playtime ASC', 'playtime_desc' => 'total_playtime DESC', 'completion_asc' => 'max_completion ASC NULLS LAST', 'completion_desc' => 'max_completion DESC NULLS LAST', 'added_asc' => 'added_at ASC NULLS LAST', 'added_desc' => 'added_at DESC NULLS LAST', 'last_played_asc' => 'last_played_at ASC NULLS LAST', 'last_played_desc' => 'last_played_at DESC NULLS LAST', 'platforms_asc' => 'platform_count ASC', 'platforms_desc' => 'platform_count DESC' ]; $sortClause = $sortOptions[$sortBy] ?? 'title ASC'; $sql .= " GROUP BY title ORDER BY $sortClause"; // Add pagination $sql .= " LIMIT :limit OFFSET :offset"; $params[':limit'] = $pagination['per_page']; $params[':offset'] = $pagination['offset']; $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':limit', $pagination['per_page'], \PDO::PARAM_INT); $stmt->bindValue(':offset', $pagination['offset'], \PDO::PARAM_INT); if (!empty($filters['search'])) { $stmt->bindValue(':search', "%{$filters['search']}%"); } if (!empty($filters['platform'])) { $stmt->bindValue(':platform', $filters['platform']); } $stmt->execute(); $games = $stmt->fetchAll(\PDO::FETCH_ASSOC); // Enhance games with additional data foreach ($games as &$game) { // Parse platforms array $game['platforms'] = !empty($game['platforms']) ? array_unique(explode(',', $game['platforms'])) : []; // Convert playtime minutes to hours if (!empty($game['total_playtime'])) { $game['playtime_hours'] = round($game['total_playtime'] / 60, 1); } else { $game['playtime_hours'] = null; } // Set default completion status if (empty($game['max_completion'])) { $game['max_completion'] = 0; } // Add platform count as a badge $game['platform_count'] = (int)$game['platform_count']; // Get platform details for each platform $platformDetails = []; foreach ($game['platforms'] as $platform) { $platformDetails[] = [ 'platform' => $platform, 'display_name' => ucfirst($platform) ]; } $game['platform_details'] = $platformDetails; } // Get total count $countSql = " SELECT COUNT(DISTINCT title) as total FROM games WHERE 1=1 "; $countParams = []; if (!empty($filters['search'])) { $countSql .= " AND title LIKE :search"; $countParams[':search'] = "%{$filters['search']}%"; } if (!empty($filters['platform'])) { $countSql .= " AND platform = :platform"; $countParams[':platform'] = $filters['platform']; } $countStmt = $this->pdo->prepare($countSql); foreach ($countParams as $key => $value) { $countStmt->bindValue($key, $value); } $countStmt->execute(); $total = $countStmt->fetch(\PDO::FETCH_ASSOC)['total']; return $this->success($response, [ 'items' => $games, 'pagination' => [ 'total' => $total, 'per_page' => $pagination['per_page'], 'current_page' => $pagination['page'], 'last_page' => ceil($total / $pagination['per_page']) ] ]); } catch (\Exception $e) { return $this->error($response, 'Failed to fetch grouped games: ' . $e->getMessage(), 500); } } // Search across all media public function search(Request $request, Response $response): Response { try { $query = $request->getQueryParams()['q'] ?? ''; $type = $request->getQueryParams()['type'] ?? 'all'; $pagination = $this->getPaginationParams($request); $results = []; if ($type === 'all' || $type === 'game') { $results['games'] = $this->searchGames($query, $pagination); } if ($type === 'all' || $type === 'movie') { $results['movies'] = $this->searchMovies($query, $pagination); } if ($type === 'all' || $type === 'tvshow') { $results['tvshows'] = $this->searchTvShows($query, $pagination); } if ($type === 'all' || $type === 'music') { $results['artists'] = $this->searchArtists($query, $pagination); } if ($type === 'all' || $type === 'actors') { $results['actors'] = $this->searchActors($query, $pagination); } if ($type === 'all' || $type === 'adult') { $results['adult'] = $this->searchAdult($query, $pagination); } /* print_r($results); print_r($query); die("results"); */ return $this->success($response, $results); } catch (\Exception $e) { return $this->error($response, 'Search failed: ' . $e->getMessage(), 500); } } // Helper methods for searching different media types private function searchGames(string $query, array $pagination): array { try { // Use the game model's search method if available if (method_exists($this->gameModel, 'search')) { $games = $this->gameModel->search($query, $pagination['per_page'], $pagination['offset']); $total = method_exists($this->gameModel, 'countSearchResults') ? $this->gameModel->countSearchResults($query) : count($games); } // Fallback to basic filtering else { $allGames = $this->gameModel->findAll(); $filtered = array_filter($allGames, function($game) use ($query) { return stripos($game['title'] ?? '', $query) !== false; }); // Apply pagination $games = array_slice($filtered, $pagination['offset'], $pagination['per_page']); $total = count($filtered); } return [ 'items' => $games, 'total' => $total, 'page' => $pagination['page'], 'per_page' => $pagination['per_page'] ]; } catch (\Exception $e) { return [ 'items' => [], 'total' => 0, 'page' => $pagination['page'], 'per_page' => $pagination['per_page'], 'error' => $e->getMessage() ]; } } private function searchMovies(string $query, array $pagination): array { try { // Use the movie model's search method if available if (method_exists($this->movieModel, 'search')) { $movies = $this->movieModel->search($query, $pagination['per_page'], $pagination['offset']); $total = method_exists($this->movieModel, 'countSearchResults') ? $this->movieModel->countSearchResults($query) : count($movies); } // Fallback to basic filtering else { $allMovies = $this->movieModel->findAll(); $filtered = array_filter($allMovies, function($movie) use ($query) { return stripos($movie['title'] ?? '', $query) !== false; }); // Apply pagination $movies = array_slice($filtered, $pagination['offset'], $pagination['per_page']); $total = count($filtered); } return [ 'items' => $movies, 'total' => $total, 'page' => $pagination['page'], 'per_page' => $pagination['per_page'] ]; } catch (\Exception $e) { return [ 'items' => [], 'total' => 0, 'page' => $pagination['page'], 'per_page' => $pagination['per_page'], 'error' => $e->getMessage() ]; } } private function searchTvShows(string $query, array $pagination): array { try { // Use the TV show model's search method if available if (method_exists($this->tvShowModel, 'search')) { $tvShows = $this->tvShowModel->search($query, $pagination['per_page'], $pagination['offset']); $total = method_exists($this->tvShowModel, 'countSearchResults') ? $this->tvShowModel->countSearchResults($query) : count($tvShows); } // Fallback to basic filtering else { $allTvShows = $this->tvShowModel->findAll(); $filtered = array_filter($allTvShows, function($tvShow) use ($query) { return stripos($tvShow['title'] ?? '', $query) !== false; }); // Apply pagination $tvShows = array_slice($filtered, $pagination['offset'], $pagination['per_page']); $total = count($filtered); } return [ 'items' => $tvShows, 'total' => $total, 'page' => $pagination['page'], 'per_page' => $pagination['per_page'] ]; } catch (\Exception $e) { return [ 'items' => [], 'total' => 0, 'page' => $pagination['page'], 'per_page' => $pagination['per_page'], 'error' => $e->getMessage() ]; } } private function searchArtists(string $query, array $pagination): array { try { // Use the music artist model's search method if available if (method_exists($this->musicArtistModel, 'search')) { $artists = $this->musicArtistModel->search($query, $pagination['per_page'], $pagination['offset']); $total = method_exists($this->musicArtistModel, 'countSearchResults') ? $this->musicArtistModel->countSearchResults($query) : count($artists); } // Fallback to basic filtering else { $allArtists = $this->musicArtistModel->findAll(); $filtered = array_filter($allArtists, function($artist) use ($query) { return stripos($artist['name'] ?? '', $query) !== false; }); // Apply pagination $artists = array_slice($filtered, $pagination['offset'], $pagination['per_page']); $total = count($filtered); } return [ 'items' => $artists, 'total' => $total, 'page' => $pagination['page'], 'per_page' => $pagination['per_page'] ]; } catch (\Exception $e) { return [ 'items' => [], 'total' => 0, 'page' => $pagination['page'], 'per_page' => $pagination['per_page'], 'error' => $e->getMessage() ]; } } private function searchActors(string $query, array $pagination): array { try { // Use the actor model's search method if available if (method_exists($this->actorModel, 'search')) { $actors = $this->actorModel->search($query, $pagination['per_page'], $pagination['offset']); $total = method_exists($this->actorModel, 'countSearchResults') ? $this->actorModel->countSearchResults($query) : count($actors); } // Fallback to basic filtering else { $allActors = $this->actorModel->findAll(); $filtered = array_filter($allActors, function($actor) use ($query) { return stripos($actor['name'] ?? '', $query) !== false; }); // Apply pagination $actors = array_slice($filtered, $pagination['offset'], $pagination['per_page']); $total = count($filtered); } return [ 'items' => $actors, 'total' => $total, 'page' => $pagination['page'], 'per_page' => $pagination['per_page'] ]; } catch (\Exception $e) { return [ 'items' => [], 'total' => 0, 'page' => $pagination['page'], 'per_page' => $pagination['per_page'], 'error' => $e->getMessage() ]; } } private function searchAdult(string $query, array $pagination): array { try { // Use the actor model's search method if available if (method_exists($this->actorModel, 'search')) { $actors = $this->adultModel->search($query, $pagination['per_page'], $pagination['offset']); $total = method_exists($this->actorModel, 'countSearchResults') ? $this->adultModel->countSearchResults($query) : count($actors); } // Fallback to basic filtering else { $allActors = $this->adultModel->findAll(); $filtered = array_filter($allActors, function($actor) use ($query) { return stripos($actor['title'] ?? '', $query) !== false; }); // Apply pagination $actors = array_slice($filtered, $pagination['offset'], $pagination['per_page']); $total = count($filtered); } return [ 'items' => $actors, 'total' => $total, 'page' => $pagination['page'], 'per_page' => $pagination['per_page'] ]; } catch (\Exception $e) { return [ 'items' => [], 'total' => 0, 'page' => $pagination['page'], 'per_page' => $pagination['per_page'], 'error' => $e->getMessage() ]; } } // List all movies with pagination public function listMovies(Request $request, Response $response): Response { try { $pagination = $this->getPaginationParams($request); $filters = $this->getFiltersFromRequest($request); // Get movies using the Movie model's pagination method $movies = Movie::getAllWithPagination( $this->pdo, $pagination['page'], $pagination['per_page'], $filters['search'] ?? '', $filters['genres'] ?? [], $filters['directors'] ?? [], 'title_asc' // Default sort ); $total = Movie::getTotalCount($this->pdo, $filters['search'] ?? '', $filters['genres'] ?? [], $filters['directors'] ?? []); return $this->success($response, [ 'items' => $movies, 'pagination' => [ 'total' => $total, 'per_page' => $pagination['per_page'], 'current_page' => $pagination['page'], 'last_page' => ceil($total / $pagination['per_page']) ] ]); } catch (\Exception $e) { return $this->error($response, 'Failed to fetch movies', 500); } } // Get single movie by ID public function getMovie(Request $request, Response $response, array $args): Response { try { $id = (int)($args['id'] ?? 0); if (!$id) { return $this->error($response, 'Invalid movie ID', 400); } $movie = $this->movieModel->find($id); if (!$movie) { return $this->error($response, 'Movie not found', 404); } // Get actors for this movie using the model instance $movieModel = new \App\Models\Movie($this->pdo); $movieModel->id = $id; try { $actors = $movieModel->actors(); if (is_array($movie)) { $movie['actors'] = $actors; } } catch (\Exception $e) { if (is_array($movie)) { $movie['actors'] = []; } } return $this->success($response, $movie); } catch (\Exception $e) { return $this->error($response, 'Failed to fetch movie', 500); } } // List all TV shows with pagination public function listTvShows(Request $request, Response $response): Response { try { $pagination = $this->getPaginationParams($request); $filters = $this->getFiltersFromRequest($request); $tvShows = $this->tvShowModel->findAll( $filters, $pagination['per_page'], $pagination['offset'] ); $total = $this->tvShowModel->count($filters); // Get available filter options $availableGenres = \App\Models\TvShow::getAvailableGenres($this->pdo); $availableSources = \App\Models\TvShow::getAvailableSources($this->pdo); return $this->success($response, [ 'items' => $tvShows, 'pagination' => [ 'total' => $total, 'per_page' => $pagination['per_page'], 'current_page' => $pagination['page'], 'last_page' => ceil($total / $pagination['per_page']) ], 'available_filters' => [ 'genres' => $availableGenres, 'sources' => $availableSources ] ]); } catch (\Exception $e) { return $this->error($response, 'Failed to fetch TV shows', 500); } } // Get single TV show by ID public function getTvShow(Request $request, Response $response, array $args): Response { try { $id = (int)($args['id'] ?? 0); if (!$id) { return $this->error($response, 'Invalid TV show ID', 400); } $tvShow = $this->tvShowModel->find($id); if (!$tvShow) { return $this->error($response, 'TV show not found', 404); } // Get additional data using the model instance $tvShowModel = new \App\Models\TvShow($this->pdo); $tvShowModel->id = $id; try { $tvShow['actors'] = $tvShowModel->getActors(); } catch (\Exception $e) { $tvShow['actors'] = []; } try { $tvShow['seasons'] = $tvShowModel->getSeasonsWithEpisodes(); } catch (\Exception $e) { $tvShow['seasons'] = []; } return $this->success($response, $tvShow); } catch (\Exception $e) { return $this->error($response, 'Failed to fetch TV show: ' . $e->getMessage(), 500); } } // List all adult content with pagination public function listAdult(Request $request, Response $response): Response { try { $pagination = $this->getPaginationParams($request); $filters = $this->getFiltersFromRequest($request); $adultContent = $this->adultModel->findAll( $filters, $pagination['per_page'], $pagination['offset'] ); // Add actors to each adult video foreach ($adultContent as &$video) { try { $adultVideoModel = new \App\Models\AdultVideo($this->pdo); $video['actors'] = $adultVideoModel->actors($video['id']); } catch (\Exception $e) { $video['actors'] = []; } } $total = $this->adultModel->count($filters); // Get available filter options $availableGenres = \App\Models\AdultVideo::getAvailableGenres($this->pdo); $availableSources = \App\Models\AdultVideo::getAvailableSources($this->pdo); return $this->success($response, [ 'items' => $adultContent, 'pagination' => [ 'total' => $total, 'per_page' => $pagination['per_page'], 'current_page' => $pagination['page'], 'last_page' => ceil($total / $pagination['per_page']) ], 'available_filters' => [ 'genres' => $availableGenres, 'sources' => $availableSources ] ]); } catch (\Exception $e) { return $this->error($response, 'Failed to fetch adult content', 500); } } // Get single adult content by ID public function getAdult(Request $request, Response $response, array $args): Response { try { $id = (int)($args['id'] ?? 0); if (!$id) { return $this->error($response, 'Invalid adult content ID', 400); } $adultContent = $this->adultModel->find($id); if (!$adultContent) { return $this->error($response, 'Adult content not found', 404); } // Get actors for this adult video using the model instance $adultVideoModel = new \App\Models\AdultVideo($this->pdo); $adultVideoModel->id = $id; try { $adultContent['actors'] = $adultVideoModel->actors($id); } catch (\Exception $e) { $adultContent['actors'] = []; } return $this->success($response, $adultContent); } catch (\Exception $e) { return $this->error($response, 'Failed to fetch adult content', 500); } } // List all actors with pagination public function listActors(Request $request, Response $response): Response { try { $pagination = $this->getPaginationParams($request); $filters = $this->getFiltersFromRequest($request); $actors = $this->actorModel->findAll( $filters, $pagination['per_page'], $pagination['offset'] ); $total = $this->actorModel->count($filters); return $this->success($response, [ 'items' => $actors, 'pagination' => [ 'total' => $total, 'per_page' => $pagination['per_page'], 'current_page' => $pagination['page'], 'last_page' => ceil($total / $pagination['per_page']) ] ]); } catch (\Exception $e) { return $this->error($response, 'Failed to fetch actors', 500); } } // Get single actor by ID public function getActor(Request $request, Response $response, array $args): Response { try { $id = (int)($args['id'] ?? 0); if (!$id) { return $this->error($response, 'Invalid actor ID', 400); } $actor = $this->actorModel->find($id); if (!$actor) { return $this->error($response, 'Actor not found', 404); } // Get additional data using model instance $actorModel = new \App\Models\Actor($this->pdo); $actorModel->id = $id; $actorModel->actor = $actor; try { $actor['movies'] = $actorModel->movies(); } catch (\Exception $e) { $actor['movies'] = []; } try { $actor['tvshows'] = $actorModel->tvShows(); } catch (\Exception $e) { $actor['tvshows'] = []; } try { $actor['adult_videos'] = $actorModel->adultVideos(); } catch (\Exception $e) { $actor['adult_videos'] = []; } return $this->success($response, $actor); } catch (\Exception $e) { return $this->error($response, 'Failed to fetch actor', 500); } } // Helper method to get filters from request private function getFiltersFromRequest(Request $request): array { $filters = []; $queryParams = $request->getQueryParams(); // Add common filters if (!empty($queryParams['genre'])) { $filters['genre'] = $queryParams['genre']; } if (!empty($queryParams['year'])) { $filters['year'] = (int)$queryParams['year']; } if (!empty($queryParams['search'])) { $filters['search'] = $queryParams['search']; } // Add games-specific filters if (!empty($queryParams['platform'])) { $filters['platform'] = $queryParams['platform']; } if (!empty($queryParams['developer'])) { $filters['developer'] = $queryParams['developer']; } if (!empty($queryParams['completion_status']) && $queryParams['completion_status'] !== 'all') { $filters['completion_status'] = $queryParams['completion_status']; } if (!empty($queryParams['source_name'])) { $filters['source_name'] = $queryParams['source_name']; } if (!empty($queryParams['rating'])) { $filters['rating'] = $queryParams['rating']; } // Add actor-specific filters if (!empty($queryParams['gender'])) { $filters['gender'] = $queryParams['gender']; } if (isset($queryParams['adult'])) { $filters['adult'] = $queryParams['adult'] === 'true' || $queryParams['adult'] === true; } if (!empty($queryParams['sort'])) { $filters['sort'] = $queryParams['sort']; } if (!empty($queryParams['order'])) { $filters['order'] = $queryParams['order']; } return $filters; } // Get all games grouped by completion status (for new frontend) public function getGamesGrouped(Request $request, Response $response): Response { try { $queryParams = $request->getQueryParams(); $search = $queryParams['search'] ?? ''; $sort = $queryParams['sort'] ?? 'title_asc'; $pagination = $this->getPaginationParams($request); // Get all games with pagination $games = $this->getAllGamesWithCategories($search, $sort, $pagination); // Group games by completion status $groupedGames = $this->groupGamesByCategory($games['items']); return $this->success($response, [ 'data' => $groupedGames, 'pagination' => $games['pagination'] ]); } catch (\Exception $e) { return $this->error($response, 'Failed to fetch games: ' . $e->getMessage(), 500); } } // Get games by category public function getGamesByCategory(Request $request, Response $response, array $args): Response { try { $category = strtoupper($args['category'] ?? ''); $queryParams = $request->getQueryParams(); $search = $queryParams['search'] ?? ''; $sort = $queryParams['sort'] ?? 'title_asc'; $pagination = $this->getPaginationParams($request); if (!in_array($category, ['BEATEN', 'PLAYING', 'COMPLETED', 'UNPLAYED'])) { return $this->error($response, 'Invalid category. Must be one of: BEATEN, PLAYING, COMPLETED, UNPLAYED', 400); } $games = $this->getGamesByCategoryFiltered($category, $search, $sort, $pagination); return $this->success($response, [ 'data' => [ 'category' => $category, 'games' => $games['items'], 'count' => count($games['items']) ], 'pagination' => $games['pagination'] ]); } catch (\Exception $e) { return $this->error($response, 'Failed to fetch games by category: ' . $e->getMessage(), 500); } } // Helper method to get all games with categories private function getAllGamesWithCategories(string $search = '', string $sort = 'title_asc', array $pagination = []): array { $limit = $pagination['per_page'] ?? 1000; // Default high limit for grouped view $offset = $pagination['offset'] ?? 0; // First get total count $countSql = " SELECT COUNT(*) as total FROM games g WHERE 1=1 "; $countParams = []; if (!empty($search)) { $countSql .= " AND (g.title LIKE :search OR g.developer LIKE :search)"; $countParams[':search'] = '%' . $search . '%'; } $countStmt = $this->pdo->prepare($countSql); $countStmt->execute($countParams); $total = $countStmt->fetch(\PDO::FETCH_ASSOC)['total']; $sql = " SELECT g.id, g.title, g.image_url as poster_url, g.banner_url as backdrop_url, g.rating, g.release_date, g.platform, g.developer, g.genres, g.playtime_hours, g.completion_status, g.last_played, g.community_score, g.critic_score, g.source_name FROM games g WHERE 1=1 "; $params = []; // Add search filter if (!empty($search)) { $sql .= " AND (g.title LIKE :search OR g.developer LIKE :search)"; $params[':search'] = '%' . $search . '%'; } // Add sorting switch ($sort) { case 'title_desc': $sql .= " ORDER BY g.title DESC"; break; case 'year_asc': $sql .= " ORDER BY g.release_date ASC"; break; case 'year_desc': $sql .= " ORDER BY g.release_date DESC"; break; case 'playtime_desc': $sql .= " ORDER BY g.playtime_hours DESC"; break; case 'rating_desc': $sql .= " ORDER BY g.rating DESC"; break; case 'last_played_desc': $sql .= " ORDER BY g.last_played DESC"; break; default: $sql .= " ORDER BY g.title ASC"; } // Add pagination $sql .= " LIMIT :limit OFFSET :offset"; $params[':limit'] = $limit; $params[':offset'] = $offset; $stmt = $this->pdo->prepare($sql); $stmt->execute($params); $games = []; while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { // Parse genres if stored as JSON if (!empty($row['genres'])) { $genres = json_decode($row['genres'], true); $row['genres'] = is_array($genres) ? $genres : []; } else { $row['genres'] = []; } // Set default completion status if (empty($row['completion_status'])) { $row['completion_status'] = 'UNPLAYED'; } $games[] = $row; } return [ 'items' => $games, 'pagination' => [ 'total' => $total, 'per_page' => $limit, 'current_page' => $pagination['page'] ?? 1, 'last_page' => ceil($total / $limit) ] ]; } // Helper method to group games by category private function groupGamesByCategory(array $games): array { $categories = [ 'BEATEN' => ['name' => 'BEATEN', 'count' => 0, 'games' => []], 'PLAYING' => ['name' => 'PLAYING', 'count' => 0, 'games' => []], 'COMPLETED' => ['name' => 'COMPLETED', 'count' => 0, 'games' => []], 'UNPLAYED' => ['name' => 'UNPLAYED', 'count' => 0, 'games' => []] ]; foreach ($games as $game) { $status = $game['completion_status'] ?? 'UNPLAYED'; if (isset($categories[$status])) { $categories[$status]['games'][] = $game; $categories[$status]['count']++; } } return array_values($categories); } // Helper method to get games by specific category private function getGamesByCategoryFiltered(string $category, string $search = '', string $sort = 'title_asc', array $pagination = []): array { $limit = $pagination['per_page'] ?? 24; $offset = $pagination['offset'] ?? 0; // First get total count $countSql = " SELECT COUNT(*) as total FROM games g WHERE g.completion_status = :category "; $countParams = [':category' => $category]; if (!empty($search)) { $countSql .= " AND (g.title LIKE :search OR g.developer LIKE :search)"; $countParams[':search'] = '%' . $search . '%'; } $countStmt = $this->pdo->prepare($countSql); $countStmt->execute($countParams); $total = $countStmt->fetch(\PDO::FETCH_ASSOC)['total']; $sql = " SELECT g.id, g.title, g.image_url as poster_url, g.banner_url as backdrop_url, g.rating, g.release_date, g.platform, g.developer, g.genres, g.playtime_hours, g.completion_status, g.last_played, g.community_score, g.critic_score, g.source_name FROM games g WHERE g.completion_status = :category "; $params = [':category' => $category]; // Add search filter if (!empty($search)) { $sql .= " AND (g.title LIKE :search OR g.developer LIKE :search)"; $params[':search'] = '%' . $search . '%'; } // Add sorting switch ($sort) { case 'title_desc': $sql .= " ORDER BY g.title DESC"; break; case 'year_asc': $sql .= " ORDER BY g.release_date ASC"; break; case 'year_desc': $sql .= " ORDER BY g.release_date DESC"; break; case 'playtime_desc': $sql .= " ORDER BY g.playtime_hours DESC"; break; case 'rating_desc': $sql .= " ORDER BY g.rating DESC"; break; case 'last_played_desc': $sql .= " ORDER BY g.last_played DESC"; break; default: $sql .= " ORDER BY g.title ASC"; } // Add pagination $sql .= " LIMIT :limit OFFSET :offset"; $params[':limit'] = $limit; $params[':offset'] = $offset; $stmt = $this->pdo->prepare($sql); $stmt->execute($params); $games = []; while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { // Parse genres if stored as JSON if (!empty($row['genres'])) { $genres = json_decode($row['genres'], true); $row['genres'] = is_array($genres) ? $genres : []; } else { $row['genres'] = []; } $games[] = $row; } return [ 'items' => $games, 'pagination' => [ 'total' => $total, 'per_page' => $limit, 'current_page' => $pagination['page'] ?? 1, 'last_page' => ceil($total / $limit) ] ]; } }