From dfa02889d6d7057d6dc5ebe52c8e57d794f73534 Mon Sep 17 00:00:00 2001 From: Lee Peuker Date: Wed, 13 Sep 2023 13:53:34 +0200 Subject: [PATCH 1/5] Add boilerplate for played endpoints --- settings/routes.php | 6 ++ .../Movie/Watchlist/MovieWatchlistApi.php | 3 +- src/HttpController/Api/PlayedController.php | 95 +++++++++++++++++++ 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/HttpController/Api/PlayedController.php diff --git a/settings/routes.php b/settings/routes.php index b806d56f..88b0dd96 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -178,5 +178,11 @@ function addApiRoutes(RouterService $routerService, FastRoute\RouteCollector $ro $routes->add('POST', $routeUserWatchlist, [Api\WatchlistController::class, 'addToWatchlist'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); $routes->add('DELETE', $routeUserWatchlist, [Api\WatchlistController::class, 'deleteFromWatchlist'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); + $routeUserWatchlist = '/users/{username:[a-zA-Z0-9]+}/played/movies'; + $routes->add('GET', $routeUserWatchlist , [Api\PlayedController::class, 'getPlayed'], [Api\Middleware\IsAuthorizedToReadUserData::class]); + $routes->add('POST', $routeUserWatchlist, [Api\PlayedController::class, 'addToPlayed'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); + $routes->add('DELETE', $routeUserWatchlist, [Api\PlayedController::class, 'deleteFromPlayed'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); + $routes->add('PUT', $routeUserHistory, [Api\PlayedController::class, 'updatePlayed'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); + $routerService->addRoutesToRouteCollector($routeCollector, $routes); } diff --git a/src/Domain/Movie/Watchlist/MovieWatchlistApi.php b/src/Domain/Movie/Watchlist/MovieWatchlistApi.php index 2783d9b0..d66499f2 100644 --- a/src/Domain/Movie/Watchlist/MovieWatchlistApi.php +++ b/src/Domain/Movie/Watchlist/MovieWatchlistApi.php @@ -3,6 +3,7 @@ namespace Movary\Domain\Movie\Watchlist; use Movary\Api\Tmdb\TmdbApi; +use Movary\Domain\Movie\History\MovieHistoryApi; use Movary\Domain\User\UserApi; use Movary\Service\UrlGenerator; use Movary\ValueObject\DateTime; @@ -13,7 +14,7 @@ class MovieWatchlistApi { public function __construct( - private readonly MovieWatchlistRepository $repository, + private readonly MovieHistoryApi $repository, private readonly UrlGenerator $urlGenerator, private readonly UserApi $userApi, private readonly TmdbApi $tmdbApi, diff --git a/src/HttpController/Api/PlayedController.php b/src/HttpController/Api/PlayedController.php new file mode 100644 index 00000000..e1902b16 --- /dev/null +++ b/src/HttpController/Api/PlayedController.php @@ -0,0 +1,95 @@ +requestMapper->mapUsernameFromRoute($request)->getId(); + $watchlistAdditions = Json::decode($request->getBody()); + + // TODO add movie plays + + return Response::createNoContent(); + } + + public function deleteFromPlayed(Request $request) : Response + { + $userId = $this->requestMapper->mapUsernameFromRoute($request)->getId(); + $watchlistRemovals = Json::decode($request->getBody()); + + // TODO delete movie plays + + return Response::createNoContent(); + } + + public function getPlayed(Request $request) : Response + { + // TODO get played movies + +// $requestData = $this->watchlistRequestMapper->mapRequest($request); +// +// $watchlistEntries = $this->movieWatchlistApi->fetchWatchlistPaginated( +// $requestData->getRequestedUserId(), +// $requestData->getLimit(), +// $requestData->getPage(), +// $requestData->getSearchTerm(), +// $requestData->getSortBy(), +// $requestData->getSortOrder(), +// $requestData->getReleaseYear(), +// $requestData->getLanguage(), +// $requestData->getGenre(), +// ); +// +// $watchlistCount = $this->movieWatchlistApi->fetchWatchlistCount( +// $requestData->getRequestedUserId(), +// $requestData->getSearchTerm(), +// $requestData->getReleaseYear(), +// $requestData->getLanguage(), +// $requestData->getGenre(), +// ); +// +// $paginationElements = $this->paginationElementsCalculator->createPaginationElements( +// $watchlistCount, +// $requestData->getLimit(), +// $requestData->getPage(), +// ); + + return Response::createJson( + Json::encode([ + 'watchlist' => $this->watchlistResponseMapper->mapWatchlistEntries($watchlistEntries), + 'currentPage' => $paginationElements->getCurrentPage(), + 'maxPage' => $paginationElements->getMaxPage(), + ]), + ); + } + + public function updatePlayed(Request $request) : Response + { + $userId = $this->requestMapper->mapUsernameFromRoute($request)->getId(); + $watchlistAdditions = Json::decode($request->getBody()); + + // TODO update movie plays + + return Response::createNoContent(); + } +} From a4a9232f7902fc6983d26e2dbc7e34629387506d Mon Sep 17 00:00:00 2001 From: Lee Peuker Date: Wed, 13 Sep 2023 13:54:53 +0200 Subject: [PATCH 2/5] Revert wrong change --- src/Domain/Movie/Watchlist/MovieWatchlistApi.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Domain/Movie/Watchlist/MovieWatchlistApi.php b/src/Domain/Movie/Watchlist/MovieWatchlistApi.php index d66499f2..2783d9b0 100644 --- a/src/Domain/Movie/Watchlist/MovieWatchlistApi.php +++ b/src/Domain/Movie/Watchlist/MovieWatchlistApi.php @@ -3,7 +3,6 @@ namespace Movary\Domain\Movie\Watchlist; use Movary\Api\Tmdb\TmdbApi; -use Movary\Domain\Movie\History\MovieHistoryApi; use Movary\Domain\User\UserApi; use Movary\Service\UrlGenerator; use Movary\ValueObject\DateTime; @@ -14,7 +13,7 @@ class MovieWatchlistApi { public function __construct( - private readonly MovieHistoryApi $repository, + private readonly MovieWatchlistRepository $repository, private readonly UrlGenerator $urlGenerator, private readonly UserApi $userApi, private readonly TmdbApi $tmdbApi, From 821facca79d8a35624e3b4328a50da0403645675 Mon Sep 17 00:00:00 2001 From: Lee Peuker Date: Wed, 13 Sep 2023 19:37:25 +0200 Subject: [PATCH 3/5] Add get endpoint --- settings/routes.php | 52 +++++++--- src/Domain/Movie/History/MovieHistoryApi.php | 30 ++++++ src/Domain/Movie/MovieApi.php | 97 ++++++++++++------- src/HttpController/Api/Dto/PlayedEntryDto.php | 25 +++++ .../Api/Dto/PlayedEntryDtoList.php | 22 +++++ src/HttpController/Api/PlayedController.php | 93 ++++++++++-------- .../Api/RequestMapper/PlayedRequestMapper.php | 70 +++++++++++++ .../ResponseMapper/PlayedResponseMapper.php | 33 +++++++ tests/rest/api/played.http | 37 +++++++ 9 files changed, 371 insertions(+), 88 deletions(-) create mode 100644 src/HttpController/Api/Dto/PlayedEntryDto.php create mode 100644 src/HttpController/Api/Dto/PlayedEntryDtoList.php create mode 100644 src/HttpController/Api/RequestMapper/PlayedRequestMapper.php create mode 100644 src/HttpController/Api/ResponseMapper/PlayedResponseMapper.php create mode 100644 tests/rest/api/played.http diff --git a/settings/routes.php b/settings/routes.php index 88b0dd96..844cb74d 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -68,18 +68,36 @@ function addWebRoutes(RouterService $routerService, FastRoute\RouteCollector $ro $routes->add('GET', '/settings/account/security', [Web\SettingsController::class, 'renderSecurityAccountPage'], [Web\Middleware\UserIsAuthenticated::class]); $routes->add('GET', '/settings/account/data', [Web\SettingsController::class, 'renderDataAccountPage'], [Web\Middleware\UserIsAuthenticated::class]); $routes->add('GET', '/settings/server/general', [Web\SettingsController::class, 'renderServerGeneralPage'], [Web\Middleware\UserIsAuthenticated::class]); - $routes->add('GET', '/settings/server/jobs', [Web\SettingsController::class, 'renderServerJobsPage'], [Web\Middleware\UserIsAuthenticated::class, Web\Middleware\UserIsAdmin::class]); + $routes->add('GET', '/settings/server/jobs', [Web\SettingsController::class, 'renderServerJobsPage'], [ + Web\Middleware\UserIsAuthenticated::class, + Web\Middleware\UserIsAdmin::class + ]); $routes->add('POST', '/settings/server/general', [Web\SettingsController::class, 'updateServerGeneral'], [ Web\Middleware\UserIsAuthenticated::class, Web\Middleware\UserIsAdmin::class ]); - $routes->add('GET', '/settings/server/users', [Web\SettingsController::class, 'renderServerUsersPage'], [Web\Middleware\UserIsAuthenticated::class, Web\Middleware\UserIsAdmin::class]); - $routes->add('GET', '/settings/server/email', [Web\SettingsController::class, 'renderServerEmailPage'], [Web\Middleware\UserIsAuthenticated::class, Web\Middleware\UserIsAdmin::class]); - $routes->add('POST', '/settings/server/email', [Web\SettingsController::class, 'updateServerEmail'], [Web\Middleware\UserIsAuthenticated::class, Web\Middleware\UserIsAdmin::class]); - $routes->add('POST', '/settings/server/email-test', [Web\SettingsController::class, 'sendTestEmail'], [Web\Middleware\UserIsAuthenticated::class, Web\Middleware\UserIsAdmin::class]); + $routes->add('GET', '/settings/server/users', [Web\SettingsController::class, 'renderServerUsersPage'], [ + Web\Middleware\UserIsAuthenticated::class, + Web\Middleware\UserIsAdmin::class + ]); + $routes->add('GET', '/settings/server/email', [Web\SettingsController::class, 'renderServerEmailPage'], [ + Web\Middleware\UserIsAuthenticated::class, + Web\Middleware\UserIsAdmin::class + ]); + $routes->add('POST', '/settings/server/email', [Web\SettingsController::class, 'updateServerEmail'], [ + Web\Middleware\UserIsAuthenticated::class, + Web\Middleware\UserIsAdmin::class + ]); + $routes->add('POST', '/settings/server/email-test', [Web\SettingsController::class, 'sendTestEmail'], [ + Web\Middleware\UserIsAuthenticated::class, + Web\Middleware\UserIsAdmin::class + ]); $routes->add('POST', '/settings/account', [Web\SettingsController::class, 'updateGeneral'], [Web\Middleware\UserIsAuthenticated::class]); $routes->add('POST', '/settings/account/security/update-password', [Web\SettingsController::class, 'updatePassword'], [Web\Middleware\UserIsAuthenticated::class]); - $routes->add('POST', '/settings/account/security/create-totp-uri', [Web\TwoFactorAuthenticationController::class, 'createTotpUri'], [Web\Middleware\UserIsAuthenticated::class]); + $routes->add('POST', '/settings/account/security/create-totp-uri', [ + Web\TwoFactorAuthenticationController::class, + 'createTotpUri' + ], [Web\Middleware\UserIsAuthenticated::class]); $routes->add('POST', '/settings/account/security/disable-totp', [Web\TwoFactorAuthenticationController::class, 'disableTotp'], [Web\Middleware\UserIsAuthenticated::class]); $routes->add('POST', '/settings/account/security/enable-totp', [Web\TwoFactorAuthenticationController::class, 'enableTotp'], [Web\Middleware\UserIsAuthenticated::class]); $routes->add('GET', '/settings/account/export/csv/{exportType:.+}', [Web\ExportController::class, 'getCsvExport'], [Web\Middleware\UserIsAuthenticated::class]); @@ -148,8 +166,14 @@ function addWebRoutes(RouterService $routerService, FastRoute\RouteCollector $ro $routes->add('GET', '/users/{username:[a-zA-Z0-9]+}/directors', [Web\DirectorsController::class, 'renderPage']); $routes->add('GET', '/users/{username:[a-zA-Z0-9]+}/movies/{id:\d+}', [Web\Movie\MovieController::class, 'renderPage']); $routes->add('GET', '/users/{username:[a-zA-Z0-9]+}/persons/{id:\d+}', [Web\PersonController::class, 'renderPage']); - $routes->add('DELETE', '/users/{username:[a-zA-Z0-9]+}/movies/{id:\d+}/history', [Web\HistoryController::class, 'deleteHistoryEntry'], [Web\Middleware\UserIsAuthenticated::class]); - $routes->add('POST', '/users/{username:[a-zA-Z0-9]+}/movies/{id:\d+}/history', [Web\HistoryController::class, 'createHistoryEntry'], [Web\Middleware\UserIsAuthenticated::class]); + $routes->add('DELETE', '/users/{username:[a-zA-Z0-9]+}/movies/{id:\d+}/history', [ + Web\HistoryController::class, + 'deleteHistoryEntry' + ], [Web\Middleware\UserIsAuthenticated::class]); + $routes->add('POST', '/users/{username:[a-zA-Z0-9]+}/movies/{id:\d+}/history', [ + Web\HistoryController::class, + 'createHistoryEntry' + ], [Web\Middleware\UserIsAuthenticated::class]); $routes->add('POST', '/users/{username:[a-zA-Z0-9]+}/movies/{id:\d+}/rating', [ Web\Movie\MovieRatingController::class, 'updateRating' @@ -174,15 +198,15 @@ function addApiRoutes(RouterService $routerService, FastRoute\RouteCollector $ro $routes->add('PUT', $routeUserHistory, [Api\HistoryController::class, 'updateHistory'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); $routeUserWatchlist = '/users/{username:[a-zA-Z0-9]+}/watchlist/movies'; - $routes->add('GET', $routeUserWatchlist , [Api\WatchlistController::class, 'getWatchlist'], [Api\Middleware\IsAuthorizedToReadUserData::class]); + $routes->add('GET', $routeUserWatchlist, [Api\WatchlistController::class, 'getWatchlist'], [Api\Middleware\IsAuthorizedToReadUserData::class]); $routes->add('POST', $routeUserWatchlist, [Api\WatchlistController::class, 'addToWatchlist'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); $routes->add('DELETE', $routeUserWatchlist, [Api\WatchlistController::class, 'deleteFromWatchlist'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); - $routeUserWatchlist = '/users/{username:[a-zA-Z0-9]+}/played/movies'; - $routes->add('GET', $routeUserWatchlist , [Api\PlayedController::class, 'getPlayed'], [Api\Middleware\IsAuthorizedToReadUserData::class]); - $routes->add('POST', $routeUserWatchlist, [Api\PlayedController::class, 'addToPlayed'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); - $routes->add('DELETE', $routeUserWatchlist, [Api\PlayedController::class, 'deleteFromPlayed'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); - $routes->add('PUT', $routeUserHistory, [Api\PlayedController::class, 'updatePlayed'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); + $routeUserPlayed = '/users/{username:[a-zA-Z0-9]+}/played/movies'; + $routes->add('GET', $routeUserPlayed, [Api\PlayedController::class, 'getPlayed'], [Api\Middleware\IsAuthorizedToReadUserData::class]); + $routes->add('POST', $routeUserPlayed, [Api\PlayedController::class, 'addToPlayed'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); + $routes->add('DELETE', $routeUserPlayed, [Api\PlayedController::class, 'deleteFromPlayed'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); + $routes->add('PUT', $routeUserPlayed, [Api\PlayedController::class, 'updatePlayed'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); $routerService->addRoutesToRouteCollector($routeCollector, $routes); } diff --git a/src/Domain/Movie/History/MovieHistoryApi.php b/src/Domain/Movie/History/MovieHistoryApi.php index 47e96ee3..81b99820 100644 --- a/src/Domain/Movie/History/MovieHistoryApi.php +++ b/src/Domain/Movie/History/MovieHistoryApi.php @@ -371,6 +371,36 @@ public function fetchUniqueWatchedMoviesPaginated( return $this->urlGenerator->replacePosterPathWithImageSrcUrl($movies); } + public function fetchPlayedMoviesPaginated( + int $userId, + int $limit, + int $page, + ?string $searchTerm = null, + string $sortBy = 'title', + ?SortOrder $sortOrder = null, + ?Year $releaseYear = null, + ?string $language = null, + ?string $genre = null, + ) : array { + if ($sortOrder === null) { + $sortOrder = SortOrder::createAsc(); + } + + $movies = $this->movieRepository->fetchUniqueWatchedMoviesPaginated( + $userId, + $limit, + $page, + $searchTerm, + $sortBy, + $sortOrder, + $releaseYear, + $language, + $genre, + ); + + return $this->urlGenerator->replacePosterPathWithImageSrcUrl($movies); + } + public function fetchWatchDatesOrderedByWatchedAtDesc(int $userId) : array { return $this->movieRepository->fetchWatchDatesOrderedByWatchedAtDesc($userId); diff --git a/src/Domain/Movie/MovieApi.php b/src/Domain/Movie/MovieApi.php index e7935d94..d05e36c7 100644 --- a/src/Domain/Movie/MovieApi.php +++ b/src/Domain/Movie/MovieApi.php @@ -47,6 +47,33 @@ public function __construct( ) { } + public function addPlaysForMovieOnDate(int $movieId, int $userId, ?Date $watchedDate, int $playsToAdd = 1, ?string $comment = null) : void + { + $historyEntry = $this->findHistoryEntryForMovieByUserOnDate($movieId, $userId, $watchedDate); + + $this->watchlistApi->removeMovieFromWatchlistAutomatically($movieId, $userId); + + if ($historyEntry === null) { + $this->historyApi->create( + $movieId, + $userId, + $watchedDate, + $playsToAdd, + $comment, + ); + + return; + } + + $this->historyApi->update( + $movieId, + $userId, + $watchedDate, + $historyEntry->getPlays() + $playsToAdd, + $comment ?? $historyEntry->getComment(), + ); + } + public function create( string $title, int $tmdbId, @@ -147,11 +174,6 @@ public function fetchHistoryMovieTotalPlays(int $movieId, int $userId) : int return $this->historyApi->fetchTotalPlaysForMovieAndUserId($movieId, $userId); } - public function fetchWatchDatesOrderedByWatchedAtDesc(int $userId) : array - { - return $this->historyApi->fetchWatchDatesOrderedByWatchedAtDesc($userId); - } - public function fetchMovieIdsHavingImdbIdOrderedByLastImdbUpdatedAt( ?int $maxAgeInHours = null, ?int $limit = null, @@ -161,6 +183,30 @@ public function fetchMovieIdsHavingImdbIdOrderedByLastImdbUpdatedAt( return $this->movieRepository->fetchMovieIdsHavingImdbIdOrderedByLastImdbUpdatedAt($maxAgeInHours, $limit, $filterMovieIds, $onlyNeverSynced); } + public function fetchPlayedMoviesPaginated( + int $userId, + int $limit, + int $page, + ?string $searchTerm, + string $sortBy, + SortOrder $sortOrder, + ?Year $releaseYear, + ?string $language, + ?string $genre, + ) : array { + return $this->historyApi->fetchPlayedMoviesPaginated( + $userId, + $limit, + $page, + $searchTerm, + $sortBy, + $sortOrder, + $releaseYear, + $language, + $genre, + ); + } + public function fetchTotalPlayCount(int $userId) : int { return $this->historyApi->fetchTotalPlayCount($userId); @@ -186,6 +232,16 @@ public function fetchUniqueMovieReleaseYears(int $userId) : array return $this->historyApi->fetchUniqueMovieReleaseYears($userId); } + public function fetchUniqueWatchedMoviesCount(int $userId, ?string $searchTerm, ?Year $releaseYear, ?string $language, ?string $genre) : int + { + return $this->historyApi->fetchUniqueWatchedMoviesCount($userId, $searchTerm, $releaseYear, $language, $genre); + } + + public function fetchPlayedMoviesCount(int $userId, ?string $searchTerm, ?Year $releaseYear, ?string $language, ?string $genre) : int + { + return $this->historyApi->fetchUniqueWatchedMoviesCount($userId, $searchTerm, $releaseYear, $language, $genre); + } + public function fetchUniqueWatchedMoviesPaginated( int $userId, int $limit, @@ -210,9 +266,9 @@ public function fetchUniqueWatchedMoviesPaginated( ); } - public function fetchUniqueWatchedMoviesCount(int $userId, ?string $searchTerm, ?Year $releaseYear, ?string $language, ?string $genre) : int + public function fetchWatchDatesOrderedByWatchedAtDesc(int $userId) : array { - return $this->historyApi->fetchUniqueWatchedMoviesCount($userId, $searchTerm, $releaseYear, $language, $genre); + return $this->historyApi->fetchWatchDatesOrderedByWatchedAtDesc($userId); } public function fetchWithActor(int $personId, int $userId) : array @@ -333,33 +389,6 @@ public function findUserRating(int $movieId, int $userId) : ?PersonalRating return $this->repository->findUserRating($movieId, $userId); } - public function addPlaysForMovieOnDate(int $movieId, int $userId, ?Date $watchedDate, int $playsToAdd = 1, ?string $comment = null) : void - { - $historyEntry = $this->findHistoryEntryForMovieByUserOnDate($movieId, $userId, $watchedDate); - - $this->watchlistApi->removeMovieFromWatchlistAutomatically($movieId, $userId); - - if ($historyEntry === null) { - $this->historyApi->create( - $movieId, - $userId, - $watchedDate, - $playsToAdd, - $comment, - ); - - return; - } - - $this->historyApi->update( - $movieId, - $userId, - $watchedDate, - $historyEntry->getPlays() + $playsToAdd, - $comment ?? $historyEntry->getComment(), - ); - } - public function replaceHistoryForMovieByDate(int $movieId, int $userId, ?Date $watchedAt, int $playsPerDate, ?string $comment = null) : void { $existingHistoryEntry = $this->findHistoryEntryForMovieByUserOnDate($movieId, $userId, $watchedAt); diff --git a/src/HttpController/Api/Dto/PlayedEntryDto.php b/src/HttpController/Api/Dto/PlayedEntryDto.php new file mode 100644 index 00000000..cd111f03 --- /dev/null +++ b/src/HttpController/Api/Dto/PlayedEntryDto.php @@ -0,0 +1,25 @@ + $this->movieDto, + ]; + } +} diff --git a/src/HttpController/Api/Dto/PlayedEntryDtoList.php b/src/HttpController/Api/Dto/PlayedEntryDtoList.php new file mode 100644 index 00000000..d6fc31b9 --- /dev/null +++ b/src/HttpController/Api/Dto/PlayedEntryDtoList.php @@ -0,0 +1,22 @@ +data[] = $dto; + } +} diff --git a/src/HttpController/Api/PlayedController.php b/src/HttpController/Api/PlayedController.php index e1902b16..f49804e4 100644 --- a/src/HttpController/Api/PlayedController.php +++ b/src/HttpController/Api/PlayedController.php @@ -2,22 +2,23 @@ namespace Movary\HttpController\Api; -use Movary\Domain\Movie\Watchlist\MovieWatchlistApi; +use Movary\Domain\Movie\MovieApi; +use Movary\HttpController\Api\RequestMapper\PlayedRequestMapper; use Movary\HttpController\Api\RequestMapper\RequestMapper; -use Movary\HttpController\Api\RequestMapper\WatchlistRequestMapper; -use Movary\HttpController\Api\ResponseMapper\WatchlistResponseMapper; +use Movary\HttpController\Api\ResponseMapper\PlayedResponseMapper; use Movary\Service\PaginationElementsCalculator; use Movary\Util\Json; +use Movary\ValueObject\Date; use Movary\ValueObject\Http\Request; use Movary\ValueObject\Http\Response; class PlayedController { public function __construct( - private readonly MovieWatchlistApi $movieWatchlistApi, + private readonly MovieApi $movieApi, private readonly PaginationElementsCalculator $paginationElementsCalculator, - private readonly WatchlistRequestMapper $watchlistRequestMapper, - private readonly WatchlistResponseMapper $watchlistResponseMapper, + private readonly PlayedRequestMapper $playedRequestMapper, + private readonly PlayedResponseMapper $playedResponseMapper, private readonly RequestMapper $requestMapper, ) { } @@ -35,48 +36,52 @@ public function addToPlayed(Request $request) : Response public function deleteFromPlayed(Request $request) : Response { $userId = $this->requestMapper->mapUsernameFromRoute($request)->getId(); - $watchlistRemovals = Json::decode($request->getBody()); + $historyAdditions = Json::decode($request->getBody()); - // TODO delete movie plays + foreach ($historyAdditions as $historyAddition) { + $this->movieApi->deleteHistoryByIdAndDate( + (int)$historyAddition['movaryId'], + $userId, + isset($historyAddition['watchedAt']) === true ? Date::createFromString($historyAddition['watchedAt']) : null, + ); + } return Response::createNoContent(); } public function getPlayed(Request $request) : Response { - // TODO get played movies - -// $requestData = $this->watchlistRequestMapper->mapRequest($request); -// -// $watchlistEntries = $this->movieWatchlistApi->fetchWatchlistPaginated( -// $requestData->getRequestedUserId(), -// $requestData->getLimit(), -// $requestData->getPage(), -// $requestData->getSearchTerm(), -// $requestData->getSortBy(), -// $requestData->getSortOrder(), -// $requestData->getReleaseYear(), -// $requestData->getLanguage(), -// $requestData->getGenre(), -// ); -// -// $watchlistCount = $this->movieWatchlistApi->fetchWatchlistCount( -// $requestData->getRequestedUserId(), -// $requestData->getSearchTerm(), -// $requestData->getReleaseYear(), -// $requestData->getLanguage(), -// $requestData->getGenre(), -// ); -// -// $paginationElements = $this->paginationElementsCalculator->createPaginationElements( -// $watchlistCount, -// $requestData->getLimit(), -// $requestData->getPage(), -// ); + $requestData = $this->playedRequestMapper->mapRequest($request); + + $watchlistEntries = $this->movieApi->fetchPlayedMoviesPaginated( + $requestData->getRequestedUserId(), + $requestData->getLimit(), + $requestData->getPage(), + $requestData->getSearchTerm(), + $requestData->getSortBy(), + $requestData->getSortOrder(), + $requestData->getReleaseYear(), + $requestData->getLanguage(), + $requestData->getGenre(), + ); + + $watchlistCount = $this->movieApi->fetchPlayedMoviesCount( + $requestData->getRequestedUserId(), + $requestData->getSearchTerm(), + $requestData->getReleaseYear(), + $requestData->getLanguage(), + $requestData->getGenre(), + ); + + $paginationElements = $this->paginationElementsCalculator->createPaginationElements( + $watchlistCount, + $requestData->getLimit(), + $requestData->getPage(), + ); return Response::createJson( Json::encode([ - 'watchlist' => $this->watchlistResponseMapper->mapWatchlistEntries($watchlistEntries), + 'played' => $this->playedResponseMapper->mapPlayedEntries($watchlistEntries), 'currentPage' => $paginationElements->getCurrentPage(), 'maxPage' => $paginationElements->getMaxPage(), ]), @@ -86,9 +91,17 @@ public function getPlayed(Request $request) : Response public function updatePlayed(Request $request) : Response { $userId = $this->requestMapper->mapUsernameFromRoute($request)->getId(); - $watchlistAdditions = Json::decode($request->getBody()); + $historyAdditions = Json::decode($request->getBody()); - // TODO update movie plays + foreach ($historyAdditions as $historyAddition) { + $this->movieApi->replaceHistoryForMovieByDate( + (int)$historyAddition['movaryId'], + $userId, + isset($historyAddition['watchedAt']) === true ? Date::createFromString($historyAddition['watchedAt']) : null, + $historyAddition['plays'], + $historyAddition['comment'], + ); + } return Response::createNoContent(); } diff --git a/src/HttpController/Api/RequestMapper/PlayedRequestMapper.php b/src/HttpController/Api/RequestMapper/PlayedRequestMapper.php new file mode 100644 index 00000000..c75cd775 --- /dev/null +++ b/src/HttpController/Api/RequestMapper/PlayedRequestMapper.php @@ -0,0 +1,70 @@ +getGetParameters(); + + $searchTerm = $getParameters['search'] ?? null; + $page = $getParameters['page'] ?? self::DEFAULT_PAGE; + $limit = $getParameters['limit'] ?? self::DEFAULT_LIMIT; + $sortBy = $getParameters['sortBy'] ?? self::DEFAULT_SORT_BY; + $sortOrder = $this->mapSortOrder($getParameters); + $releaseYear = $this->mapReleaseYear($getParameters); + + return WatchlistRequestDto::create( + $this->requestMapper->mapUsernameFromRoute($request)->getId(), + $searchTerm, + (int)$page, + (int)$limit, + $sortBy, + $sortOrder, + $releaseYear, + ); + } + + private function mapReleaseYear(array $getParameters) : ?Year + { + $releaseYear = $getParameters['releaseYear'] ?? self::DEFAULT_RELEASE_YEAR; + + if (empty($releaseYear) === true) { + return null; + } + + return Year::createFromString($releaseYear); + } + + private function mapSortOrder(array $getParameters) : SortOrder + { + if (isset($getParameters['sortOrder']) === false) { + return SortOrder::createAsc(); + } + + return match ($getParameters['sortOrder']) { + 'asc' => SortOrder::createAsc(), + 'desc' => SortOrder::createDesc(), + + default => throw new \RuntimeException('Not supported sort order: ' . $getParameters['sortOrder']) + }; + } +} diff --git a/src/HttpController/Api/ResponseMapper/PlayedResponseMapper.php b/src/HttpController/Api/ResponseMapper/PlayedResponseMapper.php new file mode 100644 index 00000000..ccf1b293 --- /dev/null +++ b/src/HttpController/Api/ResponseMapper/PlayedResponseMapper.php @@ -0,0 +1,33 @@ +mapPlayedEntry($playedEntryData); + + $playedEntries->add($playedEntry); + } + + return $playedEntries; + } + + private function mapPlayedEntry(array $playedEntryData) : PlayedEntryDto + { + $movie = $this->movieResponseMapper->mapMovie($playedEntryData); + + return PlayedEntryDto::create($movie); + } +} diff --git a/tests/rest/api/played.http b/tests/rest/api/played.http new file mode 100644 index 00000000..642e3ad5 --- /dev/null +++ b/tests/rest/api/played.http @@ -0,0 +1,37 @@ +GET http://127.0.0.1/api/users/{{username}}/played/movies?limit=10 +Accept: */* +Cache-Control: no-cache +Content-Type: application/json +X-Auth-Token: {{xAuthToken}} + +#### + +PUT http://127.0.0.1/api/users/{{username}}/played/movies +Accept: */* +Cache-Control: no-cache +Content-Type: application/json +X-Auth-Token: {{xAuthToken}} + +//[{"movieId" : 1, "watchedAt" : "2011-05-06", "plays" : 1, "comment" : "comment"}] + +#### + +POST http://127.0.0.1/api/users/{{username}}/played/movies +Accept: */* +Cache-Control: no-cache +Content-Type: application/json +X-Auth-Token: {{xAuthToken}} + +//[{"movieId" : 1, "watchedAt" : "2011-05-06"}] + +#### + +DELETE http://127.0.0.1/api/users/{{username}}/played/movies +Accept: */* +Cache-Control: no-cache +Content-Type: application/json +X-Auth-Token: {{xAuthToken}} + +//[{"movieId" : 1, "watchedAt" : "2011-05-06"}] + +#### From 8b0a2d8fcfbb22af38ab6c58f61c1af9f0ca0183 Mon Sep 17 00:00:00 2001 From: Lee Peuker Date: Wed, 13 Sep 2023 20:59:08 +0200 Subject: [PATCH 4/5] Implement played api GET and DELETE endpoint --- docs/openapi.json | 317 ++++++++++++++++++ src/Domain/Movie/History/MovieHistoryApi.php | 25 ++ .../Movie/History/MovieHistoryRepository.php | 5 + src/Domain/Movie/MovieApi.php | 16 + src/Domain/Movie/MovieRepository.php | 16 + src/HttpController/Api/Dto/MovieDto.php | 5 + src/HttpController/Api/Dto/PlayedEntryDto.php | 8 +- src/HttpController/Api/Dto/WatchDateDto.php | 34 ++ .../Api/Dto/WatchDateDtoList.php | 28 ++ src/HttpController/Api/PlayedController.php | 13 +- .../ResponseMapper/PlayedResponseMapper.php | 29 +- 11 files changed, 483 insertions(+), 13 deletions(-) create mode 100644 src/HttpController/Api/Dto/WatchDateDto.php create mode 100644 src/HttpController/Api/Dto/WatchDateDtoList.php diff --git a/docs/openapi.json b/docs/openapi.json index c6b1a92c..ba843924 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -506,6 +506,323 @@ } ] } + }, + "\/users\/{username}\/played\/movies": { + "get": { + "tags": [ + "Played" + ], + "summary": "Get played movies of user", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "Name of user", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "search", + "in": "query", + "description": "Search term", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "page", + "in": "query", + "description": "Page", + "required": false, + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "limit", + "in": "query", + "description": "Limit", + "required": false, + "schema": { + "type": "integer", + "default": 24 + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Sort by", + "required": false, + "schema": { + "type": "string", + "default": "title", + "enum": [ + "addedAt", + "rating", + "releaseDate", + "runtime", + "title" + ] + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort order", + "required": false, + "schema": { + "type": "string", + "default": "asc", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "releaseYear", + "in": "query", + "description": "Release year", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "played": { + "type": "array", + "items": { + "type": "object", + "properties": { + "movie": { + "$ref": "#/components/schemas/movie" + }, + "watchedDates": { + "type": "array", + "items": { + "type": "object", + "properties": { + "date": { + "$ref": "#/components/schemas/dateNullable" + }, + "plays": { + "$ref": "#/components/schemas/plays" + }, + "comment": { + "$ref": "#/components/schemas/comment" + } + } + } + } + } + } + }, + "currentPage": { + "type": "integer", + "example": 1 + }, + "maxPage": { + "type": "integer", + "example": 10 + } + } + } + } + } + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "security": [ + { + "authToken": [] + } + ] + }, + "post": { + "tags": [ + "Played" + ], + "summary": "Add movie plays to user", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "Name of user", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "movaryId": { + "$ref": "#/components/schemas/id" + }, + "watchedAt": { + "$ref": "#/components/schemas/dateNullable" + }, + "plays": { + "$ref": "#/components/schemas/playsOptional" + }, + "comment": { + "$ref": "#/components/schemas/commentOptional" + } + } + } + } + } + } + }, + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "security": [ + { + "authToken": [] + } + ] + }, + "put": { + "tags": [ + "Played" + ], + "summary": "Replace movie plays for user", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "Name of user", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "movaryId": { + "$ref": "#/components/schemas/id" + }, + "watchedAt": { + "$ref": "#/components/schemas/dateNullable" + }, + "plays": { + "$ref": "#/components/schemas/plays" + }, + "comment": { + "$ref": "#/components/schemas/comment" + } + } + } + } + } + } + }, + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "security": [ + { + "authToken": [] + } + ] + }, + "delete": { + "tags": [ + "Played" + ], + "summary": "Delete movie plays from user", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "Name of user", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "movaryId": { + "$ref": "#/components/schemas/id" + } + } + } + } + } + } + }, + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "security": [ + { + "authToken": [] + } + ] + } } }, "components": { diff --git a/src/Domain/Movie/History/MovieHistoryApi.php b/src/Domain/Movie/History/MovieHistoryApi.php index 81b99820..a02aebef 100644 --- a/src/Domain/Movie/History/MovieHistoryApi.php +++ b/src/Domain/Movie/History/MovieHistoryApi.php @@ -45,6 +45,17 @@ public function deleteByUserId(int $userId) : void $this->repository->deleteByUserId($userId); } + public function deleteHistoryById(int $movieId, int $userId) : void + { + $this->repository->deleteHistoryById($movieId, $userId); + + if ($this->userApi->fetchUser($userId)->hasJellyfinSyncEnabled() === false) { + return; + } + + $this->jobQueueApi->addJellyfinExportMoviesJob($userId, [$movieId]); + } + public function deleteHistoryByIdAndDate(int $movieId, int $userId, ?Date $watchedAt) : void { $this->repository->deleteHistoryByIdAndDate($movieId, $userId, $watchedAt); @@ -401,6 +412,20 @@ public function fetchPlayedMoviesPaginated( return $this->urlGenerator->replacePosterPathWithImageSrcUrl($movies); } + public function fetchWatchDatesForMovieIds(int $userId, array $movieIds) : array + { + $watchDates = []; + + foreach ($this->movieRepository->fetchWatchDatesForMovieIds($userId, $movieIds) as $watchDateData) { + $watchDates[$watchDateData['movie_id']][$watchDateData['watched_at']] = [ + 'plays' => $watchDateData['plays'], + 'comment' => $watchDateData['comment'], + ]; + } + + return $watchDates; + } + public function fetchWatchDatesOrderedByWatchedAtDesc(int $userId) : array { return $this->movieRepository->fetchWatchDatesOrderedByWatchedAtDesc($userId); diff --git a/src/Domain/Movie/History/MovieHistoryRepository.php b/src/Domain/Movie/History/MovieHistoryRepository.php index 6998be27..c409befb 100644 --- a/src/Domain/Movie/History/MovieHistoryRepository.php +++ b/src/Domain/Movie/History/MovieHistoryRepository.php @@ -38,6 +38,11 @@ public function deleteByUserId(int $userId) : void $this->dbConnection->delete('movie_user_watch_dates', ['user_id' => $userId]); } + public function deleteHistoryById(int $movieId, int $userId) : void + { + $this->dbConnection->delete('movie_user_watch_dates', ['user_id' => $userId, 'movie_id' => $movieId]); + } + public function deleteHistoryByIdAndDate(int $movieId, int $userId, ?Date $watchedAt) : void { if ($watchedAt === null) { diff --git a/src/Domain/Movie/MovieApi.php b/src/Domain/Movie/MovieApi.php index d05e36c7..d4f5038b 100644 --- a/src/Domain/Movie/MovieApi.php +++ b/src/Domain/Movie/MovieApi.php @@ -106,6 +106,11 @@ public function create( ); } + public function deleteHistoryById(int $movieId, int $userId) : void + { + $this->historyApi->deleteHistoryById($movieId, $userId); + } + public function deleteHistoryByIdAndDate(int $movieId, int $userId, ?Date $watchedAt) : void { $this->historyApi->deleteHistoryByIdAndDate($movieId, $userId, $watchedAt); @@ -266,6 +271,17 @@ public function fetchUniqueWatchedMoviesPaginated( ); } + public function fetchWatchDatesForMovies(int $userId, array $playedEntries) : array + { + $movieIds = []; + + foreach ($playedEntries as $playedEntry) { + $movieIds[] = $playedEntry['id']; + } + + return $this->historyApi->fetchWatchDatesForMovieIds($userId, $movieIds); + } + public function fetchWatchDatesOrderedByWatchedAtDesc(int $userId) : array { return $this->historyApi->fetchWatchDatesOrderedByWatchedAtDesc($userId); diff --git a/src/Domain/Movie/MovieRepository.php b/src/Domain/Movie/MovieRepository.php index 45b05578..5997edc5 100644 --- a/src/Domain/Movie/MovieRepository.php +++ b/src/Domain/Movie/MovieRepository.php @@ -304,6 +304,22 @@ public function fetchHistoryCount(int $userId, ?string $searchTerm = null) : int )[0]; } + public function fetchWatchDatesForMovieIds(int $userId, array $movieIds) : array + { + $placeholders = trim(str_repeat('?, ', count($movieIds)), ', '); + + return $this->dbConnection->fetchAllAssociative( + "SELECT watched_at, plays, comment, movie_id + FROM movie_user_watch_dates + WHERE user_id = ? and movie_id in ($placeholders) + ORDER BY watched_at DESC", + [ + $userId, + ...$movieIds + ], + ); + } + public function fetchWatchDatesOrderedByWatchedAtDesc(int $userId) : array { return $this->dbConnection->fetchAllAssociative( diff --git a/src/HttpController/Api/Dto/MovieDto.php b/src/HttpController/Api/Dto/MovieDto.php index 8e7417d2..1907f24d 100644 --- a/src/HttpController/Api/Dto/MovieDto.php +++ b/src/HttpController/Api/Dto/MovieDto.php @@ -75,6 +75,11 @@ public static function create( ); } + public function getId() : int + { + return $this->id; + } + public function jsonSerialize() : array { return [ diff --git a/src/HttpController/Api/Dto/PlayedEntryDto.php b/src/HttpController/Api/Dto/PlayedEntryDto.php index cd111f03..af04bbb8 100644 --- a/src/HttpController/Api/Dto/PlayedEntryDto.php +++ b/src/HttpController/Api/Dto/PlayedEntryDto.php @@ -2,24 +2,24 @@ namespace Movary\HttpController\Api\Dto; -use Movary\ValueObject\DateTime; - class PlayedEntryDto implements \JsonSerializable { public function __construct( private readonly MovieDto $movieDto, + private readonly WatchDateDtoList $watchDates, ) { } - public static function create(MovieDto $movieDto) : self + public static function create(MovieDto $movieDto, WatchDateDtoList $watchDates) : self { - return new self($movieDto); + return new self($movieDto, $watchDates); } public function jsonSerialize() : array { return [ 'movie' => $this->movieDto, + 'watchDates' => $this->watchDates ]; } } diff --git a/src/HttpController/Api/Dto/WatchDateDto.php b/src/HttpController/Api/Dto/WatchDateDto.php new file mode 100644 index 00000000..389505b4 --- /dev/null +++ b/src/HttpController/Api/Dto/WatchDateDto.php @@ -0,0 +1,34 @@ +watchDate; + } + + public function jsonSerialize() : array + { + return [ + 'date' => $this->watchDate, + 'plays' => $this->plays, + 'comment' => $this->comment, + ]; + } +} diff --git a/src/HttpController/Api/Dto/WatchDateDtoList.php b/src/HttpController/Api/Dto/WatchDateDtoList.php new file mode 100644 index 00000000..6a678bd6 --- /dev/null +++ b/src/HttpController/Api/Dto/WatchDateDtoList.php @@ -0,0 +1,28 @@ +getWatchDate() == $dto->getWatchDate()) { + throw new \RuntimeException('Watch date must be unique'); + } + } + + $this->data[] = $dto; + } +} diff --git a/src/HttpController/Api/PlayedController.php b/src/HttpController/Api/PlayedController.php index f49804e4..54ef2b5a 100644 --- a/src/HttpController/Api/PlayedController.php +++ b/src/HttpController/Api/PlayedController.php @@ -28,7 +28,7 @@ public function addToPlayed(Request $request) : Response $userId = $this->requestMapper->mapUsernameFromRoute($request)->getId(); $watchlistAdditions = Json::decode($request->getBody()); - // TODO add movie plays + // TODO return Response::createNoContent(); } @@ -39,10 +39,9 @@ public function deleteFromPlayed(Request $request) : Response $historyAdditions = Json::decode($request->getBody()); foreach ($historyAdditions as $historyAddition) { - $this->movieApi->deleteHistoryByIdAndDate( + $this->movieApi->deleteHistoryById( (int)$historyAddition['movaryId'], $userId, - isset($historyAddition['watchedAt']) === true ? Date::createFromString($historyAddition['watchedAt']) : null, ); } @@ -53,7 +52,7 @@ public function getPlayed(Request $request) : Response { $requestData = $this->playedRequestMapper->mapRequest($request); - $watchlistEntries = $this->movieApi->fetchPlayedMoviesPaginated( + $playedEntries = $this->movieApi->fetchPlayedMoviesPaginated( $requestData->getRequestedUserId(), $requestData->getLimit(), $requestData->getPage(), @@ -65,6 +64,8 @@ public function getPlayed(Request $request) : Response $requestData->getGenre(), ); + $watchDates = $this->movieApi->fetchWatchDatesForMovies($requestData->getRequestedUserId(), $playedEntries); + $watchlistCount = $this->movieApi->fetchPlayedMoviesCount( $requestData->getRequestedUserId(), $requestData->getSearchTerm(), @@ -81,7 +82,7 @@ public function getPlayed(Request $request) : Response return Response::createJson( Json::encode([ - 'played' => $this->playedResponseMapper->mapPlayedEntries($watchlistEntries), + 'played' => $this->playedResponseMapper->mapPlayedEntries($playedEntries, $watchDates), 'currentPage' => $paginationElements->getCurrentPage(), 'maxPage' => $paginationElements->getMaxPage(), ]), @@ -93,6 +94,8 @@ public function updatePlayed(Request $request) : Response $userId = $this->requestMapper->mapUsernameFromRoute($request)->getId(); $historyAdditions = Json::decode($request->getBody()); + // TODO + foreach ($historyAdditions as $historyAddition) { $this->movieApi->replaceHistoryForMovieByDate( (int)$historyAddition['movaryId'], diff --git a/src/HttpController/Api/ResponseMapper/PlayedResponseMapper.php b/src/HttpController/Api/ResponseMapper/PlayedResponseMapper.php index ccf1b293..a1b49ebf 100644 --- a/src/HttpController/Api/ResponseMapper/PlayedResponseMapper.php +++ b/src/HttpController/Api/ResponseMapper/PlayedResponseMapper.php @@ -4,6 +4,9 @@ use Movary\HttpController\Api\Dto\PlayedEntryDto; use Movary\HttpController\Api\Dto\PlayedEntryDtoList; +use Movary\HttpController\Api\Dto\WatchDateDto; +use Movary\HttpController\Api\Dto\WatchDateDtoList; +use Movary\ValueObject\Date; class PlayedResponseMapper { @@ -11,12 +14,12 @@ public function __construct(private readonly MovieResponseMapper $movieResponseM { } - public function mapPlayedEntries(array $playedEntriesData) : PlayedEntryDtoList + public function mapPlayedEntries(array $playedEntriesData, array $watchDatesData) : PlayedEntryDtoList { $playedEntries = PlayedEntryDtoList::create(); foreach ($playedEntriesData as $playedEntryData) { - $playedEntry = $this->mapPlayedEntry($playedEntryData); + $playedEntry = $this->mapPlayedEntry($playedEntryData, $watchDatesData); $playedEntries->add($playedEntry); } @@ -24,10 +27,28 @@ public function mapPlayedEntries(array $playedEntriesData) : PlayedEntryDtoList return $playedEntries; } - private function mapPlayedEntry(array $playedEntryData) : PlayedEntryDto + private function mapPlayedEntry(array $playedEntryData, array $watchDatesData) : PlayedEntryDto { $movie = $this->movieResponseMapper->mapMovie($playedEntryData); + $watchDates = $this->mapWatchDates($movie->getId(), $watchDatesData); - return PlayedEntryDto::create($movie); + return PlayedEntryDto::create($movie, $watchDates); + } + + private function mapWatchDates(int $movieId, array $watchDatesData) : WatchDateDtoList + { + $watchDates = WatchDateDtoList::create(); + + foreach ($watchDatesData[$movieId] as $watchDate => $watchDateData) { + $watchDate = WatchDateDto::create( + empty($watchDate) === false ? Date::createFromString($watchDate) : null, + $watchDateData['plays'], + $watchDateData['comment'], + ); + + $watchDates->add($watchDate); + } + + return $watchDates; } } From 4509a80b022e5d3d10c4469c147762af4b88b35f Mon Sep 17 00:00:00 2001 From: Lee Peuker Date: Thu, 14 Sep 2023 19:22:39 +0200 Subject: [PATCH 5/5] Implement missing endpoints and add docs --- docs/openapi.json | 59 +++++++++++----- src/HttpController/Api/PlayedController.php | 75 +++++++++++++++------ tests/rest/api/history.http | 6 +- tests/rest/api/played.http | 41 ++++++++++- 4 files changed, 137 insertions(+), 44 deletions(-) diff --git a/docs/openapi.json b/docs/openapi.json index ba843924..d1dbd1ff 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -513,6 +513,7 @@ "Played" ], "summary": "Get played movies of user", + "description": "Get all played movies and their watch dates.", "parameters": [ { "name": "username", @@ -660,6 +661,7 @@ "Played" ], "summary": "Add movie plays to user", + "description": "Create or update the provided watch dates for the specified movies.", "parameters": [ { "name": "username", @@ -682,14 +684,22 @@ "movaryId": { "$ref": "#/components/schemas/id" }, - "watchedAt": { - "$ref": "#/components/schemas/dateNullable" - }, - "plays": { - "$ref": "#/components/schemas/playsOptional" - }, - "comment": { - "$ref": "#/components/schemas/commentOptional" + "watchDates": { + "type": "array", + "items": { + "type": "object", + "properties": { + "watchedAt": { + "$ref": "#/components/schemas/dateNullable" + }, + "plays": { + "$ref": "#/components/schemas/playsOptional" + }, + "comment": { + "$ref": "#/components/schemas/commentOptional" + } + } + } } } } @@ -719,6 +729,7 @@ "Played" ], "summary": "Replace movie plays for user", + "description": "Create or replace the provided watch dates for the specified movies.", "parameters": [ { "name": "username", @@ -741,14 +752,22 @@ "movaryId": { "$ref": "#/components/schemas/id" }, - "watchedAt": { - "$ref": "#/components/schemas/dateNullable" - }, - "plays": { - "$ref": "#/components/schemas/plays" - }, - "comment": { - "$ref": "#/components/schemas/comment" + "watchDates": { + "type": "array", + "items": { + "type": "object", + "properties": { + "watchedAt": { + "$ref": "#/components/schemas/dateNullable" + }, + "plays": { + "$ref": "#/components/schemas/plays" + }, + "comment": { + "$ref": "#/components/schemas/comment" + } + } + } } } } @@ -778,6 +797,7 @@ "Played" ], "summary": "Delete movie plays from user", + "description": "Delete all watch dates of specified movies if no specific watch dates are provided.", "parameters": [ { "name": "username", @@ -799,6 +819,13 @@ "properties": { "movaryId": { "$ref": "#/components/schemas/id" + }, + "watchDates": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dateNullable" + }, + "required": false } } } diff --git a/src/HttpController/Api/PlayedController.php b/src/HttpController/Api/PlayedController.php index 54ef2b5a..1744a7f5 100644 --- a/src/HttpController/Api/PlayedController.php +++ b/src/HttpController/Api/PlayedController.php @@ -26,9 +26,22 @@ public function __construct( public function addToPlayed(Request $request) : Response { $userId = $this->requestMapper->mapUsernameFromRoute($request)->getId(); - $watchlistAdditions = Json::decode($request->getBody()); - - // TODO + $playedAdditions = Json::decode($request->getBody()); + + foreach ($playedAdditions as $playAddition) { + $movieId = (int)$playAddition['movaryId']; + $watchDates = $playAddition['watchDates'] ?? []; + + foreach ($watchDates as $watchDate) { + $this->movieApi->addPlaysForMovieOnDate( + $movieId, + $userId, + $watchDate['watchedAt'] !== null ? Date::createFromString($watchDate['watchedAt']) : null, + $watchDate['plays'] ?? 1, + $watchDate['comment'] ?? null, + ); + } + } return Response::createNoContent(); } @@ -36,13 +49,28 @@ public function addToPlayed(Request $request) : Response public function deleteFromPlayed(Request $request) : Response { $userId = $this->requestMapper->mapUsernameFromRoute($request)->getId(); - $historyAdditions = Json::decode($request->getBody()); - - foreach ($historyAdditions as $historyAddition) { - $this->movieApi->deleteHistoryById( - (int)$historyAddition['movaryId'], - $userId, - ); + $playedDeletions = Json::decode($request->getBody()); + + foreach ($playedDeletions as $playedDeletion) { + $movieId = (int)$playedDeletion['movaryId']; + $watchDates = $playedDeletion['watchDates'] ?? []; + + if (count($watchDates) === 0) { + $this->movieApi->deleteHistoryById( + $movieId, + $userId, + ); + + continue; + } + + foreach ($watchDates as $date) { + $this->movieApi->deleteHistoryByIdAndDate( + $movieId, + $userId, + empty($date) === true ? null : Date::createFromString($date), + ); + } } return Response::createNoContent(); @@ -92,18 +120,21 @@ public function getPlayed(Request $request) : Response public function updatePlayed(Request $request) : Response { $userId = $this->requestMapper->mapUsernameFromRoute($request)->getId(); - $historyAdditions = Json::decode($request->getBody()); - - // TODO - - foreach ($historyAdditions as $historyAddition) { - $this->movieApi->replaceHistoryForMovieByDate( - (int)$historyAddition['movaryId'], - $userId, - isset($historyAddition['watchedAt']) === true ? Date::createFromString($historyAddition['watchedAt']) : null, - $historyAddition['plays'], - $historyAddition['comment'], - ); + $playedUpdates = Json::decode($request->getBody()); + + foreach ($playedUpdates as $playedUpdate) { + $movieId = (int)$playedUpdate['movaryId']; + $watchDates = $playedUpdate['watchDates'] ?? []; + + foreach ($watchDates as $watchDate) { + $this->movieApi->replaceHistoryForMovieByDate( + $movieId, + $userId, + $watchDate['watchedAt'] !== null ? Date::createFromString($watchDate['watchedAt']) : null, + $watchDate['plays'], + $watchDate['comment'], + ); + } } return Response::createNoContent(); diff --git a/tests/rest/api/history.http b/tests/rest/api/history.http index 1c7ea76b..3253c379 100644 --- a/tests/rest/api/history.http +++ b/tests/rest/api/history.http @@ -12,7 +12,7 @@ Cache-Control: no-cache Content-Type: application/json X-Auth-Token: {{xAuthToken}} -[{"movieId" : 1, "watchedAt" : "2011-05-06", "plays" : 1, "comment" : "comment"}] +[{"movaryId" : 1, "watchedAt" : "2011-05-06", "plays" : 1, "comment" : "comment"}] #### @@ -22,7 +22,7 @@ Cache-Control: no-cache Content-Type: application/json X-Auth-Token: {{xAuthToken}} -[{"movieId" : 1, "watchedAt" : "2011-05-06"}] +[{"movaryId" : 1, "watchedAt" : "2011-05-06"}] #### @@ -32,6 +32,6 @@ Cache-Control: no-cache Content-Type: application/json X-Auth-Token: {{xAuthToken}} -[{"movieId" : 1, "watchedAt" : "2011-05-06"}] +[{"movaryId" : 1, "watchedAt" : "2011-05-06"}] #### diff --git a/tests/rest/api/played.http b/tests/rest/api/played.http index 642e3ad5..9177594a 100644 --- a/tests/rest/api/played.http +++ b/tests/rest/api/played.http @@ -12,7 +12,23 @@ Cache-Control: no-cache Content-Type: application/json X-Auth-Token: {{xAuthToken}} -//[{"movieId" : 1, "watchedAt" : "2011-05-06", "plays" : 1, "comment" : "comment"}] +[ + { + "movaryId": 1, + "watchDates": [ + { + "watchedAt": null, + "plays": 2, + "comment": "Test comment" + }, + { + "watchedAt": "2024-05-06", + "plays": 2, + "comment": "Test comment" + } + ] + } +] #### @@ -22,7 +38,21 @@ Cache-Control: no-cache Content-Type: application/json X-Auth-Token: {{xAuthToken}} -//[{"movieId" : 1, "watchedAt" : "2011-05-06"}] +[ + { + "movaryId": 1, + "watchDates": [ + { + "watchedAt": null + }, + { + "watchedAt": "2024-05-06", + "plays": 2, + "comment": "Test comment" + } + ] + } +] #### @@ -32,6 +62,11 @@ Cache-Control: no-cache Content-Type: application/json X-Auth-Token: {{xAuthToken}} -//[{"movieId" : 1, "watchedAt" : "2011-05-06"}] +[ + { + "movaryId": 1, + "watchDates": [] + } +] ####