diff --git a/public/js/job-modal.js b/public/js/job-modal.js new file mode 100644 index 00000000..ed104a1a --- /dev/null +++ b/public/js/job-modal.js @@ -0,0 +1,116 @@ +const jobModal = new bootstrap.Modal('#jobModal') +const jobModalTypeInput = document.getElementById('jobModalType'); + +async function showJobModal(jobType) { + jobModalTypeInput.value = jobType + setJobModalTitle(jobModalTypeInput.value) + + loadJobModal(true) +} + +async function loadJobModal(showModal) { + setJobModalLoadingSpinner(true) + document.getElementById('jobModalEmptyMessage').classList.add('d-none') + document.getElementById('jobModalErrorAlert').classList.add('d-none') + + if (showModal === true) { + jobModal.show(); + } + + let jobs = null + + try { + jobs = await fetchJobs(jobModalTypeInput.value); + } catch (error) { + document.getElementById('jobModalErrorAlert').classList.remove('d-none') + } + + setJobModalLoadingSpinner(false) + + if (jobs !== null) { + renderJobModalTable(jobs) + } +} + +function setJobModalTitle(jobType) { + let title + + switch (jobType) { + case 'trakt_import_ratings': + title = 'Rating imports'; + break; + case 'trakt_import_history': + title = 'History imports'; + break; + case 'letterboxd_import_ratings': + title = 'Rating imports'; + break; + case 'letterboxd_import_history': + title = 'History imports'; + break; + default: + throw new Error('Not supported job type: ' + jobType); + } + + + document.getElementById('jobModalTitle').innerText = title; +} + +async function fetchJobs(jobType) { + + const response = await fetch('/jobs?type=' + jobType) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + return await response.json() +} + +function setJobModalLoadingSpinner(isActive = true) { + if (isActive === true) { + emptyJobModalTable() + document.getElementById('jobModalLoadingSpinner').classList.remove('d-none'); + } else { + document.getElementById('jobModalLoadingSpinner').classList.add('d-none'); + } +} + +async function renderJobModalTable(jobs) { + const table = document.getElementById('jobModalTable'); + + let tbodyRef = table.getElementsByTagName('tbody')[0]; + + table.getElementsByTagName('tbody').innerHtml = 'ads' + if (jobs.length === 0) { + document.getElementById('jobModalEmptyMessage').classList.remove('d-none') + } else { + document.getElementById('jobModalEmptyMessage').classList.add('d-none') + } + + jobs.forEach((job, index, jobs) => { + let newRow = tbodyRef.insertRow(); + + const createdAtCell = newRow.insertCell(); + createdAtCell.appendChild(document.createTextNode(job.createdAt)); + + const statusCell = newRow.insertCell(); + statusCell.appendChild(document.createTextNode(job.status)); + + const finishedAtCell = newRow.insertCell(); + finishedAtCell.appendChild(document.createTextNode(job.status === 'done' || job.status === 'failed' ? job.updatedAt : '-')); + + if (index === jobs.length - 1) { + statusCell.style.borderBottom = '0' + createdAtCell.style.borderBottom = '0' + finishedAtCell.style.borderBottom = '0' + } + }); +} + +async function emptyJobModalTable() { + const table = document.getElementById('jobModalTable'); + + let tbodyRef = table.getElementsByTagName('tbody')[0]; + tbodyRef.innerHTML = ''; +} diff --git a/settings/routes.php b/settings/routes.php index f3fea731..33a54df0 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -54,6 +54,11 @@ ############# # Job Queue # ############# + $routeCollector->addRoute( + 'GET', + '/jobs', + [\Movary\HttpController\JobController::class, 'getJobs'], + ); $routeCollector->addRoute( 'GET', '/job-queue/purge-processed', diff --git a/src/Factory.php b/src/Factory.php index 8fa58616..64bfbf65 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -250,7 +250,6 @@ public static function createSettingsController(ContainerInterface $container, C { return new SettingsController( $container->get(Twig\Environment::class), - $container->get(JobQueueApi::class), $container->get(Authentication::class), $container->get(UserApi::class), $container->get(MovieApi::class), diff --git a/src/HttpController/JobController.php b/src/HttpController/JobController.php index 21ede3cb..c86ea0b1 100644 --- a/src/HttpController/JobController.php +++ b/src/HttpController/JobController.php @@ -5,11 +5,13 @@ use Movary\Domain\User\Service\Authentication; use Movary\JobQueue\JobQueueApi; use Movary\Service\Letterboxd\Service\LetterboxdCsvValidator; +use Movary\Util\Json; use Movary\Util\SessionWrapper; use Movary\ValueObject\Http\Header; use Movary\ValueObject\Http\Request; use Movary\ValueObject\Http\Response; use Movary\ValueObject\Http\StatusCode; +use Movary\ValueObject\JobType; use RuntimeException; use Twig\Environment; @@ -24,6 +26,21 @@ public function __construct( ) { } + public function getJobs(Request $request) : Response + { + if ($this->authenticationService->isUserAuthenticated() === false) { + return Response::createSeeOther('/'); + } + + $parameters = $request->getGetParameters(); + + $jobType = JobType::createFromString($parameters['type']); + + $jobs = $this->jobQueueApi->find($this->authenticationService->getCurrentUserId(), $jobType); + + return Response::createJson(Json::encode($jobs)); + } + public function purgeAllJobs() : Response { if ($this->authenticationService->isUserAuthenticated() === false) { diff --git a/src/HttpController/SettingsController.php b/src/HttpController/SettingsController.php index 9085338e..0307b2d7 100644 --- a/src/HttpController/SettingsController.php +++ b/src/HttpController/SettingsController.php @@ -31,7 +31,6 @@ class SettingsController { public function __construct( private readonly Environment $twig, - private readonly JobQueueApi $workerService, private readonly Authentication $authenticationService, private readonly UserApi $userApi, private readonly Movie\MovieApi $movieApi, @@ -306,7 +305,6 @@ public function renderLetterboxdPage() : Response 'letterboxdRatingsSyncSuccessful' => $letterboxdRatingsSyncSuccessful, 'letterboxdRatingsImportFileInvalid' => $letterboxdRatingsImportFileInvalid, 'letterboxdDiaryImportFileInvalid' => $letterboxdDiaryImportFileInvalid, - 'lastLetterboxdImportJobs' => $this->workerService->findLastLetterboxdImportsForUser($user->getId()), ]), ); } @@ -479,7 +477,6 @@ public function renderTraktPage() : Response 'traktCredentialsUpdated' => $traktCredentialsUpdated, 'traktScheduleHistorySyncSuccessful' => $scheduledTraktHistoryImport, 'traktScheduleRatingsSyncSuccessful' => $scheduledTraktRatingsImport, - 'lastTraktImportJobs' => $this->workerService->findLastTraktImportsForUser($user->getId()), ]), ); } diff --git a/src/JobQueue/JobEntity.php b/src/JobQueue/JobEntity.php index a9fe0292..8954efcb 100644 --- a/src/JobQueue/JobEntity.php +++ b/src/JobQueue/JobEntity.php @@ -7,7 +7,7 @@ use Movary\ValueObject\JobStatus; use Movary\ValueObject\JobType; -class JobEntity +class JobEntity implements \JsonSerializable { private function __construct( private readonly int $id, @@ -67,4 +67,15 @@ public function getUserId() : ?int { return $this->userId; } + + public function jsonSerialize() : array + { + return [ + 'type' => $this->type, + 'status' => $this->getStatus(), + 'userId' => $this->getUserId(), + 'createdAt' => $this->getCreatedAt(), + 'updatedAt' => $this->getUpdatedAt() + ]; + } } diff --git a/src/JobQueue/JobEntityList.php b/src/JobQueue/JobEntityList.php new file mode 100644 index 00000000..7617ffcd --- /dev/null +++ b/src/JobQueue/JobEntityList.php @@ -0,0 +1,33 @@ +add(JobEntity::createFromArray($historyEntry)); + } + + return $list; + } + + private function add(JobEntity $dto) : void + { + $this->data[] = $dto; + } +} diff --git a/src/JobQueue/JobQueueApi.php b/src/JobQueue/JobQueueApi.php index 6151ceb7..0f779eaa 100644 --- a/src/JobQueue/JobQueueApi.php +++ b/src/JobQueue/JobQueueApi.php @@ -83,35 +83,9 @@ public function fetchOldestWaitingJob() : ?JobEntity return $this->repository->fetchOldestWaitingJob(); } - public function findLastImdbSync() : ?DateTime + public function find(int $userId, JobType $jobType) : ?JobEntityList { - return $this->repository->findLastDateForJobByType(JobType::createImdbSync()); - } - - public function findLastLetterboxdImportsForUser(int $userId) : array - { - return $this->repository->findLastLetterboxdImportsForUser($userId); - } - - public function findLastTmdbSync() : ?DateTime - { - $lastMovieSync = $this->repository->findLastDateForJobByType(JobType::createTmdbMovieSync()); - $lastPersonSync = $this->repository->findLastDateForJobByType(JobType::createTmdbPersonSync()); - - if ($lastMovieSync === null) { - return $lastPersonSync; - } - - if ($lastPersonSync === null) { - return $lastMovieSync; - } - - return $lastMovieSync->isAfter($lastPersonSync) ? $lastMovieSync : $lastPersonSync; - } - - public function findLastTraktImportsForUser(int $userId) : array - { - return $this->repository->findLastTraktImportsForUser($userId); + return $this->repository->find($userId, $jobType); } public function purgeAllJobs() : void diff --git a/src/JobQueue/JobQueueRepository.php b/src/JobQueue/JobQueueRepository.php index 2e294ca1..e25967af 100644 --- a/src/JobQueue/JobQueueRepository.php +++ b/src/JobQueue/JobQueueRepository.php @@ -57,47 +57,17 @@ public function fetchOldestWaitingJob() : ?JobEntity return JobEntity::createFromArray($data); } - public function findLastDateForJobByType(JobType $jobType) : ?DateTime + public function find(int $userId, JobType $jobType) : ?JobEntityList { - $data = $this->dbConnection->fetchOne('SELECT created_at FROM `job_queue` WHERE job_type = ? AND job_status = ? ORDER BY created_at', [$jobType, JobStatus::createDone()]); - - if ($data === false) { - return null; - } - - return DateTime::createFromString($data); - } - - public function findLastLetterboxdImportsForUser(int $userId) : array - { - return $this->dbConnection->fetchAllAssociative( - 'SELECT * - FROM `job_queue` - WHERE job_type IN (?, ?) AND user_id = ? - ORDER BY created_at DESC - LIMIT 10', + $data = $this->dbConnection->fetchAllAssociative( + 'SELECT * FROM `job_queue` WHERE job_type = ? and user_id = ? ORDER BY `created_at` DESC LIMIT 10', [ - JobType::createLetterboxdImportHistory(), - JobType::createLetterboxdImportRatings(), - $userId, + $jobType, + $userId ], ); - } - public function findLastTraktImportsForUser(int $userId) : array - { - return $this->dbConnection->fetchAllAssociative( - 'SELECT * - FROM `job_queue` - WHERE job_type IN (?, ?) AND user_id = ? - ORDER BY created_at DESC - LIMIT 10', - [ - JobType::createTraktImportHistory(), - JobType::createTraktImportRatings(), - $userId, - ], - ); + return JobEntityList::createFromArray($data); } public function purgeNotProcessedJobs() : void diff --git a/src/ValueObject/JobStatus.php b/src/ValueObject/JobStatus.php index 0f289e48..867df7e8 100644 --- a/src/ValueObject/JobStatus.php +++ b/src/ValueObject/JobStatus.php @@ -4,7 +4,7 @@ use RuntimeException; -class JobStatus +class JobStatus implements \JsonSerializable { private const STATUS_DONE = 'done'; @@ -55,4 +55,9 @@ public function __toString() : string { return $this->status; } + + public function jsonSerialize() : string + { + return $this->status; + } } diff --git a/src/ValueObject/JobType.php b/src/ValueObject/JobType.php index 587de110..5c52e976 100644 --- a/src/ValueObject/JobType.php +++ b/src/ValueObject/JobType.php @@ -4,7 +4,7 @@ use RuntimeException; -class JobType +class JobType implements \JsonSerializable { private const TYPE_TMDB_PERSON_SYNC = 'tmdb_person_sync'; @@ -122,4 +122,9 @@ public function isOfTypeTraktImportRatings() : bool { return $this->type === self::TYPE_TRAKT_IMPORT_RATINGS; } + + public function jsonSerialize() : string + { + return $this->type; + } } diff --git a/templates/component/modal-job.html.twig b/templates/component/modal-job.html.twig new file mode 100644 index 00000000..359d5ccb --- /dev/null +++ b/templates/component/modal-job.html.twig @@ -0,0 +1,41 @@ +{% if loggedIn == true %} +
+{% endif %} diff --git a/templates/page/settings-integration-letterboxd.html.twig b/templates/page/settings-integration-letterboxd.html.twig index dd6ab516..87a564a8 100644 --- a/templates/page/settings-integration-letterboxd.html.twig +++ b/templates/page/settings-integration-letterboxd.html.twig @@ -6,6 +6,7 @@ {% block scripts %} + {% endblock %} {% block body %} @@ -34,13 +35,15 @@ +