Add cron job for removing expired stored objects

Introduced `RemoveExpiredStoredObjectCronJob` to automate the deletion of expired stored objects every 7 days. Enhanced associated tests and updated relevant interfaces and classes to support the new cron job functionality.
This commit is contained in:
2024-08-28 11:41:43 +02:00
parent c38f7c1179
commit 0db2652f08
10 changed files with 344 additions and 12 deletions

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\DocStoreBundle\Service\StoredObjectCleaner;
use Chill\DocStoreBundle\Repository\StoredObjectRepositoryInterface;
use Chill\MainBundle\Cron\CronJobInterface;
use Chill\MainBundle\Entity\CronJobExecution;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\Messenger\MessageBusInterface;
/**
* Represents a cron job that removes expired stored objects.
*
* This cronjob is executed every 7days, to remove expired stored object. For every
* expired stored object, every version is sent to message bus for async deletion.
*/
final class RemoveExpiredStoredObjectCronJob implements CronJobInterface
{
public const string KEY = 'remove-expired-stored-object';
private const string LAST_DELETED_KEY = 'last-deleted-stored-object-id';
public function __construct(
private readonly ClockInterface $clock,
private readonly MessageBusInterface $messageBus,
private readonly StoredObjectRepositoryInterface $storedObjectRepository
) {}
public function canRun(?CronJobExecution $cronJobExecution): bool
{
if (null === $cronJobExecution) {
return true;
}
return $this->clock->now() >= $cronJobExecution->getLastEnd()->add(new \DateInterval('P7D'));
}
public function getKey(): string
{
return self::KEY;
}
public function run(array $lastExecutionData): ?array
{
$lastDeleted = $lastExecutionData[self::LAST_DELETED_KEY] ?? 0;
foreach ($this->storedObjectRepository->findByExpired($this->clock->now()) as $storedObject) {
foreach ($storedObject->getVersions() as $version) {
$this->messageBus->dispatch(new RemoveOldVersionMessage($version->getId()));
}
$lastDeleted = max($lastDeleted, $storedObject->getId());
}
return [self::LAST_DELETED_KEY => $lastDeleted];
}
}

View File

@@ -11,13 +11,24 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\Service\StoredObjectCleaner;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
use Chill\DocStoreBundle\Repository\StoredObjectVersionRepository;
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
/**
* Class RemoveOldVersionMessageHandler.
*
* This class is responsible for handling the RemoveOldVersionMessage. It implements the MessageHandlerInterface.
* It removes old versions of stored objects based on certain conditions.
*
* If a StoredObject is a candidate for deletion (is expired and no more version stored), it is also removed from the
* database.
*/
final readonly class RemoveOldVersionMessageHandler implements MessageHandlerInterface
{
private const LOG_PREFIX = '[RemoveOldVersionMessageHandler] ';
@@ -27,6 +38,7 @@ final readonly class RemoveOldVersionMessageHandler implements MessageHandlerInt
private LoggerInterface $logger,
private EntityManagerInterface $entityManager,
private StoredObjectManagerInterface $storedObjectManager,
private ClockInterface $clock,
) {}
/**
@@ -37,6 +49,7 @@ final readonly class RemoveOldVersionMessageHandler implements MessageHandlerInt
$this->logger->info(self::LOG_PREFIX.'Received one message', ['storedObjectVersionId' => $message->storedObjectVersionId]);
$storedObjectVersion = $this->storedObjectVersionRepository->find($message->storedObjectVersionId);
$storedObject = $storedObjectVersion->getStoredObject();
if (null === $storedObjectVersion) {
$this->logger->error(self::LOG_PREFIX.'StoredObjectVersion not found in database', ['storedObjectVersionId' => $message->storedObjectVersionId]);
@@ -44,8 +57,13 @@ final readonly class RemoveOldVersionMessageHandler implements MessageHandlerInt
}
$this->storedObjectManager->delete($storedObjectVersion);
// to ensure an immediate deletion
$this->entityManager->remove($storedObjectVersion);
if (StoredObject::canBeDeleted($this->clock->now(), $storedObject)) {
$this->entityManager->remove($storedObject);
}
$this->entityManager->flush();
// clear the entity manager for future usage

View File

@@ -226,9 +226,11 @@ final class StoredObjectManager implements StoredObjectManagerInterface
try {
$response = $this->client->request('DELETE', $signedUrl->url);
if (Response::HTTP_NO_CONTENT !== $response->getStatusCode()) {
if (! (Response::HTTP_NO_CONTENT === $response->getStatusCode() || Response::HTTP_NOT_FOUND === $response->getStatusCode())) {
throw StoredObjectManagerException::invalidStatusCode($response->getStatusCode());
}
$storedObjectVersion->getStoredObject()->removeVersion($storedObjectVersion);
} catch (TransportExceptionInterface $exception) {
throw StoredObjectManagerException::errorDuringHttpRequest($exception);
}

View File

@@ -39,7 +39,9 @@ interface StoredObjectManagerInterface
public function read(StoredObject|StoredObjectVersion $document): string;
/**
* Set the content of a StoredObject.
* Register the content of a new version for the StoredObject.
*
* The manager is also responsible for registering a version in the StoredObject, and return this version.
*
* @param StoredObject $document the document
* @param string $clearContent The content to store in clear
@@ -52,6 +54,11 @@ interface StoredObjectManagerInterface
public function write(StoredObject $document, string $clearContent, ?string $contentType = null): StoredObjectVersion;
/**
* Remove a version from the storage.
*
* This method is also responsible for removing the version from the StoredObject (using @see{StoredObject::removeVersion})
* in case of success.
*
* @throws StoredObjectManagerException
*/
public function delete(StoredObjectVersion $storedObjectVersion): void;