Create repository to handle expired short URLs deletion

This commit is contained in:
Alejandro Celaya
2024-04-03 09:41:05 +02:00
parent f92a720d63
commit fd882834d3
5 changed files with 226 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Model;
final readonly class ExpiredShortUrlsConditions
{
public function __construct(public bool $pastValidUntil = true, public bool $maxVisitsReached = false)
{
}
public static function fromQuery(array $query): self
{
return new self(
pastValidUntil: (bool) ($query['pastValidUntil'] ?? true),
maxVisitsReached: (bool) ($query['maxVisitsReached'] ?? true),
);
}
public function hasConditions(): bool
{
return $this->pastValidUntil || $this->maxVisitsReached;
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Repository;
use Cake\Chronos\Chronos;
use Doctrine\ORM\QueryBuilder;
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\Model\ExpiredShortUrlsConditions;
use Shlinkio\Shlink\Core\Visit\Entity\ShortUrlVisitsCount;
use function sprintf;
class ExpiredShortUrlsRepository extends EntitySpecificationRepository implements ExpiredShortUrlsRepositoryInterface
{
/**
* @inheritDoc
*/
public function delete(ExpiredShortUrlsConditions $conditions = new ExpiredShortUrlsConditions()): int
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->delete(ShortUrl::class, 's');
return $this->applyConditions($qb, $conditions, fn () => (int) $qb->getQuery()->execute());
}
/**
* @inheritDoc
*/
public function dryCount(ExpiredShortUrlsConditions $conditions = new ExpiredShortUrlsConditions()): int
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('COUNT(s.id)')
->from(ShortUrl::class, 's');
return $this->applyConditions($qb, $conditions, fn () => (int) $qb->getQuery()->getSingleScalarResult());
}
/**
* @param callable(): int $getResultFromQueryBuilder
*/
private function applyConditions(
QueryBuilder $qb,
ExpiredShortUrlsConditions $conditions,
callable $getResultFromQueryBuilder,
): int {
if (! $conditions->hasConditions()) {
return 0;
}
if ($conditions->pastValidUntil) {
$qb
->where($qb->expr()->andX(
$qb->expr()->isNotNull('s.validUntil'),
$qb->expr()->lt('s.validUntil', ':now'),
))
->setParameter('now', Chronos::now()->toDateTimeString());
}
if ($conditions->maxVisitsReached) {
$qb->orWhere($qb->expr()->andX(
$qb->expr()->isNotNull('s.maxVisits'),
$qb->expr()->lte(
's.maxVisits',
sprintf(
'(SELECT COALESCE(SUM(vc.count), 0) FROM %s as vc WHERE vc.shortUrl=s)',
ShortUrlVisitsCount::class,
),
),
));
}
return $getResultFromQueryBuilder();
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Repository;
use Shlinkio\Shlink\Core\ShortUrl\Model\ExpiredShortUrlsConditions;
interface ExpiredShortUrlsRepositoryInterface
{
/**
* Delete expired short URLs matching provided conditions
*/
public function delete(ExpiredShortUrlsConditions $conditions = new ExpiredShortUrlsConditions()): int;
/**
* Count how many expired short URLs would be deleted for provided conditions
*/
public function dryCount(ExpiredShortUrlsConditions $conditions = new ExpiredShortUrlsConditions()): int;
}