diff --git a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Domain.php b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Domain.php new file mode 100644 index 00000000..d2e52541 --- /dev/null +++ b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Domain.php @@ -0,0 +1,24 @@ +setTable('domains'); + +$builder->createField('id', Type::BIGINT) + ->columnName('id') + ->makePrimaryKey() + ->generatedValue('IDENTITY') + ->option('unsigned', true) + ->build(); + +$builder->createField('authority', Type::STRING) + ->unique() + ->build(); diff --git a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php index 94398df9..00c03b30 100644 --- a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php +++ b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php @@ -61,3 +61,7 @@ $builder->createManyToMany('tags', Entity\Tag::class) ->addInverseJoinColumn('tag_id', 'id', true, false, 'CASCADE') ->addJoinColumn('short_url_id', 'id', true, false, 'CASCADE') ->build(); + +$builder->createManyToOne('domain', Entity\Domain::class) + ->addJoinColumn('domain_id', 'id', true, false, 'RESTRICT') + ->build(); diff --git a/module/Core/src/Entity/Domain.php b/module/Core/src/Entity/Domain.php new file mode 100644 index 00000000..9b0c4f32 --- /dev/null +++ b/module/Core/src/Entity/Domain.php @@ -0,0 +1,22 @@ +authority = $authority; + } + + public function getAuthority(): string + { + return $this->authority; + } +} diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 0f5cf088..fa4d69e1 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -29,6 +29,8 @@ class ShortUrl extends AbstractEntity private $validUntil; /** @var integer|null */ private $maxVisits; + /** @var Domain|null */ + private $domain; public function __construct(string $longUrl, ?ShortUrlMeta $meta = null) { @@ -42,6 +44,7 @@ class ShortUrl extends AbstractEntity $this->validUntil = $meta->getValidUntil(); $this->maxVisits = $meta->getMaxVisits(); $this->shortCode = $meta->getCustomSlug() ?? ''; // TODO logic to calculate short code should be passed somehow + $this->domain = $meta->hasDomain() ? new Domain($meta->getDomain()) : null; } public function getLongUrl(): string @@ -131,4 +134,13 @@ class ShortUrl extends AbstractEntity { return $this->maxVisits !== null && $this->getVisitsCount() >= $this->maxVisits; } + + public function domain(string $fallback = ''): string + { + if ($this->domain === null) { + return $fallback; + } + + return $this->domain->getAuthority(); + } } diff --git a/module/Core/src/Model/ShortUrlMeta.php b/module/Core/src/Model/ShortUrlMeta.php index 2eb6ebca..b6d00834 100644 --- a/module/Core/src/Model/ShortUrlMeta.php +++ b/module/Core/src/Model/ShortUrlMeta.php @@ -19,6 +19,8 @@ final class ShortUrlMeta private $maxVisits; /** @var bool|null */ private $findIfExists; + /** @var string|null */ + private $domain; // Force named constructors private function __construct() @@ -47,6 +49,7 @@ final class ShortUrlMeta * @param string|null $customSlug * @param int|null $maxVisits * @param bool|null $findIfExists + * @param string|null $domain * @throws ValidationException */ public static function createFromParams( @@ -54,7 +57,8 @@ final class ShortUrlMeta $validUntil = null, $customSlug = null, $maxVisits = null, - $findIfExists = null + $findIfExists = null, + $domain = null ): self { // We do not type hint the arguments because that will be done by the validation process and we would get a // type error if any of them do not match @@ -65,6 +69,7 @@ final class ShortUrlMeta ShortUrlMetaInputFilter::CUSTOM_SLUG => $customSlug, ShortUrlMetaInputFilter::MAX_VISITS => $maxVisits, ShortUrlMetaInputFilter::FIND_IF_EXISTS => $findIfExists, + ShortUrlMetaInputFilter::DOMAIN => $domain, ]); return $instance; } @@ -86,6 +91,7 @@ final class ShortUrlMeta $this->maxVisits = $inputFilter->getValue(ShortUrlMetaInputFilter::MAX_VISITS); $this->maxVisits = $this->maxVisits !== null ? (int) $this->maxVisits : null; $this->findIfExists = $inputFilter->getValue(ShortUrlMetaInputFilter::FIND_IF_EXISTS); + $this->domain = $inputFilter->getValue(ShortUrlMetaInputFilter::DOMAIN); } /** @@ -144,4 +150,14 @@ final class ShortUrlMeta { return (bool) $this->findIfExists; } + + public function hasDomain(): bool + { + return $this->domain !== null; + } + + public function getDomain(): ?string + { + return $this->domain; + } } diff --git a/module/Core/src/Validation/ShortUrlMetaInputFilter.php b/module/Core/src/Validation/ShortUrlMetaInputFilter.php index fad63f6c..afeefafb 100644 --- a/module/Core/src/Validation/ShortUrlMetaInputFilter.php +++ b/module/Core/src/Validation/ShortUrlMetaInputFilter.php @@ -17,6 +17,7 @@ class ShortUrlMetaInputFilter extends InputFilter public const CUSTOM_SLUG = 'customSlug'; public const MAX_VISITS = 'maxVisits'; public const FIND_IF_EXISTS = 'findIfExists'; + public const DOMAIN = 'domain'; public function __construct(?array $data = null) { @@ -46,5 +47,11 @@ class ShortUrlMetaInputFilter extends InputFilter $this->add($maxVisits); $this->add($this->createBooleanInput(self::FIND_IF_EXISTS, false)); + + $domain = $this->createInput(self::DOMAIN, false); + $domain->getValidatorChain()->attach(new Validator\Hostname([ + 'allow' => Validator\Hostname::ALLOW_DNS | Validator\Hostname::ALLOW_LOCAL, + ])); + $this->add($domain); } }