diff --git a/src/Command/PurgeEntryDeletionsCommand.php b/src/Command/PurgeEntryDeletionsCommand.php new file mode 100644 index 000000000..c9a416437 --- /dev/null +++ b/src/Command/PurgeEntryDeletionsCommand.php @@ -0,0 +1,94 @@ +addOption( + 'older-than', + null, + InputOption::VALUE_REQUIRED, + 'Purge records older than this date (format: YYYY-MM-DD)', + '-30 days' + ) + ->addOption( + 'dry-run', + null, + InputOption::VALUE_NONE, + 'Do not actually delete records, just show what would be deleted' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $olderThan = $input->getOption('older-than'); + $dryRun = (bool) $input->getOption('dry-run'); + + try { + $date = new \DateTime($olderThan); + } catch (\Exception $e) { + $io->error(sprintf('Invalid date format: %s.\nYou can use any format supported by PHP (e.g. YYYY-MM-DD).', $olderThan)); + + return 1; + } + + $count = $this->entryDeletionRepository->countAllBefore($date); + + if ($dryRun) { + $io->text('Dry run mode enabled (no records will be deleted)'); + + return 0; + } + + + if (0 === $count) { + $io->success('No entry deletion records found.'); + + return 0; + } + + if ($dryRun) { + $io->success(sprintf('Would have deleted %d records.', $count)); + + return 0; + } + + $confirmMessage = sprintf( + 'Are you sure you want to delete records older than %s? (count: %d)', + $date->format('Y-m-d'), + $count, + ); + if (!$io->confirm($confirmMessage)) { + return 0; + } + + $this->entryDeletionRepository->deleteAllBefore($date); + + $io->success(sprintf('Successfully deleted %d records.', $count)); + + return 0; + } +} diff --git a/src/Repository/EntryDeletionRepository.php b/src/Repository/EntryDeletionRepository.php index 613de69d6..22d70c3b5 100644 --- a/src/Repository/EntryDeletionRepository.php +++ b/src/Repository/EntryDeletionRepository.php @@ -40,4 +40,24 @@ class EntryDeletionRepository extends ServiceEntityRepository return $pager; } + + public function countAllBefore(\DateTime $date): int + { + return $this->createQueryBuilder('de') + ->select('COUNT(de.id)') + ->where('de.deletedAt < :date') + ->setParameter('date', $date) + ->getQuery() + ->getSingleScalarResult(); + } + + public function deleteAllBefore(\DateTime $date) + { + $this->createQueryBuilder('de') + ->delete() + ->where('de.deletedAt < :date') + ->setParameter('date', $date) + ->getQuery() + ->execute(); + } } diff --git a/tests/Command/PurgeEntryDeletionsCommandTest.php b/tests/Command/PurgeEntryDeletionsCommandTest.php new file mode 100644 index 000000000..e3a07ac96 --- /dev/null +++ b/tests/Command/PurgeEntryDeletionsCommandTest.php @@ -0,0 +1,75 @@ +getTestClient()->getKernel()); + $command = $application->find('wallabag:purge-entry-deletions'); + $dateStr = '-2 days'; + + $tester = new CommandTester($command); + $tester->execute([ + '--older-than' => $dateStr, + '--dry-run' => true, + ]); + + $this->assertStringContainsString('Dry run mode enabled', $tester->getDisplay()); + $this->assertSame(0, $tester->getStatusCode()); + + $em = $this->getEntityManager(); + $count = $em->getRepository(EntryDeletion::class)->countAllBefore(new \DateTime($dateStr)); + $this->assertSame(2, $count); + } + + public function testRunPurgeEntryDeletionsCommand() + { + $application = new Application($this->getTestClient()->getKernel()); + $command = $application->find('wallabag:purge-entry-deletions'); + $dateStr = '-2 days'; + + $tester = new CommandTester($command); + $tester->setInputs(['yes']); // confirm deletion + $tester->execute(['--older-than' => $dateStr]); + + $this->assertStringContainsString('Successfully deleted 2 records', $tester->getDisplay()); + $this->assertSame(0, $tester->getStatusCode()); + + $em = $this->getEntityManager(); + + $count = $em->getRepository(EntryDeletion::class)->countAllBefore(new \DateTime($dateStr)); + $this->assertSame(0, $count); + + $count = $em->getRepository(EntryDeletion::class)->countAllBefore(new \DateTime('now')); + $this->assertSame(1, $count); + } + + public function testRunPurgeEntryDeletionsCommandWithNoRecords() + { + $application = new Application($this->getTestClient()->getKernel()); + $command = $application->find('wallabag:purge-entry-deletions'); + + $tester = new CommandTester($command); + $tester->execute([ + '--older-than' => '-1 year', + ]); + + $this->assertStringContainsString('No entry deletion records found', $tester->getDisplay()); + $this->assertSame(0, $tester->getStatusCode()); + } +}