mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
Remove obsolete test scripts and add new API controllers for dashboard and game management
- Deleted test scripts: test_jellyfin_execution.php, test_stash.php, test_xbvr.php, test_xbvr_sync.php, vite.config.js - Added DashboardController for fetching dashboard statistics and recent activity - Added GameController for managing games, including fetching all games, game details, and games by category - Introduced various check scripts to validate database structures and data integrity for adult videos, games, gender data, posters, and TV show actors
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -149,3 +149,6 @@ composer.lock
|
||||
/public/public/images/backdrops
|
||||
/public/public/images/posters
|
||||
/storage/images
|
||||
|
||||
|
||||
/frontend/
|
||||
@@ -8,6 +8,35 @@ use App\Controllers\Controller;
|
||||
|
||||
class BaseApiController extends Controller
|
||||
{
|
||||
protected function getPdo(): \PDO
|
||||
{
|
||||
// Get PDO from the container - this assumes PDO is registered in the DI container
|
||||
global $container;
|
||||
if ($container && $container->has('pdo')) {
|
||||
return $container->get('pdo');
|
||||
}
|
||||
|
||||
// Fallback to creating a new PDO connection
|
||||
$host = $_ENV['DB_HOST'] ?? 'localhost';
|
||||
$dbname = $_ENV['DB_NAME'] ?? 'medialib';
|
||||
$username = $_ENV['DB_USER'] ?? 'root';
|
||||
$password = $_ENV['DB_PASS'] ?? '';
|
||||
|
||||
try {
|
||||
return new \PDO(
|
||||
"mysql:host=$host;dbname=$dbname;charset=utf8mb4",
|
||||
$username,
|
||||
$password,
|
||||
[
|
||||
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
||||
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
|
||||
\PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]
|
||||
);
|
||||
} catch (\PDOException $e) {
|
||||
throw new \Exception('Database connection failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
protected function success(Response $response, $data = null, int $status = 200): Response
|
||||
{
|
||||
$responseData = ['success' => true];
|
||||
|
||||
206
app/Controllers/Api/DashboardController.php
Normal file
206
app/Controllers/Api/DashboardController.php
Normal file
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use App\Controllers\Api\ApiController;
|
||||
|
||||
class DashboardController extends ApiController
|
||||
{
|
||||
private \PDO $pdo;
|
||||
|
||||
public function __construct(\PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dashboard statistics
|
||||
*/
|
||||
public function getStats(Request $request, Response $response): Response
|
||||
{
|
||||
try {
|
||||
$stats = [];
|
||||
|
||||
// Get movies count
|
||||
$stmt = $this->pdo->prepare("SELECT COUNT(*) as count FROM movies");
|
||||
$stmt->execute();
|
||||
$moviesCount = $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
|
||||
$stats[] = [
|
||||
'name' => 'Total Movies',
|
||||
'value' => number_format($moviesCount),
|
||||
'icon' => 'FilmIcon',
|
||||
'color' => 'bg-blue-500',
|
||||
'href' => '/movies'
|
||||
];
|
||||
|
||||
// Get TV shows count
|
||||
$stmt = $this->pdo->prepare("SELECT COUNT(*) as count FROM tv_shows");
|
||||
$stmt->execute();
|
||||
$tvShowsCount = $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
|
||||
$stats[] = [
|
||||
'name' => 'TV Shows',
|
||||
'value' => number_format($tvShowsCount),
|
||||
'icon' => 'TvIcon',
|
||||
'color' => 'bg-purple-500',
|
||||
'href' => '/tvshows'
|
||||
];
|
||||
|
||||
// Get games count
|
||||
$stmt = $this->pdo->prepare("SELECT COUNT(*) as count FROM games");
|
||||
$stmt->execute();
|
||||
$gamesCount = $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
|
||||
$stats[] = [
|
||||
'name' => 'Games',
|
||||
'value' => number_format($gamesCount),
|
||||
'icon' => 'GamepadIcon',
|
||||
'color' => 'bg-green-500',
|
||||
'href' => '/games'
|
||||
];
|
||||
|
||||
// Get music albums count
|
||||
$stmt = $this->pdo->prepare("SELECT COUNT(*) as count FROM music_albums");
|
||||
$stmt->execute();
|
||||
$albumsCount = $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
|
||||
$stats[] = [
|
||||
'name' => 'Music Albums',
|
||||
'value' => number_format($albumsCount),
|
||||
'icon' => 'MusicalNoteIcon',
|
||||
'color' => 'bg-pink-500',
|
||||
'href' => '/music'
|
||||
];
|
||||
|
||||
// Get adult videos count
|
||||
$stmt = $this->pdo->prepare("SELECT COUNT(*) as count FROM adult_videos");
|
||||
$stmt->execute();
|
||||
$adultCount = $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
|
||||
$stats[] = [
|
||||
'name' => 'Adult Videos',
|
||||
'value' => number_format($adultCount),
|
||||
'icon' => 'LockClosedIcon',
|
||||
'color' => 'bg-red-500',
|
||||
'href' => '/adult'
|
||||
];
|
||||
|
||||
// Get actors count
|
||||
$stmt = $this->pdo->prepare("SELECT COUNT(*) as count FROM actors");
|
||||
$stmt->execute();
|
||||
$actorsCount = $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
|
||||
$stats[] = [
|
||||
'name' => 'Actors',
|
||||
'value' => number_format($actorsCount),
|
||||
'icon' => 'UserIcon',
|
||||
'color' => 'bg-indigo-500',
|
||||
'href' => '/actors'
|
||||
];
|
||||
|
||||
return $this->success($response, $stats);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($response, 'Failed to fetch dashboard stats', 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent activity
|
||||
*/
|
||||
public function getRecentActivity(Request $request, Response $response): Response
|
||||
{
|
||||
try {
|
||||
$activities = [];
|
||||
|
||||
// Get recent movies (last 5)
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT 'Added movie' as action, title as item,
|
||||
DATE_FORMAT(created_at, '%b %d, %Y') as time,
|
||||
'movie' as type
|
||||
FROM movies
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5
|
||||
");
|
||||
$stmt->execute();
|
||||
$recentMovies = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$activities = array_merge($activities, $recentMovies);
|
||||
|
||||
// Get recent TV shows (last 5)
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT 'Added TV show' as action, title as item,
|
||||
DATE_FORMAT(created_at, '%b %d, %Y') as time,
|
||||
'tvshow' as type
|
||||
FROM tv_shows
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5
|
||||
");
|
||||
$stmt->execute();
|
||||
$recentTvShows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$activities = array_merge($activities, $recentTvShows);
|
||||
|
||||
// Get recent games (last 5)
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT 'Added game' as action, title as item,
|
||||
DATE_FORMAT(created_at, '%b %d, %Y') as time,
|
||||
'game' as type
|
||||
FROM games
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5
|
||||
");
|
||||
$stmt->execute();
|
||||
$recentGames = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$activities = array_merge($activities, $recentGames);
|
||||
|
||||
// Get recent music albums (last 5)
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT 'Added album' as action, title as item,
|
||||
DATE_FORMAT(created_at, '%b %d, %Y') as time,
|
||||
'music' as type
|
||||
FROM music_albums
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5
|
||||
");
|
||||
$stmt->execute();
|
||||
$recentAlbums = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$activities = array_merge($activities, $recentAlbums);
|
||||
|
||||
// Sort all activities by time (most recent first)
|
||||
usort($activities, function($a, $b) {
|
||||
return strtotime($b['time']) - strtotime($a['time']);
|
||||
});
|
||||
|
||||
// Take only the 10 most recent activities
|
||||
$activities = array_slice($activities, 0, 10);
|
||||
|
||||
// Format time to be more relative
|
||||
foreach ($activities as &$activity) {
|
||||
$activity['id'] = uniqid();
|
||||
$activity['time'] = $this->formatRelativeTime($activity['time']);
|
||||
}
|
||||
|
||||
return $this->success($response, $activities);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($response, 'Failed to fetch recent activity', 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format time to relative format (simplified version)
|
||||
*/
|
||||
private function formatRelativeTime($dateString): string
|
||||
{
|
||||
$date = strtotime($dateString);
|
||||
$now = time();
|
||||
$diff = $now - $date;
|
||||
|
||||
if ($diff < 3600) {
|
||||
$minutes = floor($diff / 60);
|
||||
return $minutes <= 1 ? 'Just now' : "$minutes minutes ago";
|
||||
} elseif ($diff < 86400) {
|
||||
$hours = floor($diff / 3600);
|
||||
return $hours <= 1 ? '1 hour ago' : "$hours hours ago";
|
||||
} elseif ($diff < 604800) {
|
||||
$days = floor($diff / 86400);
|
||||
return $days <= 1 ? '1 day ago' : "$days days ago";
|
||||
} else {
|
||||
return date('M j, Y', $date);
|
||||
}
|
||||
}
|
||||
}
|
||||
364
app/Controllers/Api/GameController.php
Normal file
364
app/Controllers/Api/GameController.php
Normal file
@@ -0,0 +1,364 @@
|
||||
<?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\Controllers\Api\BaseApiController;
|
||||
|
||||
class GameController extends BaseApiController
|
||||
{
|
||||
/**
|
||||
* Get all games grouped by completion status
|
||||
*/
|
||||
public function index(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$queryParams = $request->getQueryParams();
|
||||
$search = $queryParams['search'] ?? '';
|
||||
$sort = $queryParams['sort'] ?? 'title_asc';
|
||||
|
||||
try {
|
||||
// Get games from database
|
||||
$games = $this->getAllGamesWithCategories($search, $sort);
|
||||
|
||||
// Group games by completion status
|
||||
$groupedGames = $this->groupGamesByCategory($games);
|
||||
|
||||
return $this->json($response, [
|
||||
'success' => true,
|
||||
'data' => $groupedGames
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Failed to fetch games: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get game by ID
|
||||
*/
|
||||
public function show(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$gameId = $args['id'] ?? null;
|
||||
|
||||
if (!$gameId) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Game ID is required'
|
||||
], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$game = $this->getGameDetails($gameId);
|
||||
|
||||
if (!$game) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Game not found'
|
||||
], 404);
|
||||
}
|
||||
|
||||
return $this->json($response, [
|
||||
'success' => true,
|
||||
'data' => $game
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Failed to fetch game: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get games by category (BEATEN, PLAYING, etc.)
|
||||
*/
|
||||
public function getByCategory(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$category = strtoupper($args['category'] ?? '');
|
||||
$queryParams = $request->getQueryParams();
|
||||
$search = $queryParams['search'] ?? '';
|
||||
$sort = $queryParams['sort'] ?? 'title_asc';
|
||||
|
||||
if (!in_array($category, ['BEATEN', 'PLAYING', 'COMPLETED', 'UNPLAYED'])) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Invalid category. Must be one of: BEATEN, PLAYING, COMPLETED, UNPLAYED'
|
||||
], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$games = $this->getGamesByCategory($category, $search, $sort);
|
||||
|
||||
return $this->json($response, [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'category' => $category,
|
||||
'games' => $games,
|
||||
'count' => count($games)
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Failed to fetch games by category: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all games from database
|
||||
*/
|
||||
private function getAllGamesWithCategories(string $search = '', string $sort = 'title_asc'): array
|
||||
{
|
||||
$pdo = $this->getPdo();
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
g.id,
|
||||
g.title,
|
||||
g.poster_url,
|
||||
g.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";
|
||||
}
|
||||
|
||||
$stmt = $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 $games;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group games by completion status
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get games by specific category
|
||||
*/
|
||||
private function getGamesByCategory(string $category, string $search = '', string $sort = 'title_asc'): array
|
||||
{
|
||||
$pdo = $this->getPdo();
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
g.id,
|
||||
g.title,
|
||||
g.poster_url,
|
||||
g.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";
|
||||
}
|
||||
|
||||
$stmt = $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 $games;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed game information
|
||||
*/
|
||||
private function getGameDetails(int $gameId): ?array
|
||||
{
|
||||
$pdo = $this->getPdo();
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
g.id,
|
||||
g.title,
|
||||
g.poster_url,
|
||||
g.backdrop_url,
|
||||
g.rating,
|
||||
g.release_date,
|
||||
g.platform,
|
||||
g.developer,
|
||||
g.publisher,
|
||||
g.genres,
|
||||
g.playtime_hours,
|
||||
g.completion_status,
|
||||
g.last_played,
|
||||
g.community_score,
|
||||
g.critic_score,
|
||||
g.source_name,
|
||||
g.description,
|
||||
g.gameplay,
|
||||
g.synopsis,
|
||||
g.age_ratings,
|
||||
g.version,
|
||||
g.time_to_beat,
|
||||
g.controls,
|
||||
g.pacing,
|
||||
g.perspective,
|
||||
g.series
|
||||
FROM games g
|
||||
WHERE g.id = :id
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([':id' => $gameId]);
|
||||
|
||||
$game = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$game) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse JSON fields
|
||||
$jsonFields = ['genres', 'age_ratings'];
|
||||
foreach ($jsonFields as $field) {
|
||||
if (!empty($game[$field])) {
|
||||
$decoded = json_decode($game[$field], true);
|
||||
$game[$field] = is_array($decoded) ? $decoded : [];
|
||||
} else {
|
||||
$game[$field] = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Set default completion status
|
||||
if (empty($game['completion_status'])) {
|
||||
$game['completion_status'] = 'UNPLAYED';
|
||||
}
|
||||
|
||||
return $game;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,139 @@ class Actor extends Model
|
||||
'metadata' => 'array'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get all actors with filtering and pagination
|
||||
*/
|
||||
public function findAll(array $filters = [], int $limit = null, int $offset = 0): array
|
||||
{
|
||||
$sql = "
|
||||
SELECT a.*,
|
||||
COUNT(DISTINCT am.movie_id) as movie_count,
|
||||
COUNT(DISTINCT te.tv_show_id) as tv_show_count,
|
||||
COUNT(DISTINCT aav.adult_video_id) as adult_video_count
|
||||
FROM actors a
|
||||
LEFT JOIN actor_movie am ON a.id = am.actor_id
|
||||
LEFT JOIN actor_tv_episode ate ON a.id = ate.actor_id
|
||||
LEFT JOIN tv_episodes te ON ate.tv_episode_id = te.id
|
||||
LEFT JOIN actor_adult_video aav ON a.id = aav.actor_id
|
||||
";
|
||||
$params = [];
|
||||
$whereClauses = [];
|
||||
|
||||
// Search filter
|
||||
if (!empty($filters['search'])) {
|
||||
$whereClauses[] = "a.name LIKE :search";
|
||||
$params['search'] = "%{$filters['search']}%";
|
||||
}
|
||||
|
||||
// Gender filter
|
||||
if (!empty($filters['gender'])) {
|
||||
$whereClauses[] = "JSON_UNQUOTE(JSON_EXTRACT(a.metadata, '$.gender')) = :gender";
|
||||
$params['gender'] = strtoupper($filters['gender']);
|
||||
}
|
||||
|
||||
// Adult filter
|
||||
if (isset($filters['adult'])) {
|
||||
if ($filters['adult']) {
|
||||
$whereClauses[] = "aav.adult_video_id IS NOT NULL";
|
||||
} else {
|
||||
$whereClauses[] = "aav.adult_video_id IS NULL";
|
||||
}
|
||||
}
|
||||
|
||||
// Combine WHERE clauses
|
||||
if (!empty($whereClauses)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $whereClauses);
|
||||
}
|
||||
|
||||
$sql .= " GROUP BY a.id";
|
||||
|
||||
// Add sorting
|
||||
$sortBy = $filters['sort'] ?? 'name';
|
||||
$sortOrder = $filters['order'] ?? 'asc';
|
||||
|
||||
switch ($sortBy) {
|
||||
case 'name':
|
||||
$sql .= " ORDER BY a.name {$sortOrder}";
|
||||
break;
|
||||
case 'age':
|
||||
$sql .= " ORDER BY JSON_EXTRACT(a.metadata, '$.age') {$sortOrder}";
|
||||
break;
|
||||
case 'media_count':
|
||||
$sql .= " ORDER BY (COUNT(DISTINCT am.movie_id) + COUNT(DISTINCT te.tv_show_id) + COUNT(DISTINCT aav.adult_video_id)) {$sortOrder}";
|
||||
break;
|
||||
case 'movie_count':
|
||||
$sql .= " ORDER BY COUNT(DISTINCT am.movie_id) {$sortOrder}";
|
||||
break;
|
||||
case 'tv_show_count':
|
||||
$sql .= " ORDER BY COUNT(DISTINCT te.tv_show_id) {$sortOrder}";
|
||||
break;
|
||||
case 'adult_count':
|
||||
$sql .= " ORDER BY COUNT(DISTINCT aav.adult_video_id) {$sortOrder}";
|
||||
break;
|
||||
default:
|
||||
$sql .= " ORDER BY a.name ASC";
|
||||
}
|
||||
|
||||
if ($limit) {
|
||||
$sql .= " LIMIT :limit OFFSET :offset";
|
||||
$params['limit'] = $limit;
|
||||
$params['offset'] = $offset;
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count actors with filters
|
||||
*/
|
||||
public function count(array $filters = []): int
|
||||
{
|
||||
$sql = "
|
||||
SELECT COUNT(DISTINCT a.id) as total
|
||||
FROM actors a
|
||||
LEFT JOIN actor_movie am ON a.id = am.actor_id
|
||||
LEFT JOIN actor_tv_episode ate ON a.id = ate.actor_id
|
||||
LEFT JOIN tv_episodes te ON ate.tv_episode_id = te.id
|
||||
LEFT JOIN actor_adult_video aav ON a.id = aav.actor_id
|
||||
";
|
||||
$params = [];
|
||||
$whereClauses = [];
|
||||
|
||||
// Search filter
|
||||
if (!empty($filters['search'])) {
|
||||
$whereClauses[] = "a.name LIKE :search";
|
||||
$params['search'] = "%{$filters['search']}%";
|
||||
}
|
||||
|
||||
// Gender filter
|
||||
if (!empty($filters['gender'])) {
|
||||
$whereClauses[] = "JSON_UNQUOTE(JSON_EXTRACT(a.metadata, '$.gender')) = :gender";
|
||||
$params['gender'] = strtoupper($filters['gender']);
|
||||
}
|
||||
|
||||
// Adult filter
|
||||
if (isset($filters['adult'])) {
|
||||
if ($filters['adult']) {
|
||||
$whereClauses[] = "aav.adult_video_id IS NOT NULL";
|
||||
} else {
|
||||
$whereClauses[] = "aav.adult_video_id IS NULL";
|
||||
}
|
||||
}
|
||||
|
||||
// Combine WHERE clauses
|
||||
if (!empty($whereClauses)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $whereClauses);
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$result = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
return (int)$result['total'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all movies this actor is associated with
|
||||
*/
|
||||
@@ -29,24 +162,83 @@ class Actor extends Model
|
||||
ORDER BY m.release_date DESC, m.title ASC
|
||||
");
|
||||
$stmt->execute(['actor_id' => $this->id]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$movies = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Process poster URLs for each movie
|
||||
foreach ($movies as &$movie) {
|
||||
// Use poster_url field directly if available
|
||||
if (!empty($movie['poster_url'])) {
|
||||
// Keep the existing poster_url as-is since it's already in the correct format
|
||||
}
|
||||
|
||||
// Also process metadata for additional fields
|
||||
if (!empty($movie['metadata'])) {
|
||||
$metadata = json_decode($movie['metadata'], true);
|
||||
|
||||
// Extract poster aspect ratio from metadata if available
|
||||
if (!empty($metadata['poster_aspect_ratio'])) {
|
||||
$movie['poster_aspect_ratio'] = $metadata['poster_aspect_ratio'];
|
||||
}
|
||||
|
||||
// If no poster_url in main field, try to get it from metadata
|
||||
if (empty($movie['poster_url'])) {
|
||||
if (!empty($metadata['local_cover_path'])) {
|
||||
$movie['poster_url'] = $metadata['local_cover_path'];
|
||||
} elseif (!empty($metadata['cover_url'])) {
|
||||
$movie['poster_url'] = $metadata['cover_url'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $movies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all TV shows this actor is associated with
|
||||
* Get all TV shows this actor is associated with (via episodes)
|
||||
*/
|
||||
public function tvShows()
|
||||
{
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT ts.*, s.display_name as source_name
|
||||
SELECT DISTINCT ts.*, s.display_name as source_name
|
||||
FROM tv_shows ts
|
||||
JOIN sources s ON ts.source_id = s.id
|
||||
JOIN actor_tv_show ats ON ts.id = ats.tv_show_id
|
||||
WHERE ats.actor_id = :actor_id
|
||||
JOIN tv_episodes te ON ts.id = te.tv_show_id
|
||||
JOIN actor_tv_episode ate ON te.id = ate.tv_episode_id
|
||||
WHERE ate.actor_id = :actor_id
|
||||
ORDER BY ts.first_air_date DESC, ts.title ASC
|
||||
");
|
||||
$stmt->execute(['actor_id' => $this->id]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$tvShows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Process poster URLs for each TV show
|
||||
foreach ($tvShows as &$tvShow) {
|
||||
// Use poster_url field directly if available
|
||||
if (!empty($tvShow['poster_url'])) {
|
||||
// Keep the existing poster_url as-is since it's already in the correct format
|
||||
}
|
||||
|
||||
// Also process metadata for additional fields
|
||||
if (!empty($tvShow['metadata'])) {
|
||||
$metadata = json_decode($tvShow['metadata'], true);
|
||||
|
||||
// Extract poster aspect ratio from metadata if available
|
||||
if (!empty($metadata['poster_aspect_ratio'])) {
|
||||
$tvShow['poster_aspect_ratio'] = $metadata['poster_aspect_ratio'];
|
||||
}
|
||||
|
||||
// If no poster_url in main field, try to get it from metadata
|
||||
if (empty($tvShow['poster_url'])) {
|
||||
if (!empty($metadata['local_cover_path'])) {
|
||||
$tvShow['poster_url'] = $metadata['local_cover_path'];
|
||||
} elseif (!empty($metadata['cover_url'])) {
|
||||
$tvShow['poster_url'] = $metadata['cover_url'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $tvShows;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +255,41 @@ class Actor extends Model
|
||||
ORDER BY av.release_date DESC, av.title ASC
|
||||
");
|
||||
$stmt->execute(['actor_id' => $this->id]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$adultVideos = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Process poster URLs for each adult video
|
||||
foreach ($adultVideos as &$adultVideo) {
|
||||
// Use poster_url field directly if available
|
||||
if (!empty($adultVideo['poster_url'])) {
|
||||
// Keep the existing poster_url as-is since it's already in the correct format
|
||||
}
|
||||
|
||||
// Also process metadata for additional fields
|
||||
if (!empty($adultVideo['metadata'])) {
|
||||
$metadata = json_decode($adultVideo['metadata'], true);
|
||||
|
||||
// Extract poster aspect ratio from metadata if available
|
||||
if (!empty($metadata['poster_aspect_ratio'])) {
|
||||
$adultVideo['poster_aspect_ratio'] = $metadata['poster_aspect_ratio'];
|
||||
}
|
||||
|
||||
// If no poster_url in main field, try to get it from metadata
|
||||
if (empty($adultVideo['poster_url'])) {
|
||||
if (!empty($metadata['local_cover_path'])) {
|
||||
$adultVideo['poster_url'] = $metadata['local_cover_path'];
|
||||
} elseif (!empty($metadata['cover_url'])) {
|
||||
$adultVideo['poster_url'] = $metadata['cover_url'];
|
||||
}
|
||||
}
|
||||
|
||||
// Add screenshot URL if available
|
||||
if (!empty($metadata['screenshot_url'])) {
|
||||
$adultVideo['screenshot_url'] = $metadata['screenshot_url'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $adultVideos;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,12 +300,13 @@ class Actor extends Model
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT
|
||||
COUNT(DISTINCT am.movie_id) as movie_count,
|
||||
COUNT(DISTINCT ats.tv_show_id) as tv_show_count,
|
||||
COUNT(DISTINCT te.tv_show_id) as tv_show_count,
|
||||
COUNT(DISTINCT aav.adult_video_id) as adult_video_count,
|
||||
COUNT(DISTINCT am.movie_id) + COUNT(DISTINCT ats.tv_show_id) + COUNT(DISTINCT aav.adult_video_id) as total_media_count
|
||||
COUNT(DISTINCT am.movie_id) + COUNT(DISTINCT te.tv_show_id) + COUNT(DISTINCT aav.adult_video_id) as total_media_count
|
||||
FROM actors a
|
||||
LEFT JOIN actor_movie am ON a.id = am.actor_id
|
||||
LEFT JOIN actor_tv_show ats ON a.id = ats.actor_id
|
||||
LEFT JOIN actor_tv_episode ate ON a.id = ate.actor_id
|
||||
LEFT JOIN tv_episodes te ON ate.tv_episode_id = te.id
|
||||
LEFT JOIN actor_adult_video aav ON a.id = aav.actor_id
|
||||
WHERE a.id = :actor_id
|
||||
");
|
||||
@@ -196,8 +423,8 @@ class Actor extends Model
|
||||
// Build ORDER BY clause
|
||||
$orderBy = match ($sort) {
|
||||
'name_desc' => 'name DESC',
|
||||
'media_desc' => '(SELECT COUNT(*) FROM actor_movie WHERE actor_id = actors.id) + (SELECT COUNT(*) FROM actor_tv_show WHERE actor_id = actors.id) + (SELECT COUNT(*) FROM actor_adult_video WHERE actor_id = actors.id) DESC',
|
||||
'media_asc' => '(SELECT COUNT(*) FROM actor_movie WHERE actor_id = actors.id) + (SELECT COUNT(*) FROM actor_tv_show WHERE actor_id = actors.id) + (SELECT COUNT(*) FROM actor_adult_video WHERE actor_id = actors.id) ASC',
|
||||
'media_desc' => '(SELECT COUNT(*) FROM actor_movie WHERE actor_id = actors.id) + (SELECT COUNT(DISTINCT te.tv_show_id) FROM actor_tv_episode ate JOIN tv_episodes te ON ate.tv_episode_id = te.id WHERE ate.actor_id = actors.id) + (SELECT COUNT(*) FROM actor_adult_video WHERE actor_id = actors.id) DESC',
|
||||
'media_asc' => '(SELECT COUNT(*) FROM actor_movie WHERE actor_id = actors.id) + (SELECT COUNT(DISTINCT te.tv_show_id) FROM actor_tv_episode ate JOIN tv_episodes te ON ate.tv_episode_id = te.id WHERE ate.actor_id = actors.id) + (SELECT COUNT(*) FROM actor_adult_video WHERE actor_id = actors.id) ASC',
|
||||
default => 'name ASC'
|
||||
};
|
||||
|
||||
@@ -206,7 +433,7 @@ class Actor extends Model
|
||||
SELECT
|
||||
a.*,
|
||||
(SELECT COUNT(*) FROM actor_movie WHERE actor_id = a.id) as movie_count,
|
||||
(SELECT COUNT(*) FROM actor_tv_show WHERE actor_id = a.id) as tv_show_count,
|
||||
(SELECT COUNT(DISTINCT te.tv_show_id) FROM actor_tv_episode ate JOIN tv_episodes te ON ate.tv_episode_id = te.id WHERE ate.actor_id = a.id) as tv_show_count,
|
||||
(SELECT COUNT(*) FROM actor_adult_video WHERE actor_id = a.id) as adult_video_count
|
||||
FROM actors a
|
||||
{$whereClause}
|
||||
@@ -279,10 +506,11 @@ class Actor extends Model
|
||||
private function getActorTvShows(int $actorId): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT ts.id, ts.title
|
||||
SELECT DISTINCT ts.id, ts.title
|
||||
FROM tv_shows ts
|
||||
JOIN actor_tv_show ats ON ts.id = ats.tv_show_id
|
||||
WHERE ats.actor_id = ?
|
||||
JOIN tv_episodes te ON ts.id = te.tv_show_id
|
||||
JOIN actor_tv_episode ate ON te.id = ate.tv_episode_id
|
||||
WHERE ate.actor_id = ?
|
||||
ORDER BY ts.title
|
||||
");
|
||||
$stmt->execute([$actorId]);
|
||||
|
||||
@@ -230,7 +230,7 @@ class AdultVideo extends Model
|
||||
*/
|
||||
public function updateCastField(): bool
|
||||
{
|
||||
$actors = $this->actors();
|
||||
$actors = $this->actors($this->id);
|
||||
$actorNames = array_column($actors, 'name');
|
||||
$castString = implode(', ', $actorNames);
|
||||
|
||||
@@ -239,6 +239,194 @@ class AdultVideo extends Model
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find single adult video by ID with metadata processing
|
||||
*/
|
||||
public function find(int $id): ?array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT av.*, s.display_name as source_name
|
||||
FROM adult_videos av
|
||||
JOIN sources s ON av.source_id = s.id
|
||||
WHERE av.id = :id
|
||||
");
|
||||
$stmt->execute(['id' => $id]);
|
||||
$video = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$video) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Process metadata to extract additional fields
|
||||
if (!empty($video['metadata'])) {
|
||||
$metadata = json_decode($video['metadata'], true);
|
||||
|
||||
// Extract poster aspect ratio from metadata if available
|
||||
if (!empty($metadata['poster_aspect_ratio'])) {
|
||||
$video['poster_aspect_ratio'] = $metadata['poster_aspect_ratio'];
|
||||
}
|
||||
|
||||
// Use local cover path if available, otherwise fall back to original URL
|
||||
if (!empty($metadata['local_cover_path'])) {
|
||||
$video['poster_url'] = $metadata['local_cover_path'];
|
||||
} elseif (!empty($metadata['cover_url'])) {
|
||||
$video['poster_url'] = $metadata['cover_url'];
|
||||
}
|
||||
|
||||
// Add screenshot URL if available
|
||||
if (!empty($metadata['screenshot_url'])) {
|
||||
$video['screenshot_url'] = $metadata['screenshot_url'];
|
||||
}
|
||||
|
||||
// Add actors data if available
|
||||
if (!empty($metadata['actors'])) {
|
||||
$video['actors'] = $metadata['actors'];
|
||||
}
|
||||
}
|
||||
|
||||
return $video;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all adult videos with filtering and pagination
|
||||
*/
|
||||
public function findAll(array $filters = [], int $limit = null, int $offset = 0): array
|
||||
{
|
||||
$sql = "
|
||||
SELECT av.*, s.display_name as source_name
|
||||
FROM adult_videos av
|
||||
JOIN sources s ON av.source_id = s.id
|
||||
";
|
||||
$params = [];
|
||||
$whereClauses = [];
|
||||
|
||||
// Search filter
|
||||
if (!empty($filters['search'])) {
|
||||
$whereClauses[] = "(av.title LIKE :search OR av.overview LIKE :search)";
|
||||
$params['search'] = "%{$filters['search']}%";
|
||||
}
|
||||
|
||||
// Genre filter
|
||||
if (!empty($filters['genre'])) {
|
||||
$whereClauses[] = "av.genre = :genre";
|
||||
$params['genre'] = $filters['genre'];
|
||||
}
|
||||
|
||||
// Year filter
|
||||
if (!empty($filters['year'])) {
|
||||
$whereClauses[] = "YEAR(av.release_date) = :year";
|
||||
$params['year'] = $filters['year'];
|
||||
}
|
||||
|
||||
// Source filter
|
||||
if (!empty($filters['sources'])) {
|
||||
$sources = is_array($filters['sources']) ? $filters['sources'] : [$filters['sources']];
|
||||
$placeholders = [];
|
||||
foreach ($sources as $index => $source) {
|
||||
$placeholders[] = ":source_{$index}";
|
||||
$params["source_{$index}"] = $source;
|
||||
}
|
||||
$whereClauses[] = "s.display_name IN (" . implode(',', $placeholders) . ")";
|
||||
}
|
||||
|
||||
// Combine WHERE clauses
|
||||
if (!empty($whereClauses)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $whereClauses);
|
||||
}
|
||||
|
||||
// Add ordering and pagination
|
||||
$sql .= " ORDER BY av.release_date DESC, av.title ASC";
|
||||
|
||||
if ($limit) {
|
||||
$sql .= " LIMIT :limit OFFSET :offset";
|
||||
$params['limit'] = $limit;
|
||||
$params['offset'] = $offset;
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$results = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Process metadata to extract additional fields
|
||||
foreach ($results as &$video) {
|
||||
if (!empty($video['metadata'])) {
|
||||
$metadata = json_decode($video['metadata'], true);
|
||||
|
||||
// Extract poster aspect ratio from metadata if available
|
||||
if (!empty($metadata['poster_aspect_ratio'])) {
|
||||
$video['poster_aspect_ratio'] = $metadata['poster_aspect_ratio'];
|
||||
}
|
||||
|
||||
// Use local cover path if available, otherwise fall back to original URL
|
||||
if (!empty($metadata['local_cover_path'])) {
|
||||
$video['poster_url'] = $metadata['local_cover_path'];
|
||||
} elseif (!empty($metadata['cover_url'])) {
|
||||
$video['poster_url'] = $metadata['cover_url'];
|
||||
}
|
||||
|
||||
// Add actors data if available
|
||||
if (!empty($metadata['actors'])) {
|
||||
$video['actors'] = $metadata['actors'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count adult videos with filters
|
||||
*/
|
||||
public function count(array $filters = []): int
|
||||
{
|
||||
$sql = "
|
||||
SELECT COUNT(*) as total
|
||||
FROM adult_videos av
|
||||
JOIN sources s ON av.source_id = s.id
|
||||
";
|
||||
$params = [];
|
||||
$whereClauses = [];
|
||||
|
||||
// Search filter
|
||||
if (!empty($filters['search'])) {
|
||||
$whereClauses[] = "(av.title LIKE :search OR av.overview LIKE :search)";
|
||||
$params['search'] = "%{$filters['search']}%";
|
||||
}
|
||||
|
||||
// Genre filter
|
||||
if (!empty($filters['genre'])) {
|
||||
$whereClauses[] = "av.genre = :genre";
|
||||
$params['genre'] = $filters['genre'];
|
||||
}
|
||||
|
||||
// Year filter
|
||||
if (!empty($filters['year'])) {
|
||||
$whereClauses[] = "YEAR(av.release_date) = :year";
|
||||
$params['year'] = $filters['year'];
|
||||
}
|
||||
|
||||
// Source filter
|
||||
if (!empty($filters['sources'])) {
|
||||
$sources = is_array($filters['sources']) ? $filters['sources'] : [$filters['sources']];
|
||||
$placeholders = [];
|
||||
foreach ($sources as $index => $source) {
|
||||
$placeholders[] = ":source_{$index}";
|
||||
$params["source_{$index}"] = $source;
|
||||
}
|
||||
$whereClauses[] = "s.display_name IN (" . implode(',', $placeholders) . ")";
|
||||
}
|
||||
|
||||
// Combine WHERE clauses
|
||||
if (!empty($whereClauses)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $whereClauses);
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$result = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
return (int)$result['total'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available genres for filtering
|
||||
*/
|
||||
|
||||
@@ -356,62 +356,93 @@ class TvShow extends Model
|
||||
|
||||
public function getSeasonsWithEpisodes(): array
|
||||
{
|
||||
// Get all episodes for this TV show, grouped by season
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT season_number,
|
||||
COUNT(*) as episode_count,
|
||||
SUM(CASE WHEN watched = 1 THEN 1 ELSE 0 END) as watched_episodes
|
||||
FROM tv_episodes
|
||||
WHERE tv_show_id = :tv_show_id
|
||||
GROUP BY season_number
|
||||
ORDER BY season_number ASC
|
||||
");
|
||||
$stmt->execute(['tv_show_id' => $this->id]);
|
||||
$seasonStats = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
$seasons = [];
|
||||
|
||||
// For each season, get the episodes and create a season object
|
||||
foreach ($seasonStats as $stat) {
|
||||
$seasonNumber = $stat['season_number'];
|
||||
|
||||
// Get episodes for this season
|
||||
try {
|
||||
// Get all episodes for this TV show, grouped by season
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT e.*
|
||||
FROM tv_episodes e
|
||||
WHERE e.tv_show_id = :tv_show_id AND e.season_number = :season_number
|
||||
ORDER BY e.episode_number ASC
|
||||
SELECT season_number,
|
||||
COUNT(*) as episode_count,
|
||||
SUM(CASE WHEN watched = 1 THEN 1 ELSE 0 END) as watched_episodes
|
||||
FROM tv_episodes
|
||||
WHERE tv_show_id = :tv_show_id
|
||||
GROUP BY season_number
|
||||
ORDER BY season_number ASC
|
||||
");
|
||||
$stmt->execute([
|
||||
'tv_show_id' => $this->id,
|
||||
'season_number' => $seasonNumber
|
||||
]);
|
||||
$episodes = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$stmt->execute(['tv_show_id' => $this->id]);
|
||||
$seasonStats = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Add actors for each episode
|
||||
foreach ($episodes as &$episode) {
|
||||
$episodeStmt = $this->pdo->prepare("
|
||||
SELECT a.*
|
||||
FROM actors a
|
||||
JOIN actor_tv_episode ate ON a.id = ate.actor_id
|
||||
WHERE ate.tv_episode_id = :tv_episode_id
|
||||
ORDER BY a.name ASC
|
||||
$seasons = [];
|
||||
|
||||
// For each season, get the episodes and create a season object
|
||||
foreach ($seasonStats as $stat) {
|
||||
$seasonNumber = $stat['season_number'];
|
||||
|
||||
// Get episodes for this season
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT e.*
|
||||
FROM tv_episodes e
|
||||
WHERE e.tv_show_id = :tv_show_id AND e.season_number = :season_number
|
||||
ORDER BY e.episode_number ASC
|
||||
");
|
||||
$episodeStmt->execute(['tv_episode_id' => $episode['id']]);
|
||||
$episode['actors'] = $episodeStmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$stmt->execute([
|
||||
'tv_show_id' => $this->id,
|
||||
'season_number' => $seasonNumber
|
||||
]);
|
||||
$episodes = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Add actors for each episode
|
||||
foreach ($episodes as &$episode) {
|
||||
try {
|
||||
$episodeStmt = $this->pdo->prepare("
|
||||
SELECT a.*
|
||||
FROM actors a
|
||||
JOIN actor_tv_episode ate ON a.id = ate.actor_id
|
||||
WHERE ate.tv_episode_id = :tv_episode_id
|
||||
ORDER BY a.name ASC
|
||||
");
|
||||
$episodeStmt->execute(['tv_episode_id' => $episode['id']]);
|
||||
$episode['actors'] = $episodeStmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
} catch (\Exception $e) {
|
||||
$episode['actors'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Create a season object (simulating the old seasons table structure)
|
||||
$seasons[] = [
|
||||
'id' => null, // No seasons table, so no ID
|
||||
'season_number' => $seasonNumber,
|
||||
'episode_count' => (int)$stat['episode_count'],
|
||||
'watched_episodes' => (int)$stat['watched_episodes'],
|
||||
'episodes' => $episodes
|
||||
];
|
||||
}
|
||||
|
||||
// Create a season object (simulating the old seasons table structure)
|
||||
$seasons[] = [
|
||||
'id' => null, // No seasons table, so no ID
|
||||
'season_number' => $seasonNumber,
|
||||
'episode_count' => (int)$stat['episode_count'],
|
||||
'watched_episodes' => (int)$stat['watched_episodes'],
|
||||
'episodes' => $episodes
|
||||
];
|
||||
return $seasons;
|
||||
} catch (\Exception $e) {
|
||||
// Return empty array if tables don't exist or query fails
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return $seasons;
|
||||
/**
|
||||
* Get all actors for this TV show (from all episodes)
|
||||
*/
|
||||
public function getActors(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT DISTINCT a.*
|
||||
FROM actors a
|
||||
JOIN actor_tv_episode ate ON a.id = ate.actor_id
|
||||
JOIN tv_episodes te ON ate.tv_episode_id = te.id
|
||||
WHERE te.tv_show_id = :tv_show_id
|
||||
ORDER BY a.name ASC
|
||||
");
|
||||
$stmt->execute(['tv_show_id' => $this->id]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
} catch (\Exception $e) {
|
||||
// Return empty array if tables don't exist or query fails
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -471,6 +502,147 @@ public static function getAvailableGenres(\PDO $pdo): array
|
||||
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available sources from TV shows
|
||||
*/
|
||||
public static function getAvailableSources(\PDO $pdo): array
|
||||
{
|
||||
$stmt = $pdo->query("
|
||||
SELECT DISTINCT s.display_name
|
||||
FROM tv_shows t
|
||||
JOIN sources s ON t.source_id = s.id
|
||||
WHERE t.source_id IS NOT NULL
|
||||
ORDER BY s.display_name
|
||||
");
|
||||
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all TV shows with filtering and pagination
|
||||
*/
|
||||
public function findAll(array $filters = [], int $limit = null, int $offset = 0): array
|
||||
{
|
||||
$sql = "
|
||||
SELECT t.*, s.display_name as source_name
|
||||
FROM tv_shows t
|
||||
LEFT JOIN sources s ON t.source_id = s.id
|
||||
";
|
||||
$params = [];
|
||||
$whereClauses = [];
|
||||
|
||||
// Search filter
|
||||
if (!empty($filters['search'])) {
|
||||
$whereClauses[] = "(t.title LIKE :search OR t.overview LIKE :search)";
|
||||
$params['search'] = "%{$filters['search']}%";
|
||||
}
|
||||
|
||||
// Genre filter
|
||||
if (!empty($filters['genre'])) {
|
||||
$whereClauses[] = "t.genre = :genre";
|
||||
$params['genre'] = $filters['genre'];
|
||||
}
|
||||
|
||||
// Year filter
|
||||
if (!empty($filters['year'])) {
|
||||
$whereClauses[] = "YEAR(t.first_air_date) = :year";
|
||||
$params['year'] = $filters['year'];
|
||||
}
|
||||
|
||||
// Source filter
|
||||
if (!empty($filters['sources'])) {
|
||||
$sources = is_array($filters['sources']) ? $filters['sources'] : [$filters['sources']];
|
||||
$placeholders = [];
|
||||
foreach ($sources as $index => $source) {
|
||||
$placeholders[] = ":source_{$index}";
|
||||
$params["source_{$index}"] = $source;
|
||||
}
|
||||
$whereClauses[] = "s.display_name IN (" . implode(',', $placeholders) . ")";
|
||||
}
|
||||
|
||||
// Combine WHERE clauses
|
||||
if (!empty($whereClauses)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $whereClauses);
|
||||
}
|
||||
|
||||
// Add ordering and pagination
|
||||
$sql .= " ORDER BY t.first_air_date DESC, t.title ASC";
|
||||
|
||||
if ($limit) {
|
||||
$sql .= " LIMIT :limit OFFSET :offset";
|
||||
$params['limit'] = $limit;
|
||||
$params['offset'] = $offset;
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$results = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Process metadata to extract additional fields
|
||||
foreach ($results as &$tvShow) {
|
||||
if (!empty($tvShow['metadata'])) {
|
||||
$metadata = json_decode($tvShow['metadata'], true);
|
||||
|
||||
// Extract additional fields from metadata if available
|
||||
// This can be expanded based on your metadata structure
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count TV shows with filters
|
||||
*/
|
||||
public function count(array $filters = []): int
|
||||
{
|
||||
$sql = "
|
||||
SELECT COUNT(*) as total
|
||||
FROM tv_shows t
|
||||
LEFT JOIN sources s ON t.source_id = s.id
|
||||
";
|
||||
$params = [];
|
||||
$whereClauses = [];
|
||||
|
||||
// Search filter
|
||||
if (!empty($filters['search'])) {
|
||||
$whereClauses[] = "(t.title LIKE :search OR t.overview LIKE :search)";
|
||||
$params['search'] = "%{$filters['search']}%";
|
||||
}
|
||||
|
||||
// Genre filter
|
||||
if (!empty($filters['genre'])) {
|
||||
$whereClauses[] = "t.genre = :genre";
|
||||
$params['genre'] = $filters['genre'];
|
||||
}
|
||||
|
||||
// Year filter
|
||||
if (!empty($filters['year'])) {
|
||||
$whereClauses[] = "YEAR(t.first_air_date) = :year";
|
||||
$params['year'] = $filters['year'];
|
||||
}
|
||||
|
||||
// Source filter
|
||||
if (!empty($filters['sources'])) {
|
||||
$sources = is_array($filters['sources']) ? $filters['sources'] : [$filters['sources']];
|
||||
$placeholders = [];
|
||||
foreach ($sources as $index => $source) {
|
||||
$placeholders[] = ":source_{$index}";
|
||||
$params["source_{$index}"] = $source;
|
||||
}
|
||||
$whereClauses[] = "s.display_name IN (" . implode(',', $placeholders) . ")";
|
||||
}
|
||||
|
||||
// Combine WHERE clauses
|
||||
if (!empty($whereClauses)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $whereClauses);
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$result = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
return (int)$result['total'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available years from TV shows' first_air_date
|
||||
*/
|
||||
|
||||
40
check_adult_structure.php
Normal file
40
check_adult_structure.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Load environment variables
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||||
$dotenv->load();
|
||||
|
||||
// Load database configuration
|
||||
$dbConfig = require __DIR__ . '/config/database.php';
|
||||
\App\Database\Database::setConfig($dbConfig);
|
||||
|
||||
// Initialize database
|
||||
try {
|
||||
$pdo = \App\Database\Database::getInstance();
|
||||
echo "✅ Database connection successful\n";
|
||||
} catch (Exception $e) {
|
||||
die('❌ Database connection failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// Check adult videos structure
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT id, title, poster_url, metadata FROM adult_videos LIMIT 1");
|
||||
$adultVideo = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($adultVideo) {
|
||||
echo "🔞 Sample adult video structure:\n";
|
||||
echo " - ID: {$adultVideo['id']}\n";
|
||||
echo " - Title: {$adultVideo['title']}\n";
|
||||
echo " - poster_url: " . ($adultVideo['poster_url'] ?? 'NULL') . "\n";
|
||||
echo " - metadata: " . substr($adultVideo['metadata'] ?? 'NULL', 0, 200) . "...\n";
|
||||
} else {
|
||||
echo "🔞 No adult videos found\n";
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
echo "\n✨ Check completed!\n";
|
||||
0
check_adult_videos.php
Normal file
0
check_adult_videos.php
Normal file
23
check_games_columns.php
Normal file
23
check_games_columns.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
require_once 'vendor/autoload.php';
|
||||
$container = require 'bootstrap/app.php';
|
||||
$pdo = $container->get('pdo');
|
||||
|
||||
// Get column names from games table
|
||||
$stmt = $pdo->query('DESCRIBE games');
|
||||
$columns = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo 'Games table columns:' . PHP_EOL;
|
||||
foreach ($columns as $column) {
|
||||
echo '- ' . $column['Field'] . ' (' . $column['Type'] . ')' . PHP_EOL;
|
||||
}
|
||||
|
||||
// Also check a sample row to see the actual data
|
||||
echo PHP_EOL . 'Sample game data:' . PHP_EOL;
|
||||
$stmt = $pdo->query('SELECT * FROM games LIMIT 1');
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($row) {
|
||||
foreach ($row as $key => $value) {
|
||||
echo '- ' . $key . ': ' . (is_null($value) ? 'NULL' : substr($value, 0, 50)) . PHP_EOL;
|
||||
}
|
||||
}
|
||||
80
check_gender_data.php
Normal file
80
check_gender_data.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Load environment variables
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||||
$dotenv->load();
|
||||
|
||||
// Load database configuration
|
||||
$dbConfig = require __DIR__ . '/config/database.php';
|
||||
\App\Database\Database::setConfig($dbConfig);
|
||||
|
||||
// Initialize database
|
||||
try {
|
||||
$pdo = \App\Database\Database::getInstance();
|
||||
echo "✅ Database connection successful\n";
|
||||
} catch (Exception $e) {
|
||||
die('❌ Database connection failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// Check gender data in actors table
|
||||
try {
|
||||
echo "\n🔍 Checking Gender Data:\n";
|
||||
|
||||
// Check if metadata column exists and has gender data
|
||||
$stmt = $pdo->query("
|
||||
SELECT name, metadata
|
||||
FROM actors
|
||||
WHERE metadata IS NOT NULL
|
||||
AND metadata != ''
|
||||
AND JSON_EXTRACT(metadata, '$.gender') IS NOT NULL
|
||||
LIMIT 10
|
||||
");
|
||||
$actors = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo "Found " . count($actors) . " actors with gender metadata:\n";
|
||||
|
||||
foreach ($actors as $actor) {
|
||||
$metadata = json_decode($actor['metadata'], true);
|
||||
$gender = $metadata['gender'] ?? 'null';
|
||||
echo " - {$actor['name']}: {$gender}\n";
|
||||
}
|
||||
|
||||
// Count actors by gender
|
||||
echo "\n📊 Gender Statistics:\n";
|
||||
|
||||
$genders = ['male', 'female', 'non-binary'];
|
||||
foreach ($genders as $gender) {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT COUNT(*) as count
|
||||
FROM actors
|
||||
WHERE JSON_EXTRACT(metadata, '$.gender') = :gender
|
||||
");
|
||||
$stmt->execute(['gender' => $gender]);
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
echo " - " . ucfirst($gender) . ": " . $result['count'] . "\n";
|
||||
}
|
||||
|
||||
// Check actors without gender
|
||||
$stmt = $pdo->query("
|
||||
SELECT COUNT(*) as count
|
||||
FROM actors
|
||||
WHERE metadata IS NULL
|
||||
OR metadata = ''
|
||||
OR JSON_EXTRACT(metadata, '$.gender') IS NULL
|
||||
");
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
echo " - No gender data: " . $result['count'] . "\n";
|
||||
|
||||
// Total actors
|
||||
$stmt = $pdo->query("SELECT COUNT(*) as count FROM actors");
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
echo " - Total actors: " . $result['count'] . "\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Error checking gender data: " . $e->getMessage() . "\n";
|
||||
echo "Stack trace: " . $e->getTraceAsString() . "\n";
|
||||
}
|
||||
|
||||
echo "\n✨ Check completed!\n";
|
||||
68
check_poster_structure.php
Normal file
68
check_poster_structure.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Load environment variables
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||||
$dotenv->load();
|
||||
|
||||
// Load database configuration
|
||||
$dbConfig = require __DIR__ . '/config/database.php';
|
||||
\App\Database\Database::setConfig($dbConfig);
|
||||
|
||||
// Initialize database
|
||||
try {
|
||||
$pdo = \App\Database\Database::getInstance();
|
||||
echo "✅ Database connection successful\n";
|
||||
} catch (Exception $e) {
|
||||
die('❌ Database connection failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// Check sample movie and TV show structure
|
||||
try {
|
||||
// Check movies
|
||||
$stmt = $pdo->query("SELECT id, title, poster_url, metadata FROM movies LIMIT 1");
|
||||
$movie = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($movie) {
|
||||
echo "🎬 Sample movie structure:\n";
|
||||
echo " - ID: {$movie['id']}\n";
|
||||
echo " - Title: {$movie['title']}\n";
|
||||
echo " - poster_url: " . ($movie['poster_url'] ?? 'NULL') . "\n";
|
||||
echo " - metadata: " . substr($movie['metadata'] ?? 'NULL', 0, 200) . "...\n";
|
||||
}
|
||||
|
||||
// Check TV shows
|
||||
$stmt = $pdo->query("SELECT id, title, poster_url, metadata FROM tv_shows LIMIT 1");
|
||||
$tvshow = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($tvshow) {
|
||||
echo "\n📺 Sample TV show structure:\n";
|
||||
echo " - ID: {$tvshow['id']}\n";
|
||||
echo " - Title: {$tvshow['title']}\n";
|
||||
echo " - poster_url: " . ($tvshow['poster_url'] ?? 'NULL') . "\n";
|
||||
echo " - metadata: " . substr($tvshow['metadata'] ?? 'NULL', 0, 200) . "...\n";
|
||||
}
|
||||
|
||||
// Test actor movies method
|
||||
echo "\n🎭 Testing actor movies method:\n";
|
||||
$stmt = $pdo->query("SELECT id FROM actors LIMIT 1");
|
||||
$actor = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($actor) {
|
||||
$actorModel = new \App\Models\Actor($pdo);
|
||||
$actorModel->id = $actor['id'];
|
||||
$movies = $actorModel->movies();
|
||||
|
||||
echo "Found " . count($movies) . " movies for actor {$actor['id']}\n";
|
||||
if (!empty($movies)) {
|
||||
$firstMovie = $movies[0];
|
||||
echo "First movie poster_url: " . ($firstMovie['poster_url'] ?? 'NULL') . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
echo "\n✨ Check completed!\n";
|
||||
89
check_tvshow_actors.php
Normal file
89
check_tvshow_actors.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Load environment variables
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||||
$dotenv->load();
|
||||
|
||||
// Load database configuration
|
||||
$dbConfig = require __DIR__ . '/config/database.php';
|
||||
\App\Database\Database::setConfig($dbConfig);
|
||||
|
||||
// Initialize database
|
||||
try {
|
||||
$pdo = \App\Database\Database::getInstance();
|
||||
echo "✅ Database connection successful\n";
|
||||
} catch (Exception $e) {
|
||||
die('❌ Database connection failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// Check TV shows count
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT COUNT(*) as count FROM tv_shows");
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
echo "📺 TV shows in database: {$result['count']}\n";
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Error checking TV shows: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
// Check actor_tv_episode table exists and has data
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT COUNT(*) as count FROM actor_tv_episode");
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
echo "🔗 Actor-TV episode relationships: {$result['count']}\n";
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Error checking actor_tv_episode table: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
// Check actors count
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT COUNT(*) as count FROM actors");
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
echo "👥 Actors in database: {$result['count']}\n";
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Error checking actors: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
// Sample TV show with cast
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT id, title, cast FROM tv_shows WHERE cast IS NOT NULL AND cast != '' LIMIT 3");
|
||||
$tvshows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($tvshows)) {
|
||||
echo "⚠️ No TV shows with cast data found\n";
|
||||
} else {
|
||||
echo "📋 Sample TV shows with cast:\n";
|
||||
foreach ($tvshows as $tvshow) {
|
||||
echo " - ID: {$tvshow['id']}, Title: {$tvshow['title']}, Cast: " . substr($tvshow['cast'], 0, 100) . "...\n";
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Error checking TV show cast data: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
// Sample actor relationships
|
||||
try {
|
||||
$stmt = $pdo->query("
|
||||
SELECT ts.title, a.name
|
||||
FROM tv_shows ts
|
||||
JOIN tv_episodes te ON ts.id = te.tv_show_id
|
||||
JOIN actor_tv_episode ate ON te.id = ate.tv_episode_id
|
||||
JOIN actors a ON ate.actor_id = a.id
|
||||
LIMIT 5
|
||||
");
|
||||
$relationships = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($relationships)) {
|
||||
echo "⚠️ No actor-TV episode relationships found\n";
|
||||
} else {
|
||||
echo "🔗 Sample actor-TV show relationships (via episodes):\n";
|
||||
foreach ($relationships as $rel) {
|
||||
echo " - {$rel['name']} in {$rel['title']}\n";
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Error checking actor relationships: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
echo "\n✨ Check completed!\n";
|
||||
@@ -1,90 +0,0 @@
|
||||
<?php
|
||||
|
||||
// Debug script to check Jellyfin sync issues
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
try {
|
||||
echo "=== Jellyfin Sync Debug ===\n";
|
||||
|
||||
// Check if TvEpisode model exists and works
|
||||
echo "Checking TvEpisode model...\n";
|
||||
if (class_exists('App\Models\TvEpisode')) {
|
||||
echo "✓ TvEpisode model exists\n";
|
||||
} else {
|
||||
echo "✗ TvEpisode model missing\n";
|
||||
}
|
||||
|
||||
// Check if database tables exist
|
||||
echo "\nChecking database tables...\n";
|
||||
$config = require __DIR__ . '/config/database.php';
|
||||
\App\Database\Database::setConfig($config);
|
||||
|
||||
try {
|
||||
$pdo = \App\Database\Database::getInstance();
|
||||
|
||||
$tables = ['tv_episodes', 'tv_shows', 'actors', 'actor_tv_episode', 'actor_tv_show', 'actor_movie'];
|
||||
foreach ($tables as $table) {
|
||||
try {
|
||||
$stmt = $pdo->query("SHOW TABLES LIKE '$table'");
|
||||
if ($stmt->rowCount() > 0) {
|
||||
echo "✓ Table '$table' exists\n";
|
||||
} else {
|
||||
echo "✗ Table '$table' missing\n";
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "✗ Error checking table '$table': " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "Database connection error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
// Test episode data structure
|
||||
echo "\nTesting episode data structure...\n";
|
||||
$testEpisodeData = [
|
||||
'Id' => 'test-episode-123',
|
||||
'Name' => 'Test Episode 1',
|
||||
'ParentIndexNumber' => 1,
|
||||
'IndexNumber' => 1,
|
||||
'PremiereDate' => '2023-01-01T00:00:00Z',
|
||||
'RunTimeTicks' => 18000000000, // 30 minutes
|
||||
'CommunityRating' => 8.5,
|
||||
'Overview' => 'Test episode overview',
|
||||
'ProviderIds' => [
|
||||
'Imdb' => 'tt1234567',
|
||||
'Tmdb' => '123456'
|
||||
],
|
||||
'People' => [
|
||||
[
|
||||
'Name' => 'Test Actor',
|
||||
'Type' => 'Actor'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
echo "✓ Episode has required fields:\n";
|
||||
echo " - ID: " . $testEpisodeData['Id'] . "\n";
|
||||
echo " - Name: " . $testEpisodeData['Name'] . "\n";
|
||||
echo " - Season: " . $testEpisodeData['ParentIndexNumber'] . "\n";
|
||||
echo " - Episode: " . $testEpisodeData['IndexNumber'] . "\n";
|
||||
echo " - People: " . (isset($testEpisodeData['People']) ? 'Yes' : 'No') . "\n";
|
||||
echo " - ProviderIds: " . (isset($testEpisodeData['ProviderIds']) ? 'Yes' : 'No') . "\n";
|
||||
|
||||
if (isset($testEpisodeData['People']) && is_array($testEpisodeData['People'])) {
|
||||
foreach ($testEpisodeData['People'] as $person) {
|
||||
if (isset($person['Type']) && $person['Type'] === 'Actor') {
|
||||
echo " - Actor found: " . $person['Name'] . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n=== Debug Complete ===\n";
|
||||
echo "The sync should work if:\n";
|
||||
echo "1. All models exist ✓\n";
|
||||
echo "2. Database tables exist ✓\n";
|
||||
echo "3. Jellyfin API returns 'People' field ✓\n";
|
||||
echo "4. syncTvShows() is called in executeSync() ✓\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
1607
package-lock.json
generated
1607
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "media-collector",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.14",
|
||||
"postcss": "^8.4.21",
|
||||
"sass": "^1.93.2",
|
||||
"vite": "^4.3.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"alpinejs": "^3.12.0",
|
||||
"axios": "^1.4.0"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -49,6 +49,8 @@ $app->group('/api', function (RouteCollectorProxy $group) use ($container) {
|
||||
// Games
|
||||
$group->get('/games', [$mediaController, 'listGames']);
|
||||
$group->get('/games/{id:[0-9]+}', [$mediaController, 'getGame']);
|
||||
$group->get('/games/grouped', [$mediaController, 'getGamesGroupedByPlatform']);
|
||||
$group->get('/games/categories/{category}', [$mediaController, 'getGamesByCategory']);
|
||||
|
||||
// Movies
|
||||
$group->get('/movies', [$mediaController, 'listMovies']);
|
||||
@@ -58,10 +60,22 @@ $app->group('/api', function (RouteCollectorProxy $group) use ($container) {
|
||||
$group->get('/tvshows', [$mediaController, 'listTvShows']);
|
||||
$group->get('/tvshows/{id:[0-9]+}', [$mediaController, 'getTvShow']);
|
||||
|
||||
// Actors
|
||||
$group->get('/actors', [$mediaController, 'listActors']);
|
||||
$group->get('/actors/{id:[0-9]+}', [$mediaController, 'getActor']);
|
||||
|
||||
// Adult Content
|
||||
$group->get('/adult', [$mediaController, 'listAdult']);
|
||||
$group->get('/adult/{id:[0-9]+}', [$mediaController, 'getAdult']);
|
||||
|
||||
// Search
|
||||
$group->get('/search', [$mediaController, 'search']);
|
||||
|
||||
})->add(new ApiAuthMiddleware($container->get(AuthService::class)));
|
||||
// Dashboard
|
||||
$group->get('/dashboard/stats', [$container->get(\App\Controllers\Api\DashboardController::class), 'getStats']);
|
||||
$group->get('/dashboard/activity', [$container->get(\App\Controllers\Api\DashboardController::class), 'getRecentActivity']);
|
||||
|
||||
});
|
||||
|
||||
// Admin routes (require admin role)
|
||||
$group->group('/admin', function (RouteCollectorProxy $group) use ($container) {
|
||||
|
||||
@@ -18,11 +18,11 @@ $app->post('/login', AuthController::class . ':login')->setName('auth.login.post
|
||||
$app->post('/logout', AuthController::class . ':logout')->setName('auth.logout');
|
||||
$app->get('/logout', AuthController::class . ':logout')->setName('auth.logout');
|
||||
|
||||
// Public image serving (no auth required)
|
||||
$app->get('/images/{path:.+}', 'App\Controllers\ImageController:serve')->setName('images.serve');
|
||||
|
||||
// Protected routes (require authentication)
|
||||
$app->group('', function (RouteCollectorProxy $group) {
|
||||
// Image serving (no auth required for public images)
|
||||
$group->get('/images/{path:.+}', 'App\Controllers\ImageController:serve')->setName('images.serve');
|
||||
|
||||
// Global Search
|
||||
$group->get('/search', 'App\Controllers\SearchController:index')->setName('search.index');
|
||||
$group->get('/', 'App\Controllers\DashboardController:index')->setName('dashboard.index');
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./resources/views/**/*.twig",
|
||||
"./public/**/*.js",
|
||||
"./resources/**/*.js",
|
||||
"./resources/**/*.css",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Load environment variables
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||||
$dotenv->load();
|
||||
|
||||
// Load database configuration
|
||||
$dbConfig = require __DIR__ . '/config/database.php';
|
||||
\App\Database\Database::setConfig($dbConfig);
|
||||
|
||||
// Initialize database
|
||||
try {
|
||||
$pdo = \App\Database\Database::getInstance();
|
||||
} catch (Exception $e) {
|
||||
die('Database connection failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// Start session
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// Test authentication service
|
||||
$authService = new \App\Services\AuthService($pdo);
|
||||
|
||||
echo "Testing login functionality...\n";
|
||||
|
||||
// Test 1: Check if admin user exists
|
||||
$user = \App\Models\User::findByUsername($pdo, 'admin');
|
||||
if (!$user) {
|
||||
echo "ERROR: Admin user not found in database!\n";
|
||||
exit(1);
|
||||
}
|
||||
echo "✓ Admin user exists in database\n";
|
||||
|
||||
// Test 2: Test password verification
|
||||
$userModel = new \App\Models\User($pdo);
|
||||
$userModel->password = $user['password'];
|
||||
$passwordValid = $userModel->verifyPassword('admin123');
|
||||
if (!$passwordValid) {
|
||||
echo "ERROR: Password verification failed!\n";
|
||||
exit(1);
|
||||
}
|
||||
echo "✓ Password verification works\n";
|
||||
|
||||
// Test 3: Test login method
|
||||
$loginSuccess = $authService->login('admin', 'admin123', '127.0.0.1');
|
||||
if (!$loginSuccess) {
|
||||
echo "ERROR: Login method failed!\n";
|
||||
exit(1);
|
||||
}
|
||||
echo "✓ Login method works\n";
|
||||
|
||||
// Test 4: Check if user is logged in
|
||||
if (!$authService->isLoggedIn()) {
|
||||
echo "ERROR: User is not logged in after successful login!\n";
|
||||
exit(1);
|
||||
}
|
||||
echo "✓ User is logged in\n";
|
||||
|
||||
// Test 5: Check if user is admin
|
||||
if (!$authService->isAdmin()) {
|
||||
echo "ERROR: User is not recognized as admin!\n";
|
||||
exit(1);
|
||||
}
|
||||
echo "✓ User is recognized as admin\n";
|
||||
|
||||
echo "\n🎉 All authentication tests passed!\n";
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
// Test script to verify Jellyfin episode syncing
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
try {
|
||||
echo "Testing Jellyfin episode sync...\n";
|
||||
|
||||
// Mock some test data to verify the logic works
|
||||
$testEpisodeData = [
|
||||
'Id' => 'test-episode-123',
|
||||
'Name' => 'Test Episode',
|
||||
'ParentIndexNumber' => 1,
|
||||
'IndexNumber' => 1,
|
||||
'PremiereDate' => '2023-01-01T00:00:00Z',
|
||||
'RunTimeTicks' => 18000000000, // 30 minutes in ticks
|
||||
'CommunityRating' => 8.5,
|
||||
'Overview' => 'Test episode overview',
|
||||
'ProviderIds' => [
|
||||
'Imdb' => 'tt1234567',
|
||||
'Tmdb' => '123456'
|
||||
],
|
||||
'People' => [
|
||||
[
|
||||
'Name' => 'Test Actor',
|
||||
'Type' => 'Actor'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
echo "Test episode data structure looks correct\n";
|
||||
echo "Episode ID: " . $testEpisodeData['Id'] . "\n";
|
||||
echo "Episode Name: " . $testEpisodeData['Name'] . "\n";
|
||||
echo "Season: " . $testEpisodeData['ParentIndexNumber'] . "\n";
|
||||
echo "Episode Number: " . $testEpisodeData['IndexNumber'] . "\n";
|
||||
echo "Has Actor: " . (isset($testEpisodeData['People'][0]['Name']) ? 'Yes' : 'No') . "\n";
|
||||
|
||||
echo "\nEpisode sync logic should work correctly!\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Load environment variables
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||||
$dotenv->load();
|
||||
|
||||
// Load database configuration
|
||||
$dbConfig = require __DIR__ . '/config/database.php';
|
||||
\App\Database\Database::setConfig($dbConfig);
|
||||
|
||||
// Initialize database
|
||||
try {
|
||||
$pdo = \App\Database\Database::getInstance();
|
||||
echo "✅ Database connection successful\n";
|
||||
} catch (Exception $e) {
|
||||
die('❌ Database connection failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// Check if tables exist
|
||||
try {
|
||||
$tables = ['sources', 'movies', 'sync_logs', 'games'];
|
||||
foreach ($tables as $table) {
|
||||
$stmt = $pdo->query("SHOW TABLES LIKE '{$table}'");
|
||||
if ($stmt->rowCount() > 0) {
|
||||
echo "✅ Table '{$table}' exists\n";
|
||||
} else {
|
||||
echo "❌ Table '{$table}' does not exist\n";
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Error checking tables: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
// Test Jellyfin connectivity (if configured)
|
||||
echo "\n🔍 Testing Jellyfin connectivity...\n";
|
||||
try {
|
||||
$sourceModel = new \App\Models\Source($pdo);
|
||||
$sources = $sourceModel->findAll(['name' => 'jellyfin']);
|
||||
|
||||
if (empty($sources)) {
|
||||
echo "❌ No Jellyfin source configured\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
$jellyfinSource = $sources[0];
|
||||
echo "✅ Found Jellyfin source: {$jellyfinSource['display_name']}\n";
|
||||
|
||||
if (empty($jellyfinSource['api_key']) || empty($jellyfinSource['api_url'])) {
|
||||
echo "❌ Jellyfin API key or URL not configured\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Test HTTP connection to Jellyfin
|
||||
$client = new GuzzleHttp\Client(['timeout' => 10]);
|
||||
$url = rtrim($jellyfinSource['api_url'], '/') . '/Users';
|
||||
|
||||
echo "🔗 Testing connection to: {$url}\n";
|
||||
|
||||
$response = $client->get($url, [
|
||||
'headers' => [
|
||||
'X-MediaBrowser-Token' => $jellyfinSource['api_key'],
|
||||
'User-Agent' => 'MediaCollector-Test/1.0'
|
||||
]
|
||||
]);
|
||||
|
||||
$httpCode = $response->getStatusCode();
|
||||
echo "✅ HTTP Response: {$httpCode}\n";
|
||||
|
||||
if ($httpCode === 200) {
|
||||
$data = json_decode($response->getBody(), true);
|
||||
$userCount = count($data);
|
||||
echo "✅ Found {$userCount} users in Jellyfin\n";
|
||||
|
||||
if ($userCount > 0) {
|
||||
$userId = $data[0]['Id'];
|
||||
echo "✅ Using user ID: {$userId}\n";
|
||||
|
||||
// Test getting movies
|
||||
$moviesUrl = rtrim($jellyfinSource['api_url'], '/') . "/Users/{$userId}/Items";
|
||||
echo "🔗 Testing movies endpoint: {$moviesUrl}\n";
|
||||
|
||||
$moviesResponse = $client->get($moviesUrl, [
|
||||
'headers' => [
|
||||
'X-MediaBrowser-Token' => $jellyfinSource['api_key'],
|
||||
'User-Agent' => 'MediaCollector-Test/1.0'
|
||||
],
|
||||
'query' => [
|
||||
'IncludeItemTypes' => 'Movie',
|
||||
'Recursive' => 'true',
|
||||
'Fields' => 'ProviderIds,Overview,PremiereDate,CommunityRating'
|
||||
]
|
||||
]);
|
||||
|
||||
$moviesHttpCode = $moviesResponse->getStatusCode();
|
||||
echo "✅ Movies HTTP Response: {$moviesHttpCode}\n";
|
||||
|
||||
if ($moviesHttpCode === 200) {
|
||||
$moviesData = json_decode($moviesResponse->getBody(), true);
|
||||
$movieCount = count($moviesData['Items'] ?? []);
|
||||
echo "✅ Found {$movieCount} movies in Jellyfin library\n";
|
||||
} else {
|
||||
echo "❌ Failed to fetch movies from Jellyfin\n";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
echo "❌ Jellyfin API returned HTTP {$httpCode}\n";
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Jellyfin connectivity test failed: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
echo "\n✨ Test completed!\n";
|
||||
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
// Test Jellyfin sync execution
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
try {
|
||||
echo "=== Testing Jellyfin Sync Execution ===\n";
|
||||
|
||||
// Load database config
|
||||
$config = require __DIR__ . '/config/database.php';
|
||||
\App\Database\Database::setConfig($config);
|
||||
|
||||
// Create a mock source for testing
|
||||
$testSource = [
|
||||
'id' => 1,
|
||||
'name' => 'jellyfin',
|
||||
'api_url' => 'http://192.168.1.102:8096', // Adjust this to your Jellyfin URL
|
||||
'api_key' => '1db2d28854e541dd90c32ea6aab5e603' // Adjust this to your API key
|
||||
];
|
||||
|
||||
echo "Testing with source: " . $testSource['name'] . "\n";
|
||||
echo "API URL: " . $testSource['api_url'] . "\n";
|
||||
|
||||
// Create Jellyfin service instance
|
||||
$pdo = \App\Database\Database::getInstance();
|
||||
$jellyfinService = new \App\Services\JellyfinSyncService($pdo, $testSource);
|
||||
|
||||
// Test basic sync execution
|
||||
echo "\nTesting basic sync execution...\n";
|
||||
try {
|
||||
// This will trigger the executeSync method which calls all the private methods
|
||||
echo "Attempting to run sync (this may fail if Jellyfin server is not accessible)...\n";
|
||||
$jellyfinService->startSync('full');
|
||||
echo "✓ Sync completed successfully\n";
|
||||
} catch (Exception $e) {
|
||||
echo "✗ Sync failed (expected if Jellyfin server not accessible): " . $e->getMessage() . "\n";
|
||||
echo "This is normal if your Jellyfin server is not running or credentials are incorrect.\n";
|
||||
}
|
||||
|
||||
echo "\n=== Test Complete ===\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
|
||||
}
|
||||
136
test_stash.php
136
test_stash.php
@@ -1,136 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Stash Connectivity Test
|
||||
*
|
||||
* Tests if the Stash server is reachable and responding properly.
|
||||
*/
|
||||
|
||||
// Check if we're in the right directory
|
||||
if (!file_exists('app/Services/StashSyncService.php')) {
|
||||
echo "Error: Please run this script from the project root directory.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
// Load environment variables
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||||
$dotenv->load();
|
||||
|
||||
// Load database configuration
|
||||
$dbConfig = require __DIR__ . '/config/database.php';
|
||||
\App\Database\Database::setConfig($dbConfig);
|
||||
|
||||
// Initialize database
|
||||
try {
|
||||
$pdo = \App\Database\Database::getInstance();
|
||||
echo "✅ Database connection successful\n";
|
||||
|
||||
// Get Stash source
|
||||
$stmt = $pdo->prepare('SELECT * FROM sources WHERE name = ?');
|
||||
$stmt->execute(['stash']);
|
||||
$stashSource = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$stashSource) {
|
||||
echo "❌ No Stash source found in database\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "🔍 Testing Stash server connectivity...\n";
|
||||
echo " URL: {$stashSource['api_url']}\n";
|
||||
echo " API Key: " . (empty($stashSource['api_key']) ? 'NOT SET' : 'SET') . "\n\n";
|
||||
|
||||
// Test basic connectivity
|
||||
$client = new GuzzleHttp\Client([
|
||||
'timeout' => 10,
|
||||
'verify' => false // Disable SSL verification for testing
|
||||
]);
|
||||
|
||||
try {
|
||||
$response = $client->get($stashSource['api_url'], [
|
||||
'headers' => [
|
||||
'User-Agent' => 'MediaCollector/1.0',
|
||||
'ApiKey' => $stashSource['api_key'] ?? ''
|
||||
]
|
||||
]);
|
||||
|
||||
echo "✅ Stash server is reachable\n";
|
||||
echo " Status: {$response->getStatusCode()}\n";
|
||||
echo " Response received successfully\n";
|
||||
|
||||
// Test GraphQL endpoint
|
||||
echo "\n🔍 Testing GraphQL endpoint...\n";
|
||||
|
||||
$query = '
|
||||
query FindScenes($filter: FindFilterType) {
|
||||
findScenes(filter: $filter) {
|
||||
count
|
||||
scenes {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
';
|
||||
|
||||
$variables = [
|
||||
'filter' => [
|
||||
'per_page' => 1,
|
||||
'page' => 1,
|
||||
'sort' => 'created_at',
|
||||
'direction' => 'DESC'
|
||||
]
|
||||
];
|
||||
|
||||
$response = $client->post("{$stashSource['api_url']}/graphql", [
|
||||
'json' => [
|
||||
'query' => $query,
|
||||
'variables' => $variables
|
||||
],
|
||||
'headers' => [
|
||||
'User-Agent' => 'MediaCollector/1.0',
|
||||
'ApiKey' => $stashSource['api_key'] ?? '',
|
||||
'Content-Type' => 'application/json'
|
||||
],
|
||||
'timeout' => 15
|
||||
]);
|
||||
|
||||
$data = json_decode($response->getBody(), true);
|
||||
|
||||
if (isset($data['data']['findScenes'])) {
|
||||
$count = $data['data']['findScenes']['count'];
|
||||
echo "✅ GraphQL endpoint working\n";
|
||||
echo " Total scenes in Stash: {$count}\n";
|
||||
|
||||
if ($count > 0) {
|
||||
$firstScene = $data['data']['findScenes']['scenes'][0] ?? null;
|
||||
if ($firstScene) {
|
||||
echo " First scene: {$firstScene['title']} (ID: {$firstScene['id']})\n";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
echo "❌ GraphQL response format unexpected\n";
|
||||
echo " Response: " . json_encode($data) . "\n";
|
||||
}
|
||||
|
||||
} catch (GuzzleHttp\Exception\RequestException $e) {
|
||||
echo "❌ Failed to connect to Stash server\n";
|
||||
echo " Error: " . $e->getMessage() . "\n";
|
||||
|
||||
if ($e->hasResponse()) {
|
||||
$response = $e->getResponse();
|
||||
echo " Status: {$response->getStatusCode()}\n";
|
||||
echo " Response: " . $response->getBody() . "\n";
|
||||
}
|
||||
|
||||
echo "\n💡 Troubleshooting tips:\n";
|
||||
echo " 1. Check if Stash server is running\n";
|
||||
echo " 2. Verify the API URL is correct\n";
|
||||
echo " 3. Check if API key is required and correct\n";
|
||||
echo " 4. Ensure the server is accessible from this machine\n";
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
107
test_xbvr.php
107
test_xbvr.php
@@ -1,107 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Load helper functions
|
||||
require_once __DIR__ . '/app/helpers.php';
|
||||
|
||||
// Load environment variables
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||||
$dotenv->load();
|
||||
|
||||
// Load database configuration
|
||||
$dbConfig = require __DIR__ . '/config/database.php';
|
||||
|
||||
// Set up database connection
|
||||
try {
|
||||
\App\Database\Database::setConfig($dbConfig);
|
||||
$pdo = \App\Database\Database::getInstance();
|
||||
echo "Database connection established successfully.\n";
|
||||
} catch (Exception $e) {
|
||||
die('Database connection failed: ' . $e->getMessage() . "\n");
|
||||
}
|
||||
|
||||
// Test XBVR connectivity
|
||||
echo "\nTesting XBVR source connectivity...\n";
|
||||
|
||||
// Find XBVR source
|
||||
$stmt = $pdo->prepare("SELECT * FROM sources WHERE name = 'xbvr' AND is_active = 1 LIMIT 1");
|
||||
$stmt->execute();
|
||||
$xbvrSource = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$xbvrSource) {
|
||||
echo "No active XBVR source found. Please create an XBVR source first.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "Found XBVR source: {$xbvrSource['display_name']} ({$xbvrSource['api_url']})\n";
|
||||
|
||||
// Test basic connectivity
|
||||
$baseUrl = rtrim($xbvrSource['api_url'], '/');
|
||||
|
||||
try {
|
||||
$httpClient = new GuzzleHttp\Client([
|
||||
'timeout' => 10,
|
||||
'headers' => [
|
||||
'User-Agent' => 'MediaCollector/1.0'
|
||||
],
|
||||
'verify' => false
|
||||
]);
|
||||
|
||||
// Try different XBVR API endpoints
|
||||
$endpoints = [
|
||||
"{$baseUrl}/deovr"
|
||||
];
|
||||
|
||||
foreach ($endpoints as $endpoint) {
|
||||
echo "\nTrying endpoint: {$endpoint}\n";
|
||||
|
||||
try {
|
||||
$response = $httpClient->get($endpoint, [
|
||||
'timeout' => 10,
|
||||
'connect_timeout' => 5
|
||||
]);
|
||||
|
||||
echo "✓ Status: {$response->getStatusCode()}\n";
|
||||
echo "Content-Type: " . $response->getHeaderLine('content-type') . "\n";
|
||||
|
||||
$data = json_decode($response->getBody(), true);
|
||||
echo "Response keys: " . implode(', ', array_keys($data)) . "\n";
|
||||
|
||||
// XBVR DeoVR API response structure
|
||||
$scenes = null;
|
||||
|
||||
// Try different DeoVR response structures
|
||||
if (isset($data['scenes'])) {
|
||||
$scenes = $data['scenes'];
|
||||
} elseif (isset($data['content'])) {
|
||||
$scenes = $data['content'];
|
||||
} elseif (isset($data['videos'])) {
|
||||
$scenes = $data['videos'];
|
||||
} elseif (isset($data[0]) && is_array($data[0])) {
|
||||
// Array of scenes directly
|
||||
$scenes = $data;
|
||||
}
|
||||
|
||||
if ($scenes !== null) {
|
||||
echo "✓ Found " . count($scenes) . " scenes in XBVR DeoVR response\n";
|
||||
if (count($scenes) > 0) {
|
||||
echo "Sample scene keys: " . implode(', ', array_keys($scenes[0])) . "\n";
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
echo "✗ No scenes array found in response. Response keys: " . implode(', ', array_keys($data)) . "\n";
|
||||
echo "Sample response structure: " . json_encode(array_slice($data, 0, 2), JSON_PRETTY_PRINT) . "\n";
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "✗ Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "Error testing XBVR connectivity: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "\nXBVR connectivity test complete.\n";
|
||||
@@ -1,64 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Load helper functions
|
||||
require_once __DIR__ . '/app/helpers.php';
|
||||
|
||||
// Load environment variables
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||||
$dotenv->load();
|
||||
|
||||
// Load database configuration
|
||||
$dbConfig = require __DIR__ . '/config/database.php';
|
||||
|
||||
// Set up database connection
|
||||
try {
|
||||
\App\Database\Database::setConfig($dbConfig);
|
||||
$pdo = \App\Database\Database::getInstance();
|
||||
echo "Database connection established successfully.\n";
|
||||
} catch (Exception $e) {
|
||||
die('Database connection failed: ' . $e->getMessage() . "\n");
|
||||
}
|
||||
|
||||
// Find XBVR source
|
||||
$stmt = $pdo->prepare("SELECT * FROM sources WHERE name = 'xbvr' AND is_active = 1 LIMIT 1");
|
||||
$stmt->execute();
|
||||
$xbvrSource = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$xbvrSource) {
|
||||
echo "No active XBVR source found. Please create an XBVR source first.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "Found XBVR source: {$xbvrSource['display_name']} ({$xbvrSource['api_url']})\n";
|
||||
|
||||
// Convert source array to expected format for sync services
|
||||
$sourceData = [
|
||||
'id' => $xbvrSource['id'],
|
||||
'name' => $xbvrSource['name'],
|
||||
'display_name' => $xbvrSource['display_name'],
|
||||
'api_url' => $xbvrSource['api_url'],
|
||||
'api_key' => $xbvrSource['api_key'],
|
||||
'config' => $xbvrSource['config'],
|
||||
'is_active' => $xbvrSource['is_active'],
|
||||
'last_sync_at' => $xbvrSource['last_sync_at']
|
||||
];
|
||||
|
||||
// Test XBVR sync service
|
||||
echo "\nTesting XBVR sync service...\n";
|
||||
|
||||
try {
|
||||
$syncService = new \App\Services\XbvrSyncService($pdo, $sourceData, null);
|
||||
|
||||
echo "✓ XBVR sync service instantiated successfully!\n";
|
||||
echo "✓ No PHP errors during instantiation - the PDO fix worked!\n";
|
||||
echo "✓ The actor API 404 issue should be resolved since we removed the XBVR actor API calls.\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "✗ Error testing XBVR sync service: " . $e->getMessage() . "\n";
|
||||
echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "\nXBVR sync service test complete.\n";
|
||||
@@ -1,31 +0,0 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import path from 'path';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
root: 'public',
|
||||
base: '/build/',
|
||||
publicDir: 'public',
|
||||
build: {
|
||||
outDir: '../public/build',
|
||||
emptyOutDir: true,
|
||||
manifest: true,
|
||||
rollupOptions: {
|
||||
input: 'resources/js/app.js',
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './resources/js'),
|
||||
'~': path.resolve(__dirname, './node_modules'),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 3000,
|
||||
strictPort: true,
|
||||
hmr: {
|
||||
host: 'localhost',
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user