From b1a68dfbbbe71405711f403500c0bf2bfb767df1 Mon Sep 17 00:00:00 2001 From: JVT038 <47184046+JVT038@users.noreply.github.com> Date: Fri, 23 Feb 2024 21:47:07 +0100 Subject: [PATCH 1/3] Moved JobController to API endpoints and changed the URLs in the frontend --- bootstrap.php | 2 +- public/js/component/modal-job.js | 2 +- public/js/settings-integration-jellyfin.js | 4 +-- public/js/settings-integration-plex.js | 2 +- settings/routes.php | 31 +++++++++---------- src/Factory.php | 3 +- .../{Web => Api}/JobController.php | 5 +-- .../Middleware/UserHasJellyfinToken.php | 3 +- .../settings-integration-letterboxd.html.twig | 4 +-- .../page/settings-integration-trakt.html.twig | 4 +-- 10 files changed, 27 insertions(+), 33 deletions(-) rename src/HttpController/{Web => Api}/JobController.php (97%) rename src/HttpController/{Web => Api}/Middleware/UserHasJellyfinToken.php (87%) diff --git a/bootstrap.php b/bootstrap.php index 6fb46d622..3f59182ba 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -16,7 +16,7 @@ \Movary\Service\Export\ExportService::class => DI\factory([Factory::class, 'createExportService']), \Movary\HttpController\Api\OpenApiController::class => DI\factory([Factory::class, 'createOpenApiController']), \Movary\HttpController\Web\CreateUserController::class => DI\factory([Factory::class, 'createCreateUserController']), - \Movary\HttpController\Web\JobController::class => DI\factory([Factory::class, 'createJobController']), + \Movary\HttpController\Api\JobController::class => DI\factory([Factory::class, 'createJobController']), \Movary\HttpController\Web\LandingPageController::class => DI\factory([Factory::class, 'createLandingPageController']), \Movary\HttpController\Web\Middleware\ServerHasRegistrationEnabled::class => DI\factory([Factory::class, 'createMiddlewareServerHasRegistrationEnabled']), \Movary\ValueObject\Http\Request::class => DI\factory([Factory::class, 'createCurrentHttpRequest']), diff --git a/public/js/component/modal-job.js b/public/js/component/modal-job.js index 7ff077f4e..d3e48f687 100644 --- a/public/js/component/modal-job.js +++ b/public/js/component/modal-job.js @@ -67,7 +67,7 @@ function setJobModalTitle(jobType) { async function fetchJobs(jobType) { - const response = await fetch('/jobs?type=' + jobType) + const response = await fetch('/api/jobs?type=' + jobType) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) diff --git a/public/js/settings-integration-jellyfin.js b/public/js/settings-integration-jellyfin.js index 3acc213b5..f7df541aa 100644 --- a/public/js/settings-integration-jellyfin.js +++ b/public/js/settings-integration-jellyfin.js @@ -266,7 +266,7 @@ async function updateSyncOptions() { async function exportJellyfin() { const response = await fetch( - '/jobs/schedule/jellyfin-export-history', + '/api/jobs/schedule/jellyfin-export-history', {'method': 'get'} ).catch(function (error) { addAlert('alertJellyfinExportHistoryDiv', 'History export could not be scheduled', 'danger') @@ -296,7 +296,7 @@ async function exportJellyfin() { async function importJellyfin() { const response = await fetch( - '/jobs/schedule/jellyfin-import-history', + '/api/jobs/schedule/jellyfin-import-history', {'method': 'get'} ).catch(function (error) { addAlert('alertJellyfinImportHistoryDiv', 'History import could not be scheduled', 'danger') diff --git a/public/js/settings-integration-plex.js b/public/js/settings-integration-plex.js index edc34cc4f..4993b447f 100644 --- a/public/js/settings-integration-plex.js +++ b/public/js/settings-integration-plex.js @@ -226,7 +226,7 @@ document.getElementById('plexServerUrlInput').addEventListener('input', function async function importPlexWatchlist() { const response = await fetch( - '/jobs/schedule/plex-watchlist-sync', + '/api/jobs/schedule/plex-watchlist-sync', {'method': 'get'} ).catch(function (error) { addAlert('alertPlexWatchlistImportDiv', 'Watchlist import could not be scheduled', 'danger') diff --git a/settings/routes.php b/settings/routes.php index 7c49d23d3..9f65c325b 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -41,23 +41,6 @@ function addWebRoutes(RouterService $routerService, FastRoute\RouteCollector $ro $routes->add('POST', '/jellyfin/{id:.+}', [Web\JellyfinController::class, 'handleJellyfinWebhook']); $routes->add('POST', '/emby/{id:.+}', [Web\EmbyController::class, 'handleEmbyWebhook']); - ############# - # Job Queue # - ############# - $routes->add('GET', '/jobs', [Web\JobController::class, 'getJobs'], [Web\Middleware\UserIsAuthenticated::class]); - $routes->add('GET', '/job-queue/purge-processed', [Web\JobController::class, 'purgeProcessedJobs'], [Web\Middleware\UserIsAuthenticated::class]); - $routes->add('GET', '/job-queue/purge-all', [Web\JobController::class, 'purgeAllJobs'], [Web\Middleware\UserIsAuthenticated::class]); - $routes->add('GET', '/jobs/schedule/trakt-history-sync', [Web\JobController::class, 'scheduleTraktHistorySync'], [Web\Middleware\UserIsAuthenticated::class]); - $routes->add('GET', '/jobs/schedule/trakt-ratings-sync', [Web\JobController::class, 'scheduleTraktRatingsSync'], [Web\Middleware\UserIsAuthenticated::class]); - $routes->add('POST', '/jobs/schedule/letterboxd-diary-sync', [Web\JobController::class, 'scheduleLetterboxdDiaryImport'], [Web\Middleware\UserIsAuthenticated::class]); - $routes->add('POST', '/jobs/schedule/letterboxd-ratings-sync', [Web\JobController::class, 'scheduleLetterboxdRatingsImport'], [Web\Middleware\UserIsAuthenticated::class]); - $routes->add('GET', '/jobs/schedule/plex-watchlist-sync', [Web\JobController::class, 'schedulePlexWatchlistImport'], [Web\Middleware\UserIsAuthenticated::class]); - $routes->add('GET', '/jobs/schedule/jellyfin-import-history', [Web\JobController::class, 'scheduleJellyfinImportHistory'], [Web\Middleware\UserIsAuthenticated::class]); - $routes->add('GET', '/jobs/schedule/jellyfin-export-history', [Web\JobController::class, 'scheduleJellyfinExportHistory'], [ - Web\Middleware\UserIsAuthenticated::class, - Web\Middleware\UserHasJellyfinToken::class - ]); - ############ # Settings # ############ @@ -229,5 +212,19 @@ function addApiRoutes(RouterService $routerService, FastRoute\RouteCollector $ro $routes->add('GET', '/feed/radarr/{id:.+}', [Api\RadarrController::class, 'renderRadarrFeed']); + ############# + # Job Queue # + ############# + $routes->add('GET', '/jobs', [Api\JobController::class, 'getJobs'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('GET', '/job-queue/purge-processed', [Api\JobController::class, 'purgeProcessedJobs'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('GET', '/job-queue/purge-all', [Api\JobController::class, 'purgeAllJobs'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('GET', '/jobs/schedule/trakt-history-sync', [Api\JobController::class, 'scheduleTraktHistorySync'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('GET', '/jobs/schedule/trakt-ratings-sync', [Api\JobController::class, 'scheduleTraktRatingsSync'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('POST', '/jobs/schedule/letterboxd-diary-sync', [Api\JobController::class, 'scheduleLetterboxdDiaryImport'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('POST', '/jobs/schedule/letterboxd-ratings-sync', [Api\JobController::class, 'scheduleLetterboxdRatingsImport'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('GET', '/jobs/schedule/plex-watchlist-sync', [Api\JobController::class, 'schedulePlexWatchlistImport'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('GET', '/jobs/schedule/jellyfin-import-history', [Api\JobController::class, 'scheduleJellyfinImportHistory'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('GET', '/jobs/schedule/jellyfin-export-history', [Api\JobController::class, 'scheduleJellyfinExportHistory'], [Api\Middleware\IsAuthenticated::class, Api\Middleware\UserHasJellyfinToken::class]); + $routerService->addRoutesToRouteCollector($routeCollector, $routes); } diff --git a/src/Factory.php b/src/Factory.php index b59fce2ad..6befef78c 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -13,16 +13,15 @@ use Movary\Api\Trakt\Cache\User\Movie\Watched; use Movary\Api\Trakt\TraktApi; use Movary\Api\Trakt\TraktClient; -use Movary\Command; use Movary\Command\CreatePublicStorageLink; use Movary\Domain\Movie\MovieApi; use Movary\Domain\Movie\Watchlist\MovieWatchlistApi; use Movary\Domain\User; use Movary\Domain\User\Service\Authentication; use Movary\Domain\User\UserApi; +use Movary\HttpController\Api\JobController; use Movary\HttpController\Api\OpenApiController; use Movary\HttpController\Web\CreateUserController; -use Movary\HttpController\Web\JobController; use Movary\HttpController\Web\LandingPageController; use Movary\JobQueue\JobQueueApi; use Movary\JobQueue\JobQueueScheduler; diff --git a/src/HttpController/Web/JobController.php b/src/HttpController/Api/JobController.php similarity index 97% rename from src/HttpController/Web/JobController.php rename to src/HttpController/Api/JobController.php index 9b53d6995..f4f190de0 100644 --- a/src/HttpController/Web/JobController.php +++ b/src/HttpController/Api/JobController.php @@ -1,11 +1,8 @@ -
+
Import History

Upload the diary.csv file

@@ -62,7 +62,7 @@
- +
Import Ratings

Upload the ratings.csv file

diff --git a/templates/page/settings-integration-trakt.html.twig b/templates/page/settings-integration-trakt.html.twig index 489737908..d78ce3509 100644 --- a/templates/page/settings-integration-trakt.html.twig +++ b/templates/page/settings-integration-trakt.html.twig @@ -83,7 +83,7 @@
Import history + href="/api/jobs/schedule/trakt-history-sync">Import history
@@ -96,7 +96,7 @@ Import ratings + href="/api/jobs/schedule/trakt-ratings-sync" style="margin-top: 1rem">Import ratings
From e42d0e2718b26cb45f4004634f3bd32b03e8c3c3 Mon Sep 17 00:00:00 2001 From: JVT038 <47184046+JVT038@users.noreply.github.com> Date: Wed, 28 Feb 2024 17:44:42 +0100 Subject: [PATCH 2/3] Completed the OpenAPI specs with the new job management endpoints. Signed-off-by: JVT038 <47184046+JVT038@users.noreply.github.com> --- docs/openapi.json | 279 ++++++++++++++++++ settings/routes.php | 15 +- src/HttpController/Api/JobController.php | 100 +++---- .../settings-integration-letterboxd.html.twig | 2 +- tests/rest/api/jobs.http | 5 + 5 files changed, 332 insertions(+), 69 deletions(-) create mode 100644 tests/rest/api/jobs.http diff --git a/docs/openapi.json b/docs/openapi.json index ce1054b42..ea6b99956 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -1066,6 +1066,282 @@ } } }, + "/jobs": { + "get": { + "tags": [ + "Job management" + ], + "summary": "Get all jobs of a specific type", + "description": "Get all jobs in the queue with a specific type, triggered by a specific user", + "parameters": [ + { + "in": "query", + "name": "type", + "schema": { + "type": "string" + }, + "required": true, + "description": "The type of job that's requested" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "The response is an array containing all the jobs with a specific type.", + "example": [ + { + "type": "job_type", + "status": "in progress", + "userId": 1, + "createdAt": "2023-08-05 16:44:35", + "updatedAt": null + } + ] + } + } + } + }, + "403": { + "$ref": "#/components/responses/403" + } + }, + "security": [ + { + "token": [] + } + ] + } + }, + "/job-queue/purge-processed": { + "delete": { + "tags": [ + "Job management" + ], + "summary": "Delete processed jobs", + "description": "Delete processed jobs", + "responses": { + "204": { + "$ref": "#/components/responses/204-jobs" + }, + "403": { + "$ref": "#/components/responses/403" + } + }, + "security": [ + { + "token": [] + } + ] + } + }, + "/job-queue/purge-all": { + "delete": { + "tags": [ + "Job management" + ], + "summary": "Delete all jobs", + "description": "Delete all jobs, regardless of their status", + "responses": { + "204": { + "$ref": "#/components/responses/204-jobs" + }, + "403": { + "$ref": "#/components/responses/403" + } + }, + "security": [ + { + "token": [] + } + ] + } + }, + "/jobs/schedule/trakt-history-sync": { + "post": { + "tags": [ + "Job management" + ], + "summary": "Schedule a movie history import from Trakt.tv", + "description": "Schedule a job to import all watched movies from Trakt.tv, if the user has set Trakt.tv credentials in the settings.", + "responses": { + "204": { + "$ref": "#/components/responses/204-jobs" + }, + "403": { + "$ref": "#/components/responses/403" + } + }, + "security": [ + { + "token": [] + } + ] + } + }, + "/jobs/schedule/trakt-ratings-sync": { + "post": { + "tags": [ + "Job management" + ], + "summary": "Schedule a ratings import from Trakt.tv", + "description": "Schedule a job to import all ratings of movies from Trakt.tv, if the user has set Trakt.tv credentials in the settings.", + "responses": { + "204": { + "$ref": "#/components/responses/204-jobs" + }, + "403": { + "$ref": "#/components/responses/403" + } + }, + "security": [ + { + "token": [] + } + ] + } + }, + "/jobs/schedule/letterboxd-diary-sync": { + "post": { + "tags": [ + "Job management" + ], + "summary": "Schedule a diary import from Letterboxd", + "description": "Schedule a job to upload and import all diary data from Letterboxd", + "requestBody": { + "content": { + "text/csv": { + "schema": { + "type": "string", + "name": "letterboxdDiaryCsv", + "description": "The file of the letterboxd diary. It has to be in sent in a form parameter named 'letterboxdDiaryCsv', otherwise the request will fail." + } + } + } + }, + "responses": { + "204": { + "$ref": "#/components/responses/204-jobs" + }, + "400": { + "description": "An invalid Letterboxd diary has been uploaded or no file was uploaded" + }, + "403": { + "$ref": "#/components/responses/403" + } + }, + "security": [ + { + "token": [] + } + ] + } + }, + "/jobs/schedule/letterboxd-ratings-sync": { + "post": { + "tags": [ + "Job management" + ], + "summary": "Schedule a rating import from Letterboxd", + "description": "Schedule a job to upload and import all rating data from Letterboxd", + "requestBody": { + "content": { + "text/csv": { + "schema": { + "type": "string", + "name": "letterboxdRatingsCsv", + "description": "The file of the letterboxd ratings. It has to be in sent in a form parameter named 'letterboxdRatingsCsv', otherwise the request will fail." + } + } + } + }, + "responses": { + "204": { + "$ref": "#/components/responses/204-jobs" + }, + "400": { + "description": "An invalid Letterboxd ratings file been uploaded or no file was uploaded" + }, + "403": { + "$ref": "#/components/responses/403" + } + }, + "security": [ + { + "token": [] + } + ] + } + }, + "/jobs/schedule/plex-watchlist-sync": { + "post": { + "tags": [ + "Job management" + ], + "summary": "Schedule a job to import the watchlist from Plex.", + "description": "Schedule a job to import all movies from Plex's watchlist into Movary's watchlist", + "responses": { + "204": { + "$ref": "#/components/responses/204-jobs" + }, + "403": { + "$ref": "#/components/responses/403" + } + }, + "security": [ + { + "token": [] + } + ] + } + }, + "/jobs/schedule/jellyfin-import-history": { + "post": { + "tags": [ + "Job management" + ], + "summary": "Schedule a job to import the watch history from Jellyfin.", + "description": "Schedule a job to import all watched movies from Jellyfin", + "responses": { + "204": { + "$ref": "#/components/responses/204-jobs" + }, + "403": { + "$ref": "#/components/responses/403" + } + }, + "security": [ + { + "token": [] + } + ] + } + }, + "/jobs/schedule/jellyfin-export-history": { + "post": { + "tags": [ + "Job management" + ], + "summary": "Schedule a job to export the watch history to Jellyfin.", + "description": "Schedule a job to export all watched movies from Movary into Jellyfin", + "responses": { + "204": { + "$ref": "#/components/responses/204-jobs" + }, + "403": { + "$ref": "#/components/responses/403" + } + }, + "security": [ + { + "token": [] + } + ] + } + }, "/authentication/token": { "get": { "tags": [ @@ -1417,6 +1693,9 @@ "204": { "description": "Successful operation, response has no content" }, + "204-jobs": { + "description": "No response content. The job has been scheduled, but it hasn't necessarily already started running." + }, "400": { "description": "The request payload or header are not correct", "content": { diff --git a/settings/routes.php b/settings/routes.php index 9be6d8835..9e124eab3 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -216,15 +216,16 @@ function addApiRoutes(RouterService $routerService, FastRoute\RouteCollector $ro # Job Queue # ############# $routes->add('GET', '/jobs', [Api\JobController::class, 'getJobs'], [Api\Middleware\IsAuthenticated::class]); - $routes->add('GET', '/job-queue/purge-processed', [Api\JobController::class, 'purgeProcessedJobs'], [Api\Middleware\IsAuthenticated::class]); - $routes->add('GET', '/job-queue/purge-all', [Api\JobController::class, 'purgeAllJobs'], [Api\Middleware\IsAuthenticated::class]); - $routes->add('GET', '/jobs/schedule/trakt-history-sync', [Api\JobController::class, 'scheduleTraktHistorySync'], [Api\Middleware\IsAuthenticated::class]); - $routes->add('GET', '/jobs/schedule/trakt-ratings-sync', [Api\JobController::class, 'scheduleTraktRatingsSync'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('DELETE', '/job-queue/purge-processed', [Api\JobController::class, 'purgeProcessedJobs'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('DELETE', '/job-queue/purge-all', [Api\JobController::class, 'purgeAllJobs'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('POST', '/jobs/schedule/trakt-history-sync', [Api\JobController::class, 'scheduleTraktHistorySync'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('POST', '/jobs/schedule/trakt-ratings-sync', [Api\JobController::class, 'scheduleTraktRatingsSync'], [Api\Middleware\IsAuthenticated::class]); $routes->add('POST', '/jobs/schedule/letterboxd-diary-sync', [Api\JobController::class, 'scheduleLetterboxdDiaryImport'], [Api\Middleware\IsAuthenticated::class]); $routes->add('POST', '/jobs/schedule/letterboxd-ratings-sync', [Api\JobController::class, 'scheduleLetterboxdRatingsImport'], [Api\Middleware\IsAuthenticated::class]); - $routes->add('GET', '/jobs/schedule/plex-watchlist-sync', [Api\JobController::class, 'schedulePlexWatchlistImport'], [Api\Middleware\IsAuthenticated::class]); - $routes->add('GET', '/jobs/schedule/jellyfin-import-history', [Api\JobController::class, 'scheduleJellyfinImportHistory'], [Api\Middleware\IsAuthenticated::class]); - $routes->add('GET', '/jobs/schedule/jellyfin-export-history', [Api\JobController::class, 'scheduleJellyfinExportHistory'], [Api\Middleware\IsAuthenticated::class, Api\Middleware\UserHasJellyfinToken::class]); + $routes->add('POST', '/jobs/schedule/plex-watchlist-sync', [Api\JobController::class, 'schedulePlexWatchlistImport'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('POST', '/jobs/schedule/jellyfin-import-history', [Api\JobController::class, 'scheduleJellyfinImportHistory'], [Api\Middleware\IsAuthenticated::class]); + + $routes->add('POST', '/jobs/schedule/jellyfin-export-history', [Api\JobController::class, 'scheduleJellyfinExportHistory'], [Api\Middleware\IsAuthenticated::class, Api\Middleware\UserHasJellyfinToken::class]); $routerService->addRoutesToRouteCollector($routeCollector, $routes); } diff --git a/src/HttpController/Api/JobController.php b/src/HttpController/Api/JobController.php index f4f190de0..2bbb47c5c 100644 --- a/src/HttpController/Api/JobController.php +++ b/src/HttpController/Api/JobController.php @@ -30,8 +30,12 @@ public function getJobs(Request $request) : Response $parameters = $request->getGetParameters(); $jobType = JobType::createFromString($parameters['type']); + $token = $this->authenticationService->getUserIdByApiToken($request); + if(empty($token)) { + return Response::createUnauthorized(); + } - $jobs = $this->jobQueueApi->find($this->authenticationService->getCurrentUserId(), $jobType); + $jobs = $this->jobQueueApi->find($token, $jobType); return Response::createJson(Json::encode($jobs)); } @@ -40,88 +44,82 @@ public function purgeAllJobs() : Response { $this->jobQueueApi->purgeAllJobs(); - return Response::createSeeOther('/settings/server/jobs'); + return Response::createNoContent(); } public function purgeProcessedJobs() : Response { $this->jobQueueApi->purgeProcessedJobs(); - return Response::createSeeOther('/settings/server/jobs'); + return Response::createNoContent(); } public function scheduleLetterboxdDiaryImport(Request $request) : Response { $fileParameters = $request->getFileParameters(); - if (empty($fileParameters['diaryCsv']['tmp_name']) === true) { - throw new RuntimeException('Missing ratings csv file'); + if (empty($fileParameters['letterboxdDiaryCsv']['tmp_name']) === true) { + return Response::createBadRequest( + Json::encode([ + "error" => "missingRatingsCsvFile", + "message" => "No CSV file has been uploaded" + ]) + ); } $userId = $this->authenticationService->getCurrentUserId(); $targetFile = $this->appStorageDirectory . 'letterboxd-diary-' . $userId . '-' . time() . '.csv'; - move_uploaded_file($fileParameters['diaryCsv']['tmp_name'], $targetFile); + move_uploaded_file($fileParameters['letterboxdDiaryCsv']['tmp_name'], $targetFile); if ($this->letterboxdImportHistoryFileValidator->isValidDiaryCsv($targetFile) === false) { - $this->sessionWrapper->set('letterboxdDiaryImportFileInvalid', true); - - return Response::create( - StatusCode::createSeeOther(), - null, - [Header::createLocation($_SERVER['HTTP_REFERER'])], + return Response::createBadRequest( + Json::encode([ + "error" => "letterboxdDiaryImportFileInvalid", + "message" => "Invalid Letterboxd diary has been uploaded" + ]) ); } $this->jobQueueApi->addLetterboxdImportHistoryJob($userId, $targetFile); - $this->sessionWrapper->set('letterboxdDiarySyncSuccessful', true); - - return Response::create( - StatusCode::createSeeOther(), - null, - [Header::createLocation($_SERVER['HTTP_REFERER'])], - ); + return Response::createNoContent(); } public function scheduleLetterboxdRatingsImport(Request $request) : Response { $fileParameters = $request->getFileParameters(); - if (empty($fileParameters['ratingsCsv']['tmp_name']) === true) { + if (empty($fileParameters['letterboxdRatingsCsv']['tmp_name']) === true) { $this->sessionWrapper->set('letterboxdRatingsImportFileMissing', true); - return Response::create( - StatusCode::createSeeOther(), - null, - [Header::createLocation($_SERVER['HTTP_REFERER'])], + return Response::createBadRequest( + Json::encode([ + "error" => "letterboxdRatingsImportFileMissing", + "message" => "No file has been uploaded" + ]) ); } $userId = $this->authenticationService->getCurrentUserId(); $targetFile = $this->appStorageDirectory . 'letterboxd-ratings-' . $userId . '-' . time() . '.csv'; - move_uploaded_file($fileParameters['ratingsCsv']['tmp_name'], $targetFile); + move_uploaded_file($fileParameters['letterboxdRatingsCsv']['tmp_name'], $targetFile); if ($this->letterboxdImportHistoryFileValidator->isValidRatingsCsv($targetFile) === false) { $this->sessionWrapper->set('letterboxdRatingsImportFileInvalid', true); - return Response::create( - StatusCode::createSeeOther(), - null, - [Header::createLocation($_SERVER['HTTP_REFERER'])], + return Response::createBadRequest( + Json::encode([ + "error" => "letterboxdRatingsImportFileInvalid", + "message" => "Invalid Letterboxd ratings has been uploaded" + ]) ); } $this->jobQueueApi->addLetterboxdImportRatingsJob($userId, $targetFile); - $this->sessionWrapper->set('letterboxdRatingsSyncSuccessful', true); - - return Response::create( - StatusCode::createSeeOther(), - null, - [Header::createLocation($_SERVER['HTTP_REFERER'])], - ); + return Response::createNoContent(); } public function schedulePlexWatchlistImport() : Response @@ -130,11 +128,7 @@ public function schedulePlexWatchlistImport() : Response $this->jobQueueApi->addPlexImportWatchlistJob($currentUser->getId()); - return Response::create( - StatusCode::createSeeOther(), - null, - [Header::createLocation($_SERVER['HTTP_REFERER'])], - ); + return Response::createNoContent(); } public function scheduleJellyfinImportHistory() : Response @@ -143,11 +137,7 @@ public function scheduleJellyfinImportHistory() : Response $this->jobQueueApi->addJellyfinImportMoviesJob($currentUserId); - return Response::create( - StatusCode::createSeeOther(), - null, - [Header::createLocation($_SERVER['HTTP_REFERER'])], - ); + return Response::createNoContent(); } public function scheduleJellyfinExportHistory() : Response @@ -156,11 +146,7 @@ public function scheduleJellyfinExportHistory() : Response $this->jobQueueApi->addJellyfinExportMoviesJob($currentUserId); - return Response::create( - StatusCode::createSeeOther(), - null, - [Header::createLocation($_SERVER['HTTP_REFERER'])], - ); + return Response::createNoContent(); } public function scheduleTraktHistorySync() : Response @@ -169,11 +155,7 @@ public function scheduleTraktHistorySync() : Response $this->sessionWrapper->set('scheduledTraktHistoryImport', true); - return Response::create( - StatusCode::createSeeOther(), - null, - [Header::createLocation($_SERVER['HTTP_REFERER'])], - ); + return Response::createNoContent(); } public function scheduleTraktRatingsSync() : Response @@ -182,10 +164,6 @@ public function scheduleTraktRatingsSync() : Response $this->sessionWrapper->set('scheduledTraktRatingsImport', true); - return Response::create( - StatusCode::createSeeOther(), - null, - [Header::createLocation($_SERVER['HTTP_REFERER'])], - ); + return Response::createNoContent(); } } diff --git a/templates/page/settings-integration-letterboxd.html.twig b/templates/page/settings-integration-letterboxd.html.twig index 62864feee..71e17c94f 100644 --- a/templates/page/settings-integration-letterboxd.html.twig +++ b/templates/page/settings-integration-letterboxd.html.twig @@ -38,7 +38,7 @@
Import History

Upload the diary.csv file

-
diff --git a/tests/rest/api/jobs.http b/tests/rest/api/jobs.http new file mode 100644 index 000000000..d1c0eecc2 --- /dev/null +++ b/tests/rest/api/jobs.http @@ -0,0 +1,5 @@ +GET http://127.0.0.1/api/jobs?type=jellyfin_import_history +Accept: */* +Cache-Control: no-cache +Content-Type: application/json +X-Auth-Token: {{xAuthToken}} From 61c96ce62e80982f18fa9d5a0857a6cd638a2b6a Mon Sep 17 00:00:00 2001 From: JVT038 <47184046+JVT038@users.noreply.github.com> Date: Wed, 28 Feb 2024 19:27:02 +0100 Subject: [PATCH 3/3] Changed the routes a bit and theoretically fixed the triggering of jobs from the web ui. Signed-off-by: JVT038 <47184046+JVT038@users.noreply.github.com> --- docs/openapi.json | 57 ++++++++----------- public/js/component/modal-job.js | 2 +- public/js/settings-integration-jellyfin.js | 4 +- public/js/settings-integration-letterboxd.js | 36 ++++++++++++ public/js/settings-integration-plex.js | 2 +- public/js/settings-integration-trakt.js | 20 +++++++ public/js/settings-server-jobs.js | 18 ++++++ settings/routes.php | 17 +++--- src/HttpController/Api/JobController.php | 34 ++++++----- .../settings-integration-letterboxd.html.twig | 41 +++---------- .../page/settings-integration-trakt.html.twig | 16 +----- templates/page/settings-server-jobs.html.twig | 6 +- tests/rest/api/jobs.http | 2 +- 13 files changed, 146 insertions(+), 109 deletions(-) diff --git a/docs/openapi.json b/docs/openapi.json index 5339c42fb..4001836c4 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -1066,7 +1066,7 @@ } } }, - "/jobs": { + "/job-queue": { "get": { "tags": [ "Job management" @@ -1114,40 +1114,31 @@ "token": [] } ] - } - }, - "/job-queue/purge-processed": { + }, "delete": { "tags": [ "Job management" ], - "summary": "Delete processed jobs", - "description": "Delete processed jobs", - "responses": { - "204": { - "$ref": "#/components/responses/204-jobs" - }, - "403": { - "$ref": "#/components/responses/403" - } - }, - "security": [ + "summary": "Delete all jobs", + "description": "Delete all jobs, regardless of their status", + "parameters": [ { - "token": [] + "in": "query", + "name": "target", + "description": "Which jobs to delete. It can be either 'all' (meaning that all jobs would be deleted) or 'processed' (meaning only the jobs that have finished running will be deleted). It defaults to 'all'.", + "examples": { + "all": { + "target": "all" + }, + "processed": { + "target": "processed" + } + } } - ] - } - }, - "/job-queue/purge-all": { - "delete": { - "tags": [ - "Job management" ], - "summary": "Delete all jobs", - "description": "Delete all jobs, regardless of their status", "responses": { "204": { - "$ref": "#/components/responses/204-jobs" + "$ref": "#/components/responses/204" }, "403": { "$ref": "#/components/responses/403" @@ -1160,7 +1151,7 @@ ] } }, - "/jobs/schedule/trakt-history-sync": { + "/job-queue/schedule/trakt-history-sync": { "post": { "tags": [ "Job management" @@ -1182,7 +1173,7 @@ ] } }, - "/jobs/schedule/trakt-ratings-sync": { + "/job-queue/schedule/trakt-ratings-sync": { "post": { "tags": [ "Job management" @@ -1204,7 +1195,7 @@ ] } }, - "/jobs/schedule/letterboxd-diary-sync": { + "/job-queue/schedule/letterboxd-diary-sync": { "post": { "tags": [ "Job management" @@ -1240,7 +1231,7 @@ ] } }, - "/jobs/schedule/letterboxd-ratings-sync": { + "/job-queue/schedule/letterboxd-ratings-sync": { "post": { "tags": [ "Job management" @@ -1276,7 +1267,7 @@ ] } }, - "/jobs/schedule/plex-watchlist-sync": { + "/job-queue/schedule/plex-watchlist-sync": { "post": { "tags": [ "Job management" @@ -1298,7 +1289,7 @@ ] } }, - "/jobs/schedule/jellyfin-import-history": { + "/job-queue/schedule/jellyfin-import-history": { "post": { "tags": [ "Job management" @@ -1320,7 +1311,7 @@ ] } }, - "/jobs/schedule/jellyfin-export-history": { + "/job-queue/schedule/jellyfin-export-history": { "post": { "tags": [ "Job management" diff --git a/public/js/component/modal-job.js b/public/js/component/modal-job.js index d3e48f687..eb174e61c 100644 --- a/public/js/component/modal-job.js +++ b/public/js/component/modal-job.js @@ -67,7 +67,7 @@ function setJobModalTitle(jobType) { async function fetchJobs(jobType) { - const response = await fetch('/api/jobs?type=' + jobType) + const response = await fetch('/api/job-queue?type=' + jobType) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) diff --git a/public/js/settings-integration-jellyfin.js b/public/js/settings-integration-jellyfin.js index f7df541aa..ef5f78ddc 100644 --- a/public/js/settings-integration-jellyfin.js +++ b/public/js/settings-integration-jellyfin.js @@ -266,7 +266,7 @@ async function updateSyncOptions() { async function exportJellyfin() { const response = await fetch( - '/api/jobs/schedule/jellyfin-export-history', + '/api/job-queue/schedule/jellyfin-export-history', {'method': 'get'} ).catch(function (error) { addAlert('alertJellyfinExportHistoryDiv', 'History export could not be scheduled', 'danger') @@ -296,7 +296,7 @@ async function exportJellyfin() { async function importJellyfin() { const response = await fetch( - '/api/jobs/schedule/jellyfin-import-history', + '/api/job-queue/schedule/jellyfin-import-history', {'method': 'get'} ).catch(function (error) { addAlert('alertJellyfinImportHistoryDiv', 'History import could not be scheduled', 'danger') diff --git a/public/js/settings-integration-letterboxd.js b/public/js/settings-integration-letterboxd.js index fb50b2e63..be509bda6 100644 --- a/public/js/settings-integration-letterboxd.js +++ b/public/js/settings-integration-letterboxd.js @@ -3,3 +3,39 @@ function toggleTableVisibility() { document.getElementById('showImportTableButton').disabled = true console.log(document.getElementById('showImportTableButton').marginBottom) } + +async function triggerLetterboxdDiaryImport() { + let requestFormData = new FormData(); + requestFormData.append('letterboxdDiaryCsv', document.getElementById('letterboxdDiaryCsv').files[0]); + await fetch('/api/job-queue/schedule/letterboxd-diary-sync', { + method: 'POST', body: requestFormData + }).then(response => { + if(response.ok) { + addAlert('letterboxdImportDiaryResponse', 'History import scheduled', 'success', true, 0); + } else { + return response.json(); + } + }).then(jsonresponse => { + if(jsonresponse !== null) { + addAlert('letterboxdImportDiaryResponse', jsonresponse['message'], 'danger', true, 0); + } + }); +} + +async function triggerLetterboxdRatingsImport() { + let requestFormData = new FormData(); + requestFormData.append('letterboxdDiaryCsv', document.getElementById('letterboxdRatingsCsv').files[0]); + await fetch('/api/job-queue/schedule/letterboxd-ratings-sync', { + method: 'POST', body: requestFormData + }).then(response => { + if(response.ok) { + addAlert('letterboxdImportResponse', 'Ratings import scheduled', 'success', true, 0); + } else { + return response.json(); + } + }).then(jsonresponse => { + if(jsonresponse !== null) { + addAlert('letterboxdImportResponse', jsonresponse['message'], 'danger', true, 0); + } + }); +} diff --git a/public/js/settings-integration-plex.js b/public/js/settings-integration-plex.js index 4993b447f..083448f4d 100644 --- a/public/js/settings-integration-plex.js +++ b/public/js/settings-integration-plex.js @@ -226,7 +226,7 @@ document.getElementById('plexServerUrlInput').addEventListener('input', function async function importPlexWatchlist() { const response = await fetch( - '/api/jobs/schedule/plex-watchlist-sync', + '/api/job-queue/schedule/plex-watchlist-sync', {'method': 'get'} ).catch(function (error) { addAlert('alertPlexWatchlistImportDiv', 'Watchlist import could not be scheduled', 'danger') diff --git a/public/js/settings-integration-trakt.js b/public/js/settings-integration-trakt.js index ef76d233c..138d70e05 100644 --- a/public/js/settings-integration-trakt.js +++ b/public/js/settings-integration-trakt.js @@ -44,3 +44,23 @@ function toggleTableVisibility() { document.getElementById('importTable').classList.remove('d-none') document.getElementById('showImportTableButton').disabled = true } + +async function triggerTraktImportRatings(){ + await fetch('/api/job-queue/schedule/trakt-ratings-sync', { + method: 'POST', + }).then(response => { + if(response.ok) { + addAlert('#traktScheduleRatingsResponse', 'Ratings import scheduled', 'success', true, 0); + } + }) +} + +async function triggerTraktImportHistory(){ + await fetch('/api/job-queue/schedule/trakt-history-sync', { + method: 'POST', + }).then(response => { + if(response.ok) { + addAlert('#traktScheduleRatingsResponse', 'Ratings import scheduled', 'success', true, 0); + } + }) +} diff --git a/public/js/settings-server-jobs.js b/public/js/settings-server-jobs.js index 960bd3514..faaf87976 100644 --- a/public/js/settings-server-jobs.js +++ b/public/js/settings-server-jobs.js @@ -17,3 +17,21 @@ function refreshPage() { window.location.href = '/settings/server/jobs?jpp=' + jobsPerPage } + +async function deleteJobs(target = 'all') { + let confirmation; + if(target === 'all') { + confirmation = confirm('Are you sure you want to remove all processed jobs (done + failed)?'); + } else if(target === 'processed') { + confirmation = confirm('Are you sure you want to remove all jobs? This will not stop active job processes'); + } + if(confirmation === true) { + await fetch('/api/job-queue?target=' + target, { + method: 'DELETE', + }).then(response => { + if(response.ok) { + window.location.reload(); + } + }) + } +} diff --git a/settings/routes.php b/settings/routes.php index 9e124eab3..1df2d94a7 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -215,15 +215,14 @@ function addApiRoutes(RouterService $routerService, FastRoute\RouteCollector $ro ############# # Job Queue # ############# - $routes->add('GET', '/jobs', [Api\JobController::class, 'getJobs'], [Api\Middleware\IsAuthenticated::class]); - $routes->add('DELETE', '/job-queue/purge-processed', [Api\JobController::class, 'purgeProcessedJobs'], [Api\Middleware\IsAuthenticated::class]); - $routes->add('DELETE', '/job-queue/purge-all', [Api\JobController::class, 'purgeAllJobs'], [Api\Middleware\IsAuthenticated::class]); - $routes->add('POST', '/jobs/schedule/trakt-history-sync', [Api\JobController::class, 'scheduleTraktHistorySync'], [Api\Middleware\IsAuthenticated::class]); - $routes->add('POST', '/jobs/schedule/trakt-ratings-sync', [Api\JobController::class, 'scheduleTraktRatingsSync'], [Api\Middleware\IsAuthenticated::class]); - $routes->add('POST', '/jobs/schedule/letterboxd-diary-sync', [Api\JobController::class, 'scheduleLetterboxdDiaryImport'], [Api\Middleware\IsAuthenticated::class]); - $routes->add('POST', '/jobs/schedule/letterboxd-ratings-sync', [Api\JobController::class, 'scheduleLetterboxdRatingsImport'], [Api\Middleware\IsAuthenticated::class]); - $routes->add('POST', '/jobs/schedule/plex-watchlist-sync', [Api\JobController::class, 'schedulePlexWatchlistImport'], [Api\Middleware\IsAuthenticated::class]); - $routes->add('POST', '/jobs/schedule/jellyfin-import-history', [Api\JobController::class, 'scheduleJellyfinImportHistory'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('GET', '/job-queue', [Api\JobController::class, 'getJobs'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('DELETE', '/job-queue', [Api\JobController::class, 'purgeJobs'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('POST', '/job-queue/schedule/trakt-history-sync', [Api\JobController::class, 'scheduleTraktHistorySync'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('POST', '/job-queue/schedule/trakt-ratings-sync', [Api\JobController::class, 'scheduleTraktRatingsSync'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('POST', '/job-queue/schedule/letterboxd-diary-sync', [Api\JobController::class, 'scheduleLetterboxdDiaryImport'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('POST', '/job-queue/schedule/letterboxd-ratings-sync', [Api\JobController::class, 'scheduleLetterboxdRatingsImport'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('POST', '/job-queue/schedule/plex-watchlist-sync', [Api\JobController::class, 'schedulePlexWatchlistImport'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('POST', '/job-queue/schedule/jellyfin-import-history', [Api\JobController::class, 'scheduleJellyfinImportHistory'], [Api\Middleware\IsAuthenticated::class]); $routes->add('POST', '/jobs/schedule/jellyfin-export-history', [Api\JobController::class, 'scheduleJellyfinExportHistory'], [Api\Middleware\IsAuthenticated::class, Api\Middleware\UserHasJellyfinToken::class]); diff --git a/src/HttpController/Api/JobController.php b/src/HttpController/Api/JobController.php index 2bbb47c5c..38a2c2de1 100644 --- a/src/HttpController/Api/JobController.php +++ b/src/HttpController/Api/JobController.php @@ -40,18 +40,26 @@ public function getJobs(Request $request) : Response return Response::createJson(Json::encode($jobs)); } - public function purgeAllJobs() : Response + public function purgeJobs(Request $request) : Response { - $this->jobQueueApi->purgeAllJobs(); - - return Response::createNoContent(); - } - - public function purgeProcessedJobs() : Response - { - $this->jobQueueApi->purgeProcessedJobs(); - - return Response::createNoContent(); + $queryParameters = $request->getGetParameters(); + $target = $queryParameters['target'] ?? null; + switch($target) { + case 'all': + case null: + $this->jobQueueApi->purgeAllJobs(); + return Response::createNoContent(); + case 'processed': + $this->jobQueueApi->purgeProcessedJobs(); + return Response::createNoContent(); + default: + return Response::createBadRequest( + Json::encode([ + "error" => "InvalidTarget", + "message" => "An invalid target has been requested to delete." + ]) + ); + } } public function scheduleLetterboxdDiaryImport(Request $request) : Response @@ -76,7 +84,7 @@ public function scheduleLetterboxdDiaryImport(Request $request) : Response return Response::createBadRequest( Json::encode([ "error" => "letterboxdDiaryImportFileInvalid", - "message" => "Invalid Letterboxd diary has been uploaded" + "message" => 'Csv file invalid: Must contain the columns "Date", "Letterboxd URI" and "Watched date".' ]) ); } @@ -112,7 +120,7 @@ public function scheduleLetterboxdRatingsImport(Request $request) : Response return Response::createBadRequest( Json::encode([ "error" => "letterboxdRatingsImportFileInvalid", - "message" => "Invalid Letterboxd ratings has been uploaded" + "message" => 'Csv file invalid: Must contain the columns "Rating", "Letterboxd URI" and "Name".' ]) ); } diff --git a/templates/page/settings-integration-letterboxd.html.twig b/templates/page/settings-integration-letterboxd.html.twig index 71e17c94f..3bb34eb95 100644 --- a/templates/page/settings-integration-letterboxd.html.twig +++ b/templates/page/settings-integration-letterboxd.html.twig @@ -34,35 +34,23 @@ - +
Import History

Upload the diary.csv file

-
- - - {% if letterboxdDiarySyncSuccessful == true %} - - - {% endif %} - {% if letterboxdDiaryImportFileInvalid == true %} - - {% endif %} + + +

-
+
Import Ratings

Upload the ratings.csv file

@@ -71,20 +59,9 @@
- - - {% if letterboxdRatingsSyncSuccessful == true %} - - {% endif %} - {% if letterboxdRatingsImportFileInvalid == true %} - - {% endif %} + + +

diff --git a/templates/page/settings-integration-trakt.html.twig b/templates/page/settings-integration-trakt.html.twig index d78ce3509..11914ba9f 100644 --- a/templates/page/settings-integration-trakt.html.twig +++ b/templates/page/settings-integration-trakt.html.twig @@ -82,21 +82,11 @@
- Import history - +
- - {% if traktScheduleRatingsSyncSuccessful == true %} - - {% endif %} - +
- Import ratings +
diff --git a/templates/page/settings-server-jobs.html.twig b/templates/page/settings-server-jobs.html.twig index 247e7ea69..9b2a5c2ff 100644 --- a/templates/page/settings-server-jobs.html.twig +++ b/templates/page/settings-server-jobs.html.twig @@ -63,10 +63,8 @@ {% if jobs|length == 0 %}

No jobs in queue

{% endif %}
- Remove processed jobs - Remove all jobs + +

diff --git a/tests/rest/api/jobs.http b/tests/rest/api/jobs.http index d1c0eecc2..60c5f41e8 100644 --- a/tests/rest/api/jobs.http +++ b/tests/rest/api/jobs.http @@ -2,4 +2,4 @@ GET http://127.0.0.1/api/jobs?type=jellyfin_import_history Accept: */* Cache-Control: no-cache Content-Type: application/json -X-Auth-Token: {{xAuthToken}} +X-Movary-Token: {{xAuthToken}}