From 2d8be7743faba00e8846c0caaf73b2507165a9b7 Mon Sep 17 00:00:00 2001 From: JVT038 <47184046+JVT038@users.noreply.github.com> Date: Sun, 25 Feb 2024 21:00:20 +0100 Subject: [PATCH] Added API endpoint for person- and cover images. --- bootstrap.php | 1 + docs/openapi.json | 74 +++++++++++++++++++++ settings/routes.php | 3 + src/Factory.php | 11 +++ src/HttpController/Api/ImagesController.php | 53 +++++++++++++++ src/ValueObject/Http/Header.php | 8 +++ src/ValueObject/Http/Response.php | 5 ++ tests/rest/api/image.http | 26 ++++++++ 8 files changed, 181 insertions(+) create mode 100644 src/HttpController/Api/ImagesController.php create mode 100644 tests/rest/api/image.http diff --git a/bootstrap.php b/bootstrap.php index 6fb46d622..91e744fe5 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -10,6 +10,7 @@ \Movary\ValueObject\Config::class => DI\factory([Factory::class, 'createConfig']), \Movary\Api\Trakt\TraktApi::class => DI\factory([Factory::class, 'createTraktApi']), \Movary\Service\ImageCacheService::class => DI\factory([Factory::class, 'createImageCacheService']), + \Movary\HttpController\Api\ImagesController::class => DI\factory([Factory::class, 'createImagesController']), \Movary\JobQueue\JobQueueScheduler::class => DI\factory([Factory::class, 'createJobQueueScheduler']), \Movary\Api\Tmdb\TmdbClient::class => DI\factory([Factory::class, 'createTmdbApiClient']), \Movary\Service\UrlGenerator::class => DI\factory([Factory::class, 'createUrlGenerator']), diff --git a/docs/openapi.json b/docs/openapi.json index 964a35b61..9f1440ad2 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -1066,6 +1066,80 @@ } } }, + "/images/person/{id}": { + "get": { + "tags": [ + "Images" + ], + "summary": "Get person image.", + "description": "Get image of an actor, actress or directory based on their Movary ID.", + "parameters": [ + { + "name": "ID", + "in": "path", + "description": "ID of the person in Movary", + "required": true, + "schema": { + "type": "integer" + }, + "example": 1 + } + ], + "responses": { + "200": { + "description": "Person was found and has an image in Movary.", + "content": { + "image/jpeg": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Either no person exists with the given ID or the person exists, but doesn't have an image in Movary." + } + } + } + }, + "/images/movie/{id}": { + "get": { + "tags": [ + "Images" + ], + "summary": "Get movie cover image.", + "description": "Get cover image of a movie based on their Movary ID.", + "parameters": [ + { + "name": "ID", + "in": "path", + "description": "ID of the movie in Movary", + "required": true, + "schema": { + "type": "integer" + }, + "example": 1 + } + ], + "responses": { + "200": { + "description": "Movie was found and has a cover in Movary.", + "content": { + "image/jpeg": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Either no movie exists with the given ID or the movie exists, but doesn't have a cover image in Movary." + } + } + } + }, "/authentication/token": { "post": { "tags": [ diff --git a/settings/routes.php b/settings/routes.php index 6fcac1c1d..f42407fa8 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -229,5 +229,8 @@ function addApiRoutes(RouterService $routerService, FastRoute\RouteCollector $ro $routes->add('GET', '/feed/radarr/{id:.+}', [Api\RadarrController::class, 'renderRadarrFeed']); + $routes->add('GET', '/images/movies/{id:\d+}', [Api\ImagesController::class, 'getMovieImage']); + $routes->add('GET', '/images/person/{id:\d+}', [Api\ImagesController::class, 'getPersonImage']); + $routerService->addRoutesToRouteCollector($routeCollector, $routes); } diff --git a/src/Factory.php b/src/Factory.php index b59fce2ad..06342d9e7 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -17,9 +17,11 @@ use Movary\Command\CreatePublicStorageLink; use Movary\Domain\Movie\MovieApi; use Movary\Domain\Movie\Watchlist\MovieWatchlistApi; +use Movary\Domain\Person\PersonApi; use Movary\Domain\User; use Movary\Domain\User\Service\Authentication; use Movary\Domain\User\UserApi; +use Movary\HttpController\Api\ImagesController; use Movary\HttpController\Api\OpenApiController; use Movary\HttpController\Web\CreateUserController; use Movary\HttpController\Web\JobController; @@ -184,6 +186,15 @@ public static function createImageCacheService(ContainerInterface $container) : ); } + public static function createImagesController(ContainerInterface $container) : ImagesController + { + return new ImagesController( + $container->get(PersonApi::class), + $container->get(MovieApi::class), + self::createDirectoryAppRoot() . 'public' + ); + } + public static function createJobController(ContainerInterface $container) : JobController { return new JobController( diff --git a/src/HttpController/Api/ImagesController.php b/src/HttpController/Api/ImagesController.php new file mode 100644 index 000000000..e39bfe751 --- /dev/null +++ b/src/HttpController/Api/ImagesController.php @@ -0,0 +1,53 @@ +getRouteParameters()['id']; + $movie = $this->movieApi->findById($resourceId); + if($movie === null) { + return Response::createNotFound(); + } + $posterPath = $movie->getPosterPath(); + if($posterPath === null) { + return Response::createNotFound(); + } + $image = file_get_contents($this->publicDirectory . $posterPath); + if($image === false) { + return Response::createNotFound(); + } + return Response::createJpeg($image); + } + + public function getPersonImage(Request $request) : Response + { + $resourceId = (int)$request->getRouteParameters()['id']; + $person = $this->personApi->findById($resourceId); + if($person === null) { + return Response::createNotFound(); + } + $posterPath = $person->getPosterPath(); + if($posterPath === null) { + return Response::createNotFound(); + } + $image = file_get_contents($this->publicDirectory . $posterPath); + if($image === false) { + return Response::createNotFound(); + } + return Response::createJpeg($image); + } +} diff --git a/src/ValueObject/Http/Header.php b/src/ValueObject/Http/Header.php index 5c4d47bed..2b287a3c0 100644 --- a/src/ValueObject/Http/Header.php +++ b/src/ValueObject/Http/Header.php @@ -20,6 +20,14 @@ public static function createContentTypeJson() : self return new self('Content-Type', 'application/json'); } + public static function createContentTypeJpeg(int $contentLength) : array + { + return [ + new self('Content-Type', 'image/jpeg'), + new self('Content-Length', (string)$contentLength) + ]; + } + public static function createLocation(string $value) : self { return new self('Location', $value); diff --git a/src/ValueObject/Http/Response.php b/src/ValueObject/Http/Response.php index 86e83803a..6664e754b 100644 --- a/src/ValueObject/Http/Response.php +++ b/src/ValueObject/Http/Response.php @@ -40,6 +40,11 @@ public static function createForbiddenRedirect(string $redirectTarget) : self return new self(StatusCode::createForbidden(), null, [Header::createLocation('/login?redirect='.$query)]); } + public static function createJpeg(string $image) : self + { + return new self(StatusCode::createOk(), $image, Header::createContentTypeJpeg(strlen($image))); + } + public static function createJson(string $body, StatusCode $statusCode = null) : self { return new self($statusCode ?? StatusCode::createOk(), $body, [Header::createContentTypeJson()]); diff --git a/tests/rest/api/image.http b/tests/rest/api/image.http new file mode 100644 index 000000000..1e8e6aa33 --- /dev/null +++ b/tests/rest/api/image.http @@ -0,0 +1,26 @@ +GET http://127.0.0.1/api/images/person/1 +Accept: */* +Cache-Control: no-cache +Content-Type: application/json + +### + +GET http://127.0.0.1/api/images/movies/1 +Accept: */* +Cache-Control: no-cache +Content-Type: application/json + +### + +GET http://127.0.0.1/api/images/invalidresourcetype/1 +Accept: */* +Cache-Control: no-cache +Content-Type: application/json + +### + +GET http://127.0.0.1/api/images/person/invalidresourceid +Accept: */* +Cache-Control: no-cache +Content-Type: application/json +