mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
Stuff i guess ?
This commit is contained in:
@@ -40,6 +40,424 @@ class AdminController extends AdminBaseController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Media Management
|
||||
*/
|
||||
|
||||
// Movies Management
|
||||
public function movies(Request $request, Response $response, $args)
|
||||
{
|
||||
$movieModel = new \App\Models\Movie($this->pdo);
|
||||
|
||||
// Get query parameters with defaults
|
||||
$page = max(1, (int)($request->getQueryParams()['page'] ?? 1));
|
||||
$search = trim($request->getQueryParams()['search'] ?? '');
|
||||
$genre = trim($request->getQueryParams()['genre'] ?? '');
|
||||
$director = trim($request->getQueryParams()['director'] ?? '');
|
||||
$sort = trim($request->getQueryParams()['sort'] ?? 'title_asc');
|
||||
$perPage = 20;
|
||||
|
||||
// Prepare filters for the view
|
||||
$filters = [
|
||||
'search' => $search,
|
||||
'genre' => $genre,
|
||||
'director' => $director,
|
||||
'sort' => $sort
|
||||
];
|
||||
|
||||
// Get paginated and filtered movies
|
||||
$movies = $movieModel->getPaginated(
|
||||
$this->pdo,
|
||||
$page,
|
||||
$perPage,
|
||||
$search,
|
||||
$genre ? [$genre] : [],
|
||||
$sort
|
||||
);
|
||||
|
||||
// Get available genres and directors for filters
|
||||
$genres = $movieModel->getGenres($this->pdo);
|
||||
$directors = $movieModel->getDirectors($this->pdo);
|
||||
|
||||
// Calculate pagination data
|
||||
$totalMovies = $movieModel->getTotalCount(
|
||||
$this->pdo,
|
||||
$search,
|
||||
$genre ? [$genre] : []
|
||||
);
|
||||
|
||||
$totalPages = max(1, ceil($totalMovies / $perPage));
|
||||
$currentPage = min($page, $totalPages);
|
||||
|
||||
// Get flash messages if any
|
||||
// $successMessages = $this->container->get('flash')->getMessage('success');
|
||||
|
||||
return $this->render($response, 'admin/movies/index.twig', [
|
||||
'title' => 'Manage Movies',
|
||||
'movies' => $movies,
|
||||
'genres' => $genres,
|
||||
'directors' => $directors,
|
||||
'filters' => $filters,
|
||||
'pagination' => [
|
||||
'current' => $currentPage,
|
||||
'total' => $totalPages,
|
||||
'per_page' => $perPage,
|
||||
'total_items' => $totalMovies,
|
||||
'from' => (($currentPage - 1) * $perPage) + 1,
|
||||
'to' => min($currentPage * $perPage, $totalMovies)
|
||||
]
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
public function editMovie(Request $request, Response $response, $args)
|
||||
{
|
||||
$id = $args['id'] ?? null;
|
||||
$movieModel = new \App\Models\Movie($this->pdo);
|
||||
|
||||
if ($request->getMethod() === 'POST') {
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if ($id) {
|
||||
// Update existing movie
|
||||
$movieModel->update($id, $data);
|
||||
//$this->flash->addMessage('success', 'Movie updated successfully');
|
||||
} else {
|
||||
// Create new movie
|
||||
$id = $movieModel->create($data);
|
||||
// $this->flash->addMessage('success', 'Movie created successfully');
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', '/admin/movies/' . $id . '/edit')
|
||||
->withStatus(302);
|
||||
}
|
||||
|
||||
$movie = $id ? $movieModel->find($id) : null;
|
||||
|
||||
return $this->render($response, 'admin/movies/edit.twig', [
|
||||
'title' => $id ? 'Edit Movie' : 'Add New Movie',
|
||||
'movie' => $movie
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteMovie(Request $request, Response $response, $args)
|
||||
{
|
||||
$id = $args['id'];
|
||||
$movieModel = new \App\Models\Movie($this->pdo);
|
||||
$movieModel->delete($id);
|
||||
|
||||
$this->flash->addMessage('success', 'Movie deleted successfully');
|
||||
return $response->withHeader('Location', '/admin/movies')
|
||||
->withStatus(302);
|
||||
}
|
||||
|
||||
// Games Management
|
||||
public function games(Request $request, Response $response, $args)
|
||||
{
|
||||
$gameModel = new \App\Models\Game($this->pdo);
|
||||
|
||||
// Get query parameters
|
||||
$page = (int)($request->getQueryParams()['page'] ?? 1);
|
||||
$search = $request->getQueryParams()['search'] ?? '';
|
||||
$platform = $request->getQueryParams()['platform'] ?? '';
|
||||
$genre = $request->getQueryParams()['genre'] ?? '';
|
||||
$isInstalled = $request->getQueryParams()['installed'] ?? '';
|
||||
$isFavorite = $request->getQueryParams()['favorite'] ?? '';
|
||||
$sort = $request->getQueryParams()['sort'] ?? 'title_asc';
|
||||
$perPage = 20; // Items per page
|
||||
|
||||
// Prepare filters
|
||||
$filters = [
|
||||
'search' => $search,
|
||||
'platform' => $platform,
|
||||
'genre' => $genre,
|
||||
'is_installed' => $isInstalled,
|
||||
'is_favorite' => $isFavorite,
|
||||
'sort' => $sort
|
||||
];
|
||||
|
||||
// Get paginated and filtered games
|
||||
$result = $gameModel->getGroupedGamesWithPagination(
|
||||
$this->pdo,
|
||||
$page,
|
||||
$perPage,
|
||||
$search,
|
||||
$genre ? [$genre] : [],
|
||||
$platform ? [$platform] : []
|
||||
);
|
||||
|
||||
// Get available platforms and genres for filters
|
||||
$platforms = $gameModel->getPlatforms();
|
||||
$genres = $gameModel->getGenres();
|
||||
|
||||
// Calculate pagination data
|
||||
$totalGames = $gameModel->getTotalCount(
|
||||
$this->pdo,
|
||||
$search,
|
||||
$genre ? [$genre] : [],
|
||||
$platform ? [$platform] : []
|
||||
);
|
||||
|
||||
$totalPages = ceil($totalGames / $perPage);
|
||||
|
||||
return $this->render($response, 'admin/games/index.twig', [
|
||||
'title' => 'Manage Games',
|
||||
'games' => $result,
|
||||
'platforms' => $platforms,
|
||||
'genres' => $genres,
|
||||
'filters' => $filters,
|
||||
'pagination' => [
|
||||
'current' => $page,
|
||||
'total' => $totalPages,
|
||||
'per_page' => $perPage,
|
||||
'total_items' => $totalGames
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function editGame(Request $request, Response $response, $args)
|
||||
{
|
||||
$id = $args['id'] ?? null;
|
||||
$gameModel = new \App\Models\Game($this->pdo);
|
||||
|
||||
if ($request->getMethod() === 'POST') {
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if ($id) {
|
||||
$gameModel->update($id, $data);
|
||||
} else {
|
||||
$id = $gameModel->create($data);
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', '/admin/games/' . $id . '/edit')
|
||||
->withStatus(302);
|
||||
}
|
||||
|
||||
$game = $id ? $gameModel->find($id) : null;
|
||||
|
||||
return $this->render($response, 'admin/games/edit.twig', [
|
||||
'title' => $id ? 'Edit Game' : 'Add New Game',
|
||||
'game' => $game
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteGame(Request $request, Response $response, $args)
|
||||
{
|
||||
$id = $args['id'];
|
||||
$gameModel = new \App\Models\Game($this->pdo);
|
||||
$gameModel->delete($id);
|
||||
|
||||
return $response->withHeader('Location', '/admin/games')
|
||||
->withStatus(302);
|
||||
}
|
||||
|
||||
// TV Shows Management
|
||||
public function shows(Request $request, Response $response, $args)
|
||||
{
|
||||
$showModel = new \App\Models\TvShow($this->pdo);
|
||||
|
||||
// Get query parameters with defaults
|
||||
$page = max(1, (int)($request->getQueryParams()['page'] ?? 1));
|
||||
$search = trim($request->getQueryParams()['search'] ?? '');
|
||||
$genre = trim($request->getQueryParams()['genre'] ?? '');
|
||||
$network = trim($request->getQueryParams()['network'] ?? '');
|
||||
$status = trim($request->getQueryParams()['status'] ?? '');
|
||||
$sort = trim($request->getQueryParams()['sort'] ?? 'name_asc');
|
||||
$perPage = 20;
|
||||
|
||||
// Prepare filters for the view
|
||||
$filters = [
|
||||
'search' => $search,
|
||||
'genre' => $genre,
|
||||
'network' => $network,
|
||||
'status' => $status,
|
||||
'sort' => $sort
|
||||
];
|
||||
|
||||
// Get paginated and filtered shows
|
||||
$shows = $showModel->getPaginated(
|
||||
$this->pdo,
|
||||
$page,
|
||||
$perPage,
|
||||
$search,
|
||||
$genre ? [$genre] : [],
|
||||
$network ? [$network] : [],
|
||||
$status ? [$status] : [],
|
||||
$sort
|
||||
);
|
||||
|
||||
// Get available filters
|
||||
$genres = $showModel->getGenres($this->pdo);
|
||||
//$networks = $showModel->getNetworks($this->pdo);
|
||||
$statuses = ['Returning Series', 'Ended', 'Canceled', 'In Production'];
|
||||
|
||||
// Calculate pagination data
|
||||
$totalShows = $showModel->getTotalCount(
|
||||
$this->pdo,
|
||||
$search,
|
||||
$genre ? [$genre] : [],
|
||||
$network ? [$network] : [],
|
||||
$status ? [$status] : []
|
||||
);
|
||||
|
||||
$totalPages = max(1, ceil($totalShows / $perPage));
|
||||
$currentPage = min($page, $totalPages);
|
||||
|
||||
return $this->render($response, 'admin/shows/index.twig', [
|
||||
'title' => 'Manage TV Shows',
|
||||
'shows' => $shows,
|
||||
'genres' => $genres,
|
||||
//'networks' => $networks,
|
||||
'statuses' => $statuses,
|
||||
'filters' => $filters,
|
||||
'pagination' => [
|
||||
'current' => $currentPage,
|
||||
'total' => $totalPages,
|
||||
'per_page' => $perPage,
|
||||
'total_items' => $totalShows,
|
||||
'from' => (($currentPage - 1) * $perPage) + 1,
|
||||
'to' => min($currentPage * $perPage, $totalShows)
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function editShow(Request $request, Response $response, $args)
|
||||
{
|
||||
$id = $args['id'] ?? null;
|
||||
$showModel = new \App\Models\TvShow($this->pdo);
|
||||
|
||||
if ($request->getMethod() === 'POST') {
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if ($id) {
|
||||
$showModel->update($id, $data);
|
||||
} else {
|
||||
$id = $showModel->create($data);
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', '/admin/shows/' . $id . '/edit')
|
||||
->withStatus(302);
|
||||
}
|
||||
|
||||
$show = $id ? $showModel->find($id) : null;
|
||||
|
||||
return $this->render($response, 'admin/shows/edit.twig', [
|
||||
'title' => $id ? 'Edit TV Show' : 'Add New TV Show',
|
||||
'show' => $show
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteShow(Request $request, Response $response, $args)
|
||||
{
|
||||
$id = $args['id'];
|
||||
$showModel = new \App\Models\TvShow($this->pdo);
|
||||
$showModel->delete($id);
|
||||
|
||||
return $response->withHeader('Location', '/admin/shows')
|
||||
->withStatus(302);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of adult videos with pagination and filters
|
||||
*/
|
||||
public function adultVideos(Request $request, Response $response, $args)
|
||||
{
|
||||
$adultVideoModel = new \App\Models\AdultVideo($this->pdo);
|
||||
|
||||
// Get query parameters with defaults
|
||||
$page = max(1, (int)($request->getQueryParams()['page'] ?? 1));
|
||||
$search = trim($request->getQueryParams()['search'] ?? '');
|
||||
$genre = trim($request->getQueryParams()['genre'] ?? '');
|
||||
$director = trim($request->getQueryParams()['director'] ?? '');
|
||||
$sort = trim($request->getQueryParams()['sort'] ?? 'newest');
|
||||
$perPage = 20;
|
||||
|
||||
// Prepare filters for the view
|
||||
$filters = [
|
||||
'search' => $search,
|
||||
'genre' => $genre,
|
||||
'director' => $director,
|
||||
'sort' => $sort
|
||||
];
|
||||
|
||||
// Get available filters
|
||||
$genres = $adultVideoModel::getAvailableGenres($this->pdo);
|
||||
$directors = $adultVideoModel::getAvailableDirectors($this->pdo);
|
||||
|
||||
// Get paginated and filtered adult videos
|
||||
$videos = $adultVideoModel::getAllWithPagination(
|
||||
$this->pdo,
|
||||
$page,
|
||||
$perPage,
|
||||
$search,
|
||||
$genre ? [$genre] : [],
|
||||
$director ? [$director] : []
|
||||
);
|
||||
|
||||
// Get total count for pagination
|
||||
$totalVideos = $adultVideoModel::getTotalCount(
|
||||
$this->pdo,
|
||||
$search,
|
||||
$genre ? [$genre] : [],
|
||||
$director ? [$director] : []
|
||||
);
|
||||
|
||||
$totalPages = max(1, ceil($totalVideos / $perPage));
|
||||
$currentPage = min($page, $totalPages);
|
||||
|
||||
return $this->render($response, 'admin/adult/index.twig', [
|
||||
'title' => 'Manage Adult Videos',
|
||||
'videos' => $videos,
|
||||
'genres' => $genres,
|
||||
'directors' => $directors,
|
||||
'filters' => $filters,
|
||||
'pagination' => [
|
||||
'current' => $currentPage,
|
||||
'total' => $totalPages,
|
||||
'per_page' => $perPage,
|
||||
'total_items' => $totalVideos,
|
||||
'from' => (($currentPage - 1) * $perPage) + 1,
|
||||
'to' => min($currentPage * $perPage, $totalVideos)
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function editAdultVideo(Request $request, Response $response, $args)
|
||||
{
|
||||
$id = $args['id'] ?? null;
|
||||
$adultModel = new \App\Models\AdultVideo($this->pdo);
|
||||
|
||||
if ($request->getMethod() === 'POST') {
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if ($id) {
|
||||
$adultModel->update($id, $data);
|
||||
} else {
|
||||
$id = $adultModel->create($data);
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', '/admin/adult/' . $id . '/edit')
|
||||
->withStatus(302);
|
||||
}
|
||||
|
||||
$video = $id ? $adultModel->find($id) : null;
|
||||
|
||||
return $this->render($response, 'admin/adult/edit.twig', [
|
||||
'title' => $id ? 'Edit Adult Video' : 'Add New Adult Video',
|
||||
'video' => $video
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteAdultVideo(Request $request, Response $response, $args)
|
||||
{
|
||||
$id = $args['id'];
|
||||
$adultModel = new \App\Models\AdultVideo($this->pdo);
|
||||
$adultModel->delete($id);
|
||||
|
||||
return $response->withHeader('Location', '/admin/adult')
|
||||
->withStatus(302);
|
||||
}
|
||||
|
||||
public function syncSource(Request $request, Response $response, $args)
|
||||
{
|
||||
$sourceId = $args['id'];
|
||||
@@ -202,4 +620,119 @@ class AdminController extends AdminBaseController
|
||||
'message' => 'Sync process starting in background'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get actors for a specific adult video
|
||||
*/
|
||||
public function getAdultVideoActors(Request $request, Response $response, $args)
|
||||
{
|
||||
$adultVideo = new \App\Models\AdultVideo($this->pdo);
|
||||
$video = $adultVideo->find($args['id']);
|
||||
|
||||
if (!$video) {
|
||||
$response->getBody()->write(json_encode(['error' => 'Video not found']));
|
||||
return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$actors = $adultVideo->actors($args['id']);
|
||||
$response->getBody()->write(json_encode(['data' => $actors]));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an actor to an adult video
|
||||
*/
|
||||
public function addActorToAdultVideo(Request $request, Response $response, $args)
|
||||
{
|
||||
$contentType = $request->getHeaderLine('Content-Type');
|
||||
|
||||
if (strstr($contentType, 'application/json')) {
|
||||
$data = json_decode((string)$request->getBody(), true);
|
||||
} else {
|
||||
$data = $request->getParsedBody();
|
||||
}
|
||||
|
||||
$actorId = $data['actor_id'] ?? null;
|
||||
|
||||
if (!$actorId) {
|
||||
$response->getBody()->write(json_encode(['error' => 'Actor ID is required']));
|
||||
return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$adultVideo = new \App\Models\AdultVideo($this->pdo);
|
||||
$video = $adultVideo->find($args['id']);
|
||||
|
||||
if (!$video) {
|
||||
$response->getBody()->write(json_encode(['error' => 'Video not found']));
|
||||
return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$success = $adultVideo->addActor($actorId);
|
||||
|
||||
if ($success) {
|
||||
$adultVideo->updateCastField();
|
||||
$response->getBody()->write(json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Actor added successfully'
|
||||
]));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(['error' => 'Failed to add actor']));
|
||||
return $response->withStatus(500)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an actor from an adult video
|
||||
*/
|
||||
public function removeActorFromAdultVideo(Request $request, Response $response, $args)
|
||||
{
|
||||
$actorId = $args['actorId'] ?? null;
|
||||
|
||||
if (!$actorId) {
|
||||
$response->getBody()->write(json_encode(['error' => 'Actor ID is required']));
|
||||
return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$adultVideo = new \App\Models\AdultVideo($this->pdo);
|
||||
$video = $adultVideo->find($args['id']);
|
||||
|
||||
if (!$video) {
|
||||
$response->getBody()->write(json_encode(['error' => 'Video not found']));
|
||||
return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$success = $adultVideo->removeActor($actorId);
|
||||
|
||||
if ($success) {
|
||||
$adultVideo->updateCastField();
|
||||
$response->getBody()->write(json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Actor removed successfully'
|
||||
]));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(['error' => 'Failed to remove actor']));
|
||||
return $response->withStatus(500)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Search actors by name
|
||||
*/
|
||||
public function searchActors(Request $request, Response $response, $args)
|
||||
{
|
||||
$query = $request->getQueryParams()['q'] ?? '';
|
||||
|
||||
if (empty($query)) {
|
||||
$response->getBody()->write(json_encode(['data' => []]));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$adultVideo = new \App\Models\AdultVideo($this->pdo);
|
||||
$actors = $adultVideo->searchActors($this->pdo, $query);
|
||||
|
||||
$response->getBody()->write(json_encode(['data' => $actors]));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,11 +41,12 @@ class AdultController extends Controller
|
||||
}
|
||||
$directors = array_filter($directors);
|
||||
|
||||
// Get view mode
|
||||
// Get view mode and sort
|
||||
$viewMode = $queryParams['view'] ?? 'grid'; // grid, list, covers
|
||||
$sort = $queryParams['sort'] ?? 'recent';
|
||||
|
||||
// Get adult videos with pagination and filters
|
||||
$adultVideos = AdultVideo::getAllWithPagination($this->pdo, $page, $perPage, $search, $genres, $directors);
|
||||
// Get adult videos with pagination, filters, and sorting
|
||||
$adultVideos = AdultVideo::getAllWithPagination($this->pdo, $page, $perPage, $search, $genres, $directors, $sort);
|
||||
|
||||
// Process metadata to extract local image paths for template compatibility
|
||||
foreach ($adultVideos as &$video) {
|
||||
@@ -94,6 +95,21 @@ class AdultController extends Controller
|
||||
'search' => $search,
|
||||
'view_mode' => $viewMode,
|
||||
'view_modes' => ['grid', 'list', 'covers'],
|
||||
'sort' => $sort,
|
||||
'sort_options' => [
|
||||
'recent' => 'Most Recent',
|
||||
'oldest' => 'Oldest First',
|
||||
'title_asc' => 'Title (A-Z)',
|
||||
'title_desc' => 'Title (Z-A)',
|
||||
'year_asc' => 'Release Year (Oldest First)',
|
||||
'year_desc' => 'Release Year (Newest First)',
|
||||
'rating_desc' => 'Highest Rated',
|
||||
'rating_asc' => 'Lowest Rated',
|
||||
'views_desc' => 'Most Viewed',
|
||||
'views_asc' => 'Least Viewed',
|
||||
'runtime_desc' => 'Longest Runtime',
|
||||
'runtime_asc' => 'Shortest Runtime',
|
||||
],
|
||||
'filters' => [
|
||||
'genres' => $genres,
|
||||
'directors' => $directors
|
||||
|
||||
51
app/Controllers/Api/AuthController.php
Normal file
51
app/Controllers/Api/AuthController.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use App\Controllers\Controller;
|
||||
use App\Services\AuthService;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
private AuthService $authService;
|
||||
|
||||
public function __construct(AuthService $authService)
|
||||
{
|
||||
$this->authService = $authService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is authenticated (API endpoint)
|
||||
*/
|
||||
public function checkAuth(Request $request, Response $response, $args)
|
||||
{
|
||||
try {
|
||||
if (!$this->authService->isLoggedIn()) {
|
||||
return $this->jsonResponse($response->withStatus(401), [
|
||||
'error' => '401 Forbidden'
|
||||
]);
|
||||
}
|
||||
|
||||
$user = $this->authService->getCurrentUser();
|
||||
if (!$user) {
|
||||
return $this->jsonResponse($response->withStatus(401), [
|
||||
'error' => '401 Forbidden'
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->jsonResponse($response, [
|
||||
'id' => $user['id'],
|
||||
'username' => $user['username'],
|
||||
'email' => $user['email'],
|
||||
'is_admin' => $this->authService->isAdmin()
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->jsonResponse($response->withStatus(500), [
|
||||
'error' => 'Authentication check failed'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
569
app/Controllers/Api/PlayniteController.php
Normal file
569
app/Controllers/Api/PlayniteController.php
Normal file
@@ -0,0 +1,569 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use App\Controllers\Controller;
|
||||
use App\Models\Game;
|
||||
use App\Services\PlayniteImportService;
|
||||
|
||||
class PlayniteController extends Controller
|
||||
{
|
||||
|
||||
private \PDO $pdo;
|
||||
private PlayniteImportService $importService;
|
||||
|
||||
public function __construct(\PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
$this->importService = new PlayniteImportService($pdo);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/playnite/insert",
|
||||
* summary="Insert or update games from Playnite",
|
||||
* tags={"Playnite"},
|
||||
* operationId="insertGames",
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* required={"games"},
|
||||
* @OA\Property(
|
||||
* property="games",
|
||||
* type="array",
|
||||
* @OA\Items(type="object")
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="update_existing",
|
||||
* type="boolean",
|
||||
* default=true,
|
||||
* description="Whether to update existing games"
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Games successfully imported/updated",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean"),
|
||||
* @OA\Property(property="result", type="object")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=400,
|
||||
* description="Invalid input"
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=500,
|
||||
* description="Server error"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param array $args
|
||||
* @return Response
|
||||
*/
|
||||
public function insertGames(Request $request, Response $response, $args)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if (!isset($data['games']) || !is_array($data['games'])) {
|
||||
return $this->jsonResponse($response->withStatus(400), [
|
||||
'error' => 'Games data is required'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$importResult = $this->importService->importGames($data['games'], true);
|
||||
|
||||
return $this->jsonResponse($response, [
|
||||
'success' => true,
|
||||
'result' => $importResult
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->jsonResponse($response->withStatus(500), [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/playnite/update",
|
||||
* summary="Update existing games from Playnite",
|
||||
* tags={"Playnite"},
|
||||
* operationId="updateGames",
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* required={"games"},
|
||||
* @OA\Property(
|
||||
* property="games",
|
||||
* type="array",
|
||||
* @OA\Items(type="object")
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Games successfully updated",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean"),
|
||||
* @OA\Property(property="result", type="object")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=400,
|
||||
* description="Invalid input"
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=500,
|
||||
* description="Server error"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param array $args
|
||||
* @return Response
|
||||
*/
|
||||
public function updateGames(Request $request, Response $response, $args)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if (!isset($data['games']) || !is_array($data['games'])) {
|
||||
return $this->jsonResponse($response->withStatus(400), [
|
||||
'error' => 'Games data is required'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$importResult = $this->importService->importGames($data['games'], true);
|
||||
|
||||
return $this->jsonResponse($response, [
|
||||
'success' => true,
|
||||
'result' => $importResult
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->jsonResponse($response->withStatus(500), [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/playnite/delete",
|
||||
* summary="Delete games from Playnite",
|
||||
* tags={"Playnite"},
|
||||
* operationId="deleteGames",
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* required={"games"},
|
||||
* @OA\Property(
|
||||
* property="games",
|
||||
* type="array",
|
||||
* @OA\Items(type="object")
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Games successfully deleted",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean"),
|
||||
* @OA\Property(property="result", type="object",
|
||||
* @OA\Property(property="deleted", type="integer"),
|
||||
* @OA\Property(property="errors", type="array", @OA\Items(type="string"))
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=400,
|
||||
* description="Invalid input"
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=500,
|
||||
* description="Server error"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param array $args
|
||||
* @return Response
|
||||
*/
|
||||
public function deleteGames(Request $request, Response $response, $args)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if (!isset($data['games']) || !is_array($data['games'])) {
|
||||
return $this->jsonResponse($response->withStatus(400), [
|
||||
'error' => 'Games data is required'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$results = [
|
||||
'deleted' => 0,
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
foreach ($data['games'] as $gameData) {
|
||||
try {
|
||||
// Find the game by platform_game_id and source_id
|
||||
$existingGame = $this->findExistingGame($gameData);
|
||||
|
||||
if ($existingGame) {
|
||||
$this->deleteGame($existingGame['id']);
|
||||
$results['deleted']++;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$results['errors'][] = "Failed to delete {$gameData['title']}: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->jsonResponse($response, [
|
||||
'success' => true,
|
||||
'result' => $results
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->jsonResponse($response->withStatus(500), [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/playnite/upload-images",
|
||||
* summary="Upload game images from Playnite",
|
||||
* tags={"Playnite"},
|
||||
* operationId="uploadImages",
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* oneOf={
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="name", type="string"),
|
||||
* @OA\Property(property="cover", type="string", format="byte"),
|
||||
* @OA\Property(property="icon", type="string", format="byte"),
|
||||
* @OA\Property(property="background", type="string", format="byte")
|
||||
* ),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(
|
||||
* property="games",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* @OA\Property(property="name", type="string"),
|
||||
* @OA\Property(property="cover", type="string", format="byte"),
|
||||
* @OA\Property(property="icon", type="string", format="byte"),
|
||||
* @OA\Property(property="background", type="string", format="byte")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Images successfully uploaded",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean"),
|
||||
* @OA\Property(property="result", type="object",
|
||||
* @OA\Property(property="uploaded", type="integer"),
|
||||
* @OA\Property(property="errors", type="array", @OA\Items(type="string"))
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=500,
|
||||
* description="Server error"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param array $args
|
||||
* @return Response
|
||||
*/
|
||||
public function uploadImages(Request $request, Response $response, $args)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
try {
|
||||
$results = [
|
||||
'uploaded' => 0,
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
// Handle image uploads based on the format expected by the plugin
|
||||
if (isset($data['name']) && isset($data['cover'])) {
|
||||
// Single game image upload
|
||||
$result = $this->handleImageUpload($data);
|
||||
if ($result) {
|
||||
$results['uploaded']++;
|
||||
}
|
||||
} elseif (isset($data['games']) && is_array($data['games'])) {
|
||||
// Multiple games with images
|
||||
foreach ($data['games'] as $gameData) {
|
||||
$result = $this->handleImageUpload($gameData);
|
||||
if ($result) {
|
||||
$results['uploaded']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->jsonResponse($response, [
|
||||
'success' => true,
|
||||
'result' => $results
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->jsonResponse($response->withStatus(500), [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle individual image upload
|
||||
*/
|
||||
private function handleImageUpload(array $gameData): bool
|
||||
{
|
||||
try {
|
||||
// For now, we'll just validate the data format
|
||||
// In a real implementation, you might want to save the images to disk
|
||||
// and update the game records with the image paths
|
||||
|
||||
$name = $gameData['name'] ?? '';
|
||||
$cover = $gameData['cover'] ?? '';
|
||||
$icon = $gameData['icon'] ?? '';
|
||||
$background = $gameData['background'] ?? '';
|
||||
|
||||
// Validate base64 images
|
||||
if ($cover && !preg_match('/^data:image\/(jpeg|png|gif|webp);base64,/', $cover)) {
|
||||
throw new \Exception("Invalid cover image format");
|
||||
}
|
||||
|
||||
// Here you would typically:
|
||||
// 1. Decode base64 images
|
||||
// 2. Save them to the filesystem
|
||||
// 3. Update the game record with the image paths
|
||||
|
||||
return true;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
error_log("Image upload failed for game {$name}: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find existing game by platform_game_id and source_id
|
||||
*/
|
||||
private function findExistingGame(array $gameData): ?array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT id, title, platform_game_id, source_id
|
||||
FROM games
|
||||
WHERE platform_game_id = :platform_game_id AND source_id = :source_id
|
||||
");
|
||||
$stmt->execute([
|
||||
'platform_game_id' => $gameData['platform_game_id'],
|
||||
'source_id' => $gameData['source_id']
|
||||
]);
|
||||
|
||||
return $stmt->fetch(\PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete game
|
||||
*/
|
||||
private function deleteGame(int $gameId): void
|
||||
{
|
||||
$stmt = $this->pdo->prepare("DELETE FROM games WHERE id = :id");
|
||||
$stmt->execute(['id' => $gameId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform Playnite game data to internal format
|
||||
*/
|
||||
private function transformPlayniteGame(array $game): array
|
||||
{
|
||||
// Find or create source
|
||||
$source = $this->findOrCreateSource($game);
|
||||
|
||||
// Transform the game data similar to PlayniteImportService
|
||||
return [
|
||||
'title' => $game['Name'] ?? $game['name'] ?? '',
|
||||
'game_key' => $this->generateGameKey($game['Name'] ?? $game['name'] ?? ''),
|
||||
'description' => $this->cleanHtml($game['Description'] ?? $game['description'] ?? ''),
|
||||
'platform_game_id' => $game['GameId'] ?? $game['game_id'] ?? '',
|
||||
'platform' => $this->extractPlatformFromPlaynite($game),
|
||||
'source_id' => $source['id'],
|
||||
|
||||
// Rich media
|
||||
'background_image' => $game['BackgroundImage'] ?? $game['background'] ?? null,
|
||||
'cover_image' => $game['CoverImage'] ?? $game['cover'] ?? null,
|
||||
'icon' => $game['Icon'] ?? $game['icon'] ?? null,
|
||||
|
||||
// Play statistics
|
||||
'playtime_minutes' => $this->parsePlaytime($game['Playtime'] ?? $game['playtime'] ?? 0),
|
||||
'play_count' => $game['PlayCount'] ?? $game['play_count'] ?? 0,
|
||||
|
||||
// Enhanced ratings
|
||||
'rating' => $this->normalizeRating($game['CriticScore'] ?? $game['critic_score'] ?? null),
|
||||
'critic_score' => $game['CriticScore'] ?? $game['critic_score'] ?? null,
|
||||
'community_score' => $game['CommunityScore'] ?? $game['community_score'] ?? null,
|
||||
'user_score' => $game['UserScore'] ?? $game['user_score'] ?? null,
|
||||
|
||||
// Timestamps
|
||||
'added_at' => isset($game['Added']) ? date('Y-m-d H:i:s', strtotime($game['Added'])) : null,
|
||||
'modified_at' => isset($game['Modified']) ? date('Y-m-d H:i:s', strtotime($game['Modified'])) : null,
|
||||
'last_played_at' => isset($game['LastActivity']) ? date('Y-m-d H:i:s', strtotime($game['LastActivity'])) : null,
|
||||
|
||||
// Playnite metadata
|
||||
'metadata' => json_encode([
|
||||
'playnite_id' => $game['Id'] ?? $game['playnite_id'] ?? null,
|
||||
'version' => $game['Version'] ?? $game['version'] ?? null,
|
||||
'hidden' => $this->toBoolean($game['Hidden'] ?? $game['hidden'] ?? false),
|
||||
'notes' => $game['Notes'] ?? $game['notes'] ?? null,
|
||||
'manual' => $game['Manual'] ?? $game['manual'] ?? null,
|
||||
'pre_script' => $game['PreScript'] ?? $game['pre_script'] ?? null,
|
||||
'post_script' => $game['PostScript'] ?? $game['post_script'] ?? null,
|
||||
'game_started_script' => $game['GameStartedScript'] ?? $game['game_started_script'] ?? null,
|
||||
'use_global_scripts' => [
|
||||
'pre' => $this->toBoolean($game['UseGlobalPreScript'] ?? $game['use_global_pre_script'] ?? true),
|
||||
'post' => $this->toBoolean($game['UseGlobalPostScript'] ?? $game['use_global_post_script'] ?? true),
|
||||
'game_started' => $this->toBoolean($game['UseGlobalGameStartedScript'] ?? $game['use_global_game_started_script'] ?? true)
|
||||
]
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find or create a source for the game
|
||||
*/
|
||||
private function findOrCreateSource(array $game): array
|
||||
{
|
||||
$sourceName = $game['Source']['Name'] ?? $game['source'] ?? 'Playnite';
|
||||
$sourceId = $game['Source']['Id'] ?? $game['source_id'] ?? null;
|
||||
|
||||
// Try to find existing source
|
||||
$stmt = $this->pdo->prepare("SELECT id, display_name FROM sources WHERE display_name = :name OR id = :source_id");
|
||||
$stmt->execute([
|
||||
'name' => $sourceName,
|
||||
'source_id' => $sourceId
|
||||
]);
|
||||
|
||||
$source = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$source) {
|
||||
// Create new source
|
||||
$stmt = $this->pdo->prepare("INSERT INTO sources (display_name, created_at, updated_at) VALUES (:name, NOW(), NOW())");
|
||||
$stmt->execute(['name' => $sourceName]);
|
||||
$source = ['id' => $this->pdo->lastInsertId(), 'display_name' => $sourceName];
|
||||
}
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a consistent game key for grouping
|
||||
*/
|
||||
private function generateGameKey(string $title): string
|
||||
{
|
||||
return \App\Models\Game::generateGameKey($title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract platform from Playnite data
|
||||
*/
|
||||
private function extractPlatformFromPlaynite(array $game): string
|
||||
{
|
||||
if (isset($game['Platforms']) && is_array($game['Platforms'])) {
|
||||
$platformNames = array_map(function($platform) {
|
||||
return $platform['Name'] ?? 'Unknown';
|
||||
}, $game['Platforms']);
|
||||
|
||||
return implode(', ', $platformNames);
|
||||
}
|
||||
|
||||
if (isset($game['Platform']) && is_array($game['Platform'])) {
|
||||
return $game['Platform']['Name'] ?? 'PC';
|
||||
}
|
||||
|
||||
return $game['platform'] ?? 'PC';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse playtime from Playnite format (usually in seconds)
|
||||
*/
|
||||
private function parsePlaytime($playtime): int
|
||||
{
|
||||
if (is_numeric($playtime)) {
|
||||
return (int)($playtime / 60); // Convert seconds to minutes
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize rating to 0-10 scale
|
||||
*/
|
||||
private function normalizeRating($rating): ?float
|
||||
{
|
||||
if (is_numeric($rating)) {
|
||||
$rating = (float)$rating;
|
||||
// If rating is 0-100 scale, convert to 0-10
|
||||
if ($rating > 10) {
|
||||
return $rating / 10;
|
||||
}
|
||||
return $rating;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean HTML from description
|
||||
*/
|
||||
private function cleanHtml(?string $html): ?string
|
||||
{
|
||||
if (!$html) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove HTML tags but keep basic formatting
|
||||
$text = strip_tags($html);
|
||||
// Decode HTML entities
|
||||
$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
|
||||
// Clean up extra whitespace
|
||||
$text = preg_replace('/\s+/', ' ', $text);
|
||||
return trim($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a value to boolean
|
||||
*/
|
||||
private function toBoolean($value): bool
|
||||
{
|
||||
if ($value === null || $value === false || $value === 0 || $value === '0') {
|
||||
return false;
|
||||
}
|
||||
if ($value === true || $value === 1 || $value === '1') {
|
||||
return true;
|
||||
}
|
||||
if (is_string($value)) {
|
||||
return !empty(trim($value));
|
||||
}
|
||||
return (bool) $value;
|
||||
}
|
||||
}
|
||||
41
app/Controllers/Api/_openapi.php
Normal file
41
app/Controllers/Api/_openapi.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @OA\OpenApi(
|
||||
* @OA\Info(
|
||||
* title="Playnite API",
|
||||
* version="1.0.0",
|
||||
* description="API for managing games and media from Playnite",
|
||||
* @OA\Contact(
|
||||
* email="support@example.com"
|
||||
* ),
|
||||
* @OA\License(
|
||||
* name="Apache 2.0",
|
||||
* url="http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
* )
|
||||
* ),
|
||||
* @OA\Server(
|
||||
* url="/api",
|
||||
* description="API Server"
|
||||
* ),
|
||||
* @OA\Components(
|
||||
* @OA\Schema(
|
||||
* schema="Error",
|
||||
* type="object",
|
||||
* @OA\Property(property="success", type="boolean", example=false),
|
||||
* @OA\Property(property="error", type="string", example="Error message")
|
||||
* ),
|
||||
* @OA\Schema(
|
||||
* schema="Success",
|
||||
* type="object",
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="result", type="object")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @OA\Tag(
|
||||
* name="Playnite",
|
||||
* description="Endpoints for Playnite integration"
|
||||
* )
|
||||
*/
|
||||
@@ -80,7 +80,7 @@ class DashboardController extends Controller
|
||||
'recent_games' => $recentGames,
|
||||
'recent_movies' => $recentMovies,
|
||||
'recent_syncs' => $recentSyncs,
|
||||
'sync_stats' => $syncStats
|
||||
//'sync_stats' => $syncStats
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,16 +5,164 @@ namespace App\Controllers;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use App\Models\Game;
|
||||
use App\Services\SteamGridDbService;
|
||||
use Slim\Views\Twig;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
class GameController extends Controller
|
||||
{
|
||||
private \PDO $pdo;
|
||||
private SteamGridDbService $steamGridDb;
|
||||
|
||||
public function __construct(\PDO $pdo, Twig $view)
|
||||
{
|
||||
parent::__construct($view);
|
||||
$this->pdo = $pdo;
|
||||
$this->steamGridDb = new SteamGridDbService();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for games on SteamGridDB
|
||||
*/
|
||||
public function searchSteamGridDb(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$query = $request->getQueryParams()['q'] ?? '';
|
||||
$results = [];
|
||||
|
||||
if (!empty($query)) {
|
||||
$results = $this->steamGridDb->searchGames($query);
|
||||
}
|
||||
|
||||
return $this->json($response, [
|
||||
'success' => true,
|
||||
'data' => $results
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get media from SteamGridDB
|
||||
*/
|
||||
public function getSteamGridDbMedia(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$gameId = $args['gameId'] ?? null;
|
||||
$type = $args['type'] ?? 'grids';
|
||||
$media = [];
|
||||
|
||||
if ($gameId) {
|
||||
switch ($type) {
|
||||
case 'grids':
|
||||
$media = $this->steamGridDb->getGrids($gameId, [
|
||||
'styles' => ['alternate', 'blurred', 'white_logo', 'material', 'no_logo'],
|
||||
'dimensions' => ['600x900', '920x430', '460x215', '920x430']
|
||||
]);
|
||||
break;
|
||||
case 'heroes':
|
||||
$media = $this->steamGridDb->getHeroes($gameId, [
|
||||
'dimensions' => ['1920x620', '3840x1240']
|
||||
]);
|
||||
break;
|
||||
case 'icons':
|
||||
$media = $this->steamGridDb->getIcons($gameId, [
|
||||
'dimensions' => ['32x32', '64x64', '128x128', '256x256', '512x512']
|
||||
]);
|
||||
break;
|
||||
case 'logos':
|
||||
$media = $this->steamGridDb->getLogos($gameId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->json($response, [
|
||||
'success' => true,
|
||||
'data' => $media
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download and set media from SteamGridDB
|
||||
*/
|
||||
public function setSteamGridDbMedia(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$gameId = $args['id'] ?? null;
|
||||
$data = $request->getParsedBody();
|
||||
$type = $data['type'] ?? '';
|
||||
$url = $data['url'] ?? '';
|
||||
$field = '';
|
||||
|
||||
if (!$gameId || !$type || !$url) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Missing required parameters'
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Map media type to database field
|
||||
switch ($type) {
|
||||
case 'grid':
|
||||
$field = 'image_url';
|
||||
break;
|
||||
case 'hero':
|
||||
$field = 'banner_url';
|
||||
break;
|
||||
case 'icon':
|
||||
$field = 'icon';
|
||||
break;
|
||||
case 'logo':
|
||||
$field = 'logo_url';
|
||||
break;
|
||||
default:
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Invalid media type'
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Download the media file
|
||||
$tempFile = $this->steamGridDb->downloadMedia($url);
|
||||
if (!$tempFile) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Failed to download media'
|
||||
], 500);
|
||||
}
|
||||
|
||||
// Move the file to the appropriate location
|
||||
$uploadDir = __DIR__ . '/../../public/uploads/games/' . $gameId;
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0755, true);
|
||||
}
|
||||
|
||||
$filename = $type . '_' . uniqid() . '.' . pathinfo($url, PATHINFO_EXTENSION);
|
||||
$filepath = $uploadDir . '/' . $filename;
|
||||
$publicPath = '/uploads/games/' . $gameId . '/' . $filename;
|
||||
|
||||
if (!rename($tempFile, $filepath)) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Failed to save media file'
|
||||
], 500);
|
||||
}
|
||||
|
||||
// Update the game record
|
||||
$game = Game::find($this->pdo, $gameId);
|
||||
if (!$game) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Game not found'
|
||||
], 404);
|
||||
}
|
||||
|
||||
$game->{$field} = $publicPath;
|
||||
$game->save($this->pdo);
|
||||
|
||||
return $this->json($response, [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'url' => $publicPath,
|
||||
'field' => $field
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function index(Request $request, Response $response, $args)
|
||||
@@ -44,8 +192,11 @@ class GameController extends Controller
|
||||
// Get view mode
|
||||
$viewMode = $queryParams['view'] ?? 'grid'; // grid, list, covers
|
||||
|
||||
// Get games with pagination and filters
|
||||
$games = Game::getGroupedGamesWithPagination($this->pdo, $page, $perPage, $search, $genres, $platforms);
|
||||
// Get sort parameter
|
||||
$sort = $queryParams['sort'] ?? 'title_asc';
|
||||
|
||||
// Get games with pagination, filters, and sorting
|
||||
$games = Game::getGroupedGamesWithPagination($this->pdo, $page, $perPage, $search, $genres, $platforms, $sort);
|
||||
|
||||
// Get total count for pagination
|
||||
$totalCount = Game::getTotalCount($this->pdo, $search, $genres, $platforms);
|
||||
@@ -82,6 +233,17 @@ class GameController extends Controller
|
||||
'available_filters' => [
|
||||
'genres' => $availableGenres,
|
||||
'platforms' => $availablePlatforms
|
||||
],
|
||||
'sort' => $sort,
|
||||
'sort_options' => [
|
||||
'title_asc' => 'Title (A-Z)',
|
||||
'title_desc' => 'Title (Z-A)',
|
||||
'year_asc' => 'Release Year (Oldest First)',
|
||||
'year_desc' => 'Release Year (Newest First)',
|
||||
'playtime_desc' => 'Most Played',
|
||||
'completion_desc' => 'Highest Completion',
|
||||
'added_desc' => 'Recently Added',
|
||||
'last_played_desc' => 'Last Played'
|
||||
]
|
||||
]);
|
||||
}
|
||||
@@ -92,9 +254,9 @@ class GameController extends Controller
|
||||
|
||||
// Find the main game entry (could be any platform version)
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT g.*, s.display_name as source_name
|
||||
SELECT g.*, g.platform as source_name
|
||||
FROM games g
|
||||
JOIN sources s ON g.source_id = s.id
|
||||
|
||||
WHERE g.game_key = :game_key
|
||||
LIMIT 1
|
||||
");
|
||||
|
||||
@@ -44,8 +44,11 @@ class MovieController extends Controller
|
||||
// Get view mode
|
||||
$viewMode = $queryParams['view'] ?? 'grid'; // grid, list, covers
|
||||
|
||||
// Get movies with pagination and filters
|
||||
$movies = Movie::getAllWithPagination($this->pdo, $page, $perPage, $search, $genres, $directors);
|
||||
// Get sort parameter
|
||||
$sort = $queryParams['sort'] ?? 'title_asc';
|
||||
|
||||
// Get movies with pagination, filters, and sorting
|
||||
$movies = Movie::getAllWithPagination($this->pdo, $page, $perPage, $search, $genres, $directors, $sort);
|
||||
|
||||
// Get total count for pagination
|
||||
$totalCount = Movie::getTotalCount($this->pdo, $search, $genres, $directors);
|
||||
@@ -82,6 +85,18 @@ class MovieController extends Controller
|
||||
'available_filters' => [
|
||||
'genres' => $availableGenres,
|
||||
'directors' => $availableDirectors
|
||||
],
|
||||
'sort' => $sort,
|
||||
'sort_options' => [
|
||||
'title_asc' => 'Title (A-Z)',
|
||||
'title_desc' => 'Title (Z-A)',
|
||||
'year_asc' => 'Release Year (Oldest First)',
|
||||
'year_desc' => 'Release Year (Newest First)',
|
||||
'rating_desc' => 'Highest Rated',
|
||||
'views_desc' => 'Most Viewed',
|
||||
'added_desc' => 'Recently Added',
|
||||
'added_asc' => 'Oldest Added',
|
||||
'last_watched_desc' => 'Last Watched'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -33,32 +33,27 @@ class SearchController extends Controller
|
||||
$results = [];
|
||||
|
||||
// Search movies (including adult videos)
|
||||
$movieStmt = $this->pdo->prepare("
|
||||
$searchTerm = $this->pdo->quote("%$search%");
|
||||
$movieStmt = $this->pdo->query("
|
||||
SELECT m.*, s.display_name as source_name, 'movie' as type
|
||||
FROM movies m
|
||||
JOIN sources s ON m.source_id = s.id
|
||||
WHERE (m.title LIKE :search OR m.overview LIKE :search)
|
||||
WHERE (m.title LIKE $searchTerm OR m.overview LIKE $searchTerm)
|
||||
ORDER BY m.title
|
||||
LIMIT 20
|
||||
");
|
||||
$searchParam = "%{$search}%";
|
||||
$movieStmt->bindParam(':search', $searchParam, \PDO::PARAM_STR);
|
||||
$movieStmt->execute();
|
||||
$results['movies'] = $movieStmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Search games
|
||||
$gameStmt = $this->pdo->prepare("
|
||||
SELECT g.*, s.display_name as source_name, 'game' as type
|
||||
$gameStmt = $this->pdo->query("
|
||||
SELECT g.*, 'game' as type
|
||||
FROM games g
|
||||
JOIN sources s ON g.source_id = s.id
|
||||
WHERE (g.name LIKE :search OR g.description LIKE :search)
|
||||
ORDER BY g.name
|
||||
WHERE (g.title LIKE $searchTerm OR g.description LIKE $searchTerm)
|
||||
ORDER BY g.title
|
||||
LIMIT 20
|
||||
");
|
||||
$gameStmt->bindParam(':search', $searchParam, \PDO::PARAM_STR);
|
||||
$gameStmt->execute();
|
||||
$results['games'] = $gameStmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
|
||||
return $this->view->render($response, 'search/index.twig', [
|
||||
'title' => 'Search Results',
|
||||
'search' => $search,
|
||||
|
||||
Reference in New Issue
Block a user