Files
MediaCollectorLibary/app/Controllers/Api/MediaController.php

1328 lines
48 KiB
PHP

<?php
namespace App\Controllers\Api;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use App\Models\Game;
use App\Models\Movie;
use App\Models\TvShow;
use App\Models\MusicArtist;
use App\Models\AdultVideo;
use App\Models\Actor;
use App\Controllers\Api\ApiController;
class MediaController extends ApiController
{
private $gameModel;
private $movieModel;
private $tvShowModel;
private $musicArtistModel;
private $adultModel;
private $actorModel;
private $pdo;
public function __construct(\PDO $pdo)
{
$this->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)
]
];
}
}