mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
1328 lines
48 KiB
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)
|
|
]
|
|
];
|
|
}
|
|
}
|