|
@@ -0,0 +1,181 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+declare(strict_types=1);
|
|
|
|
|
+
|
|
|
|
|
+namespace App\Service\Cron\Job;
|
|
|
|
|
+
|
|
|
|
|
+use App\Entity\Booking\Work;
|
|
|
|
|
+use App\Entity\Booking\WorkByUser;
|
|
|
|
|
+use App\Entity\Core\File;
|
|
|
|
|
+use App\Enum\Core\FileHostEnum;
|
|
|
|
|
+use App\Enum\Core\FileStatusEnum;
|
|
|
|
|
+use App\Service\Cron\BaseCronJob;
|
|
|
|
|
+use App\Service\File\Storage\LocalStorage;
|
|
|
|
|
+use App\Service\Utils\DatesUtils;
|
|
|
|
|
+use Doctrine\DBAL\Connection;
|
|
|
|
|
+use Doctrine\ORM\EntityManagerInterface;
|
|
|
|
|
+use JetBrains\PhpStorm\Pure;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Cronjob that delete work to do entities and associated files older than 12 months.
|
|
|
|
|
+ *
|
|
|
|
|
+ * >>> ot:cron run clean-work-to-do --preview
|
|
|
|
|
+ * >>> ot:cron run clean-work-to-do
|
|
|
|
|
+ */
|
|
|
|
|
+class CleanWorkToDo extends BaseCronJob
|
|
|
|
|
+{
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Delay before removing work to do records (in months).
|
|
|
|
|
+ */
|
|
|
|
|
+ private const DELETE_OLDER_THAN = 12;
|
|
|
|
|
+
|
|
|
|
|
+ #[Pure]
|
|
|
|
|
+ public function __construct(
|
|
|
|
|
+ private Connection $connection,
|
|
|
|
|
+ private EntityManagerInterface $entityManager,
|
|
|
|
|
+ private LocalStorage $storage,
|
|
|
|
|
+ ) {
|
|
|
|
|
+ parent::__construct();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Preview the result of the execution, without actually deleting anything.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @throws \Exception
|
|
|
|
|
+ */
|
|
|
|
|
+ public function preview(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->deleteWorks(true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Proceed to the deletion of the works and associated files.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @throws \Exception
|
|
|
|
|
+ */
|
|
|
|
|
+ public function execute(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->deleteWorks();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Delete works and associated files older than N months.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @throws \Exception
|
|
|
|
|
+ * @throws \Doctrine\DBAL\Driver\Exception
|
|
|
|
|
+ * @throws \Throwable
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function deleteWorks(bool $preview = false): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $maxDate = DatesUtils::new();
|
|
|
|
|
+ $maxDate->sub(new \DateInterval('P'.self::DELETE_OLDER_THAN.'M'));
|
|
|
|
|
+
|
|
|
|
|
+ $this->logger->info('Finding works with courses created before '.$maxDate->format('c'));
|
|
|
|
|
+
|
|
|
|
|
+ // Find works to delete based on course datetime
|
|
|
|
|
+ $works = $this->listWorksToDelete($maxDate);
|
|
|
|
|
+ $total = count($works);
|
|
|
|
|
+
|
|
|
|
|
+ $this->ui->print($total.' works to be removed');
|
|
|
|
|
+
|
|
|
|
|
+ if ($total === 0) {
|
|
|
|
|
+ $this->logger->info('No works found to delete');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ($preview) {
|
|
|
|
|
+ $this->ui->print('> Printing the first and last 10 :');
|
|
|
|
|
+ $this->previewWorks($works);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $deleted = 0;
|
|
|
|
|
+
|
|
|
|
|
+ $this->ui->progress(0, $total);
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($works as $i => $work) {
|
|
|
|
|
+ $this->ui->progress($i + 1, $total);
|
|
|
|
|
+
|
|
|
|
|
+ $this->connection->beginTransaction();
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ $workId = $work->getId();
|
|
|
|
|
+
|
|
|
|
|
+ // Set DELETION_REQUESTED status for all associated files
|
|
|
|
|
+ $this->connection->executeStatement(
|
|
|
|
|
+ "UPDATE File SET status = ?, work_id = null WHERE work_id = ?",
|
|
|
|
|
+ [FileStatusEnum::DELETION_REQUESTED->value, $workId]
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Delete WorkByUser records
|
|
|
|
|
+ $this->connection->executeStatement(
|
|
|
|
|
+ "DELETE FROM WorkByUser WHERE work_id = ?",
|
|
|
|
|
+ [$workId]
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Delete the work itself
|
|
|
|
|
+ $this->connection->executeStatement(
|
|
|
|
|
+ "DELETE FROM Work WHERE id = ?",
|
|
|
|
|
+ [$workId]
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ $this->connection->commit();
|
|
|
|
|
+
|
|
|
|
|
+ ++$deleted;
|
|
|
|
|
+ } catch (\RuntimeException|\InvalidArgumentException $e) {
|
|
|
|
|
+ $this->logger->error('ERROR deleting work '.$work->getId().': '.$e->getMessage());
|
|
|
|
|
+ $this->connection->rollback();
|
|
|
|
|
+ } catch (\Exception $exception) {
|
|
|
|
|
+ $this->connection->rollback();
|
|
|
|
|
+ throw $exception;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $this->logger->info('Works cleanup completed - '.$deleted.' works permanently deleted');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * List the works to delete based on course datetime.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return array<Work>
|
|
|
|
|
+ *
|
|
|
|
|
+ * @throws \Exception
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function listWorksToDelete(\DateTime $maxDate): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $queryBuilder = $this->entityManager->createQueryBuilder();
|
|
|
|
|
+ $queryBuilder
|
|
|
|
|
+ ->select('w')
|
|
|
|
|
+ ->from(Work::class, 'w')
|
|
|
|
|
+ ->leftJoin('w.course', 'c')
|
|
|
|
|
+ ->where('c.datetimeStart <= :maxDate OR c.datetimeStart IS NULL')
|
|
|
|
|
+ ->setParameter('maxDate', $maxDate);
|
|
|
|
|
+
|
|
|
|
|
+ return $queryBuilder->getQuery()->getResult();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Preview works to be deleted.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array<Work> $works
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function previewWorks(array $works): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $total = count($works);
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($works as $i => $work) {
|
|
|
|
|
+ if ($i >= 10 && ($total - $i) > 10) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (($total - $i) === 10) {
|
|
|
|
|
+ $this->ui->print(' (...)');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $courseName = $work->getCourse() ? $work->getCourse()->getName() : 'No Course';
|
|
|
|
|
+ $fileCount = $work->getFiles()->count();
|
|
|
|
|
+ $userCount = $work->getWorkByUsers()->count();
|
|
|
|
|
+
|
|
|
|
|
+ $this->ui->print(" * Work ID {$work->getId()} - Course: {$courseName} - Files: {$fileCount} - Users: {$userCount}");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|