Add two modes for short URLs

This commit is contained in:
Alejandro Celaya
2023-01-25 20:33:07 +01:00
parent 87007677ed
commit 05acd4ae88
15 changed files with 68 additions and 17 deletions

View File

@@ -43,6 +43,7 @@ enum EnvVars: string
case REDIRECT_CACHE_LIFETIME = 'REDIRECT_CACHE_LIFETIME';
case BASE_PATH = 'BASE_PATH';
case SHORT_URL_TRAILING_SLASH = 'SHORT_URL_TRAILING_SLASH';
case SHORT_URL_MODE = 'SHORT_URL_MODE';
case PORT = 'PORT';
case TASK_WORKER_NUM = 'TASK_WORKER_NUM';
case WEB_WORKER_NUM = 'WEB_WORKER_NUM';

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Options;
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode;
use const Shlinkio\Shlink\DEFAULT_SHORT_CODES_LENGTH;
final class UrlShortenerOptions
@@ -16,6 +18,7 @@ final class UrlShortenerOptions
public readonly bool $appendExtraPath = false,
public readonly bool $multiSegmentSlugsEnabled = false,
public readonly bool $trailingSlashEnabled = false,
public readonly ShortUrlMode $mode = ShortUrlMode::STRICT,
) {
}
}

View File

@@ -16,6 +16,7 @@ use Shlinkio\Shlink\Core\Model\DeviceType;
use Shlinkio\Shlink\Core\ShortUrl\Model\DeviceLongUrlPair;
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlEdition;
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode;
use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter;
use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface;
use Shlinkio\Shlink\Core\ShortUrl\Resolver\SimpleShortUrlRelationResolver;
@@ -95,7 +96,10 @@ class ShortUrl extends AbstractEntity
$instance->maxVisits = $creation->maxVisits;
$instance->customSlugWasProvided = $creation->hasCustomSlug();
$instance->shortCodeLength = $creation->shortCodeLength;
$instance->shortCode = $creation->customSlug ?? generateRandomShortCode($instance->shortCodeLength);
$instance->shortCode = $creation->customSlug ?? generateRandomShortCode(
$instance->shortCodeLength,
$creation->shortUrlMode,
);
$instance->domain = $relationResolver->resolveDomain($creation->domain);
$instance->authorApiKey = $creation->apiKey;
$instance->title = $creation->title;
@@ -292,7 +296,7 @@ class ShortUrl extends AbstractEntity
/**
* @throws ShortCodeCannotBeRegeneratedException
*/
public function regenerateShortCode(): void
public function regenerateShortCode(ShortUrlMode $mode): void
{
// In ShortUrls where a custom slug was provided, throw error, unless it is an imported one
if ($this->customSlugWasProvided && $this->importSource === null) {
@@ -304,7 +308,7 @@ class ShortUrl extends AbstractEntity
throw ShortCodeCannotBeRegeneratedException::forShortUrlAlreadyPersisted();
}
$this->shortCode = generateRandomShortCode($this->shortCodeLength);
$this->shortCode = generateRandomShortCode($this->shortCodeLength, $mode);
}
public function isEnabled(): bool

View File

@@ -5,14 +5,17 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Helper;
use Doctrine\ORM\EntityManagerInterface;
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepository;
class ShortCodeUniquenessHelper implements ShortCodeUniquenessHelperInterface
{
public function __construct(private readonly EntityManagerInterface $em)
{
public function __construct(
private readonly EntityManagerInterface $em,
private readonly UrlShortenerOptions $options,
) {
}
public function ensureShortCodeUniqueness(ShortUrl $shortUrlToBeCreated, bool $hasCustomSlug): bool
@@ -29,7 +32,7 @@ class ShortCodeUniquenessHelper implements ShortCodeUniquenessHelperInterface
return false;
}
$shortUrlToBeCreated->regenerateShortCode();
$shortUrlToBeCreated->regenerateShortCode($this->options->mode);
return $this->ensureShortCodeUniqueness($shortUrlToBeCreated, $hasCustomSlug);
}
}

View File

@@ -25,6 +25,7 @@ final class ShortUrlCreation implements TitleResolutionModelInterface
*/
private function __construct(
public readonly string $longUrl,
public readonly ShortUrlMode $shortUrlMode,
public readonly array $deviceLongUrls = [],
public readonly ?Chronos $validSince = null,
public readonly ?Chronos $validUntil = null,
@@ -47,7 +48,7 @@ final class ShortUrlCreation implements TitleResolutionModelInterface
/**
* @throws ValidationException
*/
public static function fromRawData(array $data): self
public static function fromRawData(array $data, ShortUrlMode $mode = ShortUrlMode::STRICT): self
{
$inputFilter = ShortUrlInputFilter::withRequiredLongUrl($data);
if (! $inputFilter->isValid()) {
@@ -60,6 +61,7 @@ final class ShortUrlCreation implements TitleResolutionModelInterface
return new self(
longUrl: $inputFilter->getValue(ShortUrlInputFilter::LONG_URL),
shortUrlMode: $mode,
deviceLongUrls: $deviceLongUrls,
validSince: normalizeOptionalDate($inputFilter->getValue(ShortUrlInputFilter::VALID_SINCE)),
validUntil: normalizeOptionalDate($inputFilter->getValue(ShortUrlInputFilter::VALID_UNTIL)),
@@ -84,6 +86,7 @@ final class ShortUrlCreation implements TitleResolutionModelInterface
{
return new self(
longUrl: $this->longUrl,
shortUrlMode: $this->shortUrlMode,
deviceLongUrls: $this->deviceLongUrls,
validSince: $this->validSince,
validUntil: $this->validUntil,

View File

@@ -0,0 +1,9 @@
<?php
namespace Shlinkio\Shlink\Core\ShortUrl\Model;
enum ShortUrlMode: string
{
case STRICT = 'strict';
case LOOSELY = 'loosely';
}

View File

@@ -42,7 +42,6 @@ class ShortUrlInputFilter extends InputFilter
private function __construct(array $data, bool $requireLongUrl)
{
// FIXME The multi-segment slug option should be injected
$this->initialize($requireLongUrl, $data[EnvVars::MULTI_SEGMENT_SLUGS_ENABLED->value] ?? false);
$this->setData($data);
}