diff --git a/data/migrations/Version20210522051601.php b/data/migrations/Version20210522051601.php new file mode 100644 index 00000000..9e2bd19e --- /dev/null +++ b/data/migrations/Version20210522051601.php @@ -0,0 +1,26 @@ +getTable('short_urls'); + $this->skipIf($shortUrls->hasColumn('crawlable')); + $shortUrls->addColumn('crawlable', Types::BOOLEAN, ['default' => false]); + } + + public function down(Schema $schema): void + { + $shortUrls = $schema->getTable('short_urls'); + $this->skipIf(! $shortUrls->hasColumn('crawlable')); + $shortUrls->dropColumn('crawlable'); + } +} 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 751e513c..a9269d36 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 @@ -95,4 +95,9 @@ return static function (ClassMetadata $metadata, array $emConfig): void { ->columnName('title_was_auto_resolved') ->option('default', false) ->build(); + + $builder->createField('crawlable', Types::BOOLEAN) + ->columnName('crawlable') + ->option('default', false) + ->build(); }; diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 3fe2932b..6f502da3 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -42,6 +42,7 @@ class ShortUrl extends AbstractEntity private ?ApiKey $authorApiKey = null; private ?string $title = null; private bool $titleWasAutoResolved = false; + private bool $crawlable = false; private function __construct() { @@ -78,6 +79,7 @@ class ShortUrl extends AbstractEntity $instance->authorApiKey = $meta->getApiKey(); $instance->title = $meta->getTitle(); $instance->titleWasAutoResolved = $meta->titleWasAutoResolved(); + $instance->crawlable = $meta->isCrawlable(); return $instance; } @@ -200,6 +202,11 @@ class ShortUrl extends AbstractEntity return $this->title; } + public function crawlable(): bool + { + return $this->crawlable; + } + public function update( ShortUrlEdit $shortUrlEdit, ?ShortUrlRelationResolverInterface $relationResolver = null @@ -220,6 +227,9 @@ class ShortUrl extends AbstractEntity $relationResolver = $relationResolver ?? new SimpleShortUrlRelationResolver(); $this->tags = $relationResolver->resolveTags($shortUrlEdit->tags()); } + if ($shortUrlEdit->crawlableWasProvided()) { + $this->crawlable = $shortUrlEdit->crawlable(); + } if ( $this->title === null || $shortUrlEdit->titleWasProvided() diff --git a/module/Core/src/Model/ShortUrlEdit.php b/module/Core/src/Model/ShortUrlEdit.php index 3327aad4..32c1ca1e 100644 --- a/module/Core/src/Model/ShortUrlEdit.php +++ b/module/Core/src/Model/ShortUrlEdit.php @@ -30,6 +30,8 @@ final class ShortUrlEdit implements TitleResolutionModelInterface private ?string $title = null; private bool $titleWasAutoResolved = false; private ?bool $validateUrl = null; + private bool $crawlablePropWasProvided = false; + private bool $crawlable = false; private function __construct() { @@ -61,6 +63,7 @@ final class ShortUrlEdit implements TitleResolutionModelInterface $this->maxVisitsPropWasProvided = array_key_exists(ShortUrlInputFilter::MAX_VISITS, $data); $this->tagsPropWasProvided = array_key_exists(ShortUrlInputFilter::TAGS, $data); $this->titlePropWasProvided = array_key_exists(ShortUrlInputFilter::TITLE, $data); + $this->crawlablePropWasProvided = array_key_exists(ShortUrlInputFilter::CRAWLABLE, $data); $this->longUrl = $inputFilter->getValue(ShortUrlInputFilter::LONG_URL); $this->validSince = parseDateField($inputFilter->getValue(ShortUrlInputFilter::VALID_SINCE)); @@ -69,6 +72,7 @@ final class ShortUrlEdit implements TitleResolutionModelInterface $this->validateUrl = getOptionalBoolFromInputFilter($inputFilter, ShortUrlInputFilter::VALIDATE_URL); $this->tags = $inputFilter->getValue(ShortUrlInputFilter::TAGS); $this->title = $inputFilter->getValue(ShortUrlInputFilter::TITLE); + $this->crawlable = $inputFilter->getValue(ShortUrlInputFilter::CRAWLABLE); } public function longUrl(): ?string @@ -162,4 +166,14 @@ final class ShortUrlEdit implements TitleResolutionModelInterface { return $this->validateUrl; } + + public function crawlable(): bool + { + return $this->crawlable; + } + + public function crawlableWasProvided(): bool + { + return $this->crawlablePropWasProvided; + } } diff --git a/module/Core/src/Model/ShortUrlMeta.php b/module/Core/src/Model/ShortUrlMeta.php index df25735c..06e0eee7 100644 --- a/module/Core/src/Model/ShortUrlMeta.php +++ b/module/Core/src/Model/ShortUrlMeta.php @@ -31,6 +31,7 @@ final class ShortUrlMeta implements TitleResolutionModelInterface private array $tags = []; private ?string $title = null; private bool $titleWasAutoResolved = false; + private bool $crawlable = false; private function __construct() { @@ -80,6 +81,7 @@ final class ShortUrlMeta implements TitleResolutionModelInterface $this->apiKey = $inputFilter->getValue(ShortUrlInputFilter::API_KEY); $this->tags = $inputFilter->getValue(ShortUrlInputFilter::TAGS); $this->title = $inputFilter->getValue(ShortUrlInputFilter::TITLE); + $this->crawlable = $inputFilter->getValue(ShortUrlInputFilter::CRAWLABLE); } public function getLongUrl(): string @@ -188,4 +190,9 @@ final class ShortUrlMeta implements TitleResolutionModelInterface return $copy; } + + public function isCrawlable(): bool + { + return $this->crawlable; + } } diff --git a/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php b/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php index 49918867..52b98c36 100644 --- a/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php +++ b/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php @@ -35,6 +35,7 @@ class ShortUrlDataTransformer implements DataTransformerInterface 'meta' => $this->buildMeta($shortUrl), 'domain' => $shortUrl->getDomain(), 'title' => $shortUrl->title(), + 'crawlable' => $shortUrl->crawlable(), ]; } diff --git a/module/Core/src/Validation/ShortUrlInputFilter.php b/module/Core/src/Validation/ShortUrlInputFilter.php index b5d4fa07..c7cdaa43 100644 --- a/module/Core/src/Validation/ShortUrlInputFilter.php +++ b/module/Core/src/Validation/ShortUrlInputFilter.php @@ -32,6 +32,7 @@ class ShortUrlInputFilter extends InputFilter public const API_KEY = 'apiKey'; public const TAGS = 'tags'; public const TITLE = 'title'; + public const CRAWLABLE = 'crawlable'; private function __construct(array $data, bool $requireLongUrl) { @@ -105,5 +106,7 @@ class ShortUrlInputFilter extends InputFilter $this->add($this->createTagsInput(self::TAGS, false)); $this->add($this->createInput(self::TITLE, false)); + + $this->add($this->createBooleanInput(self::CRAWLABLE, false)); } } diff --git a/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php b/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php index b4361ca5..1d460623 100644 --- a/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php +++ b/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php @@ -59,6 +59,7 @@ class MercureUpdatesGeneratorTest extends TestCase ], 'domain' => null, 'title' => $title, + 'crawlable' => false, ], 'visit' => [ 'referer' => '', diff --git a/module/Rest/test-api/Action/ListShortUrlsTest.php b/module/Rest/test-api/Action/ListShortUrlsTest.php index f81524ae..95d77dc6 100644 --- a/module/Rest/test-api/Action/ListShortUrlsTest.php +++ b/module/Rest/test-api/Action/ListShortUrlsTest.php @@ -26,6 +26,7 @@ class ListShortUrlsTest extends ApiTestCase ], 'domain' => null, 'title' => 'My cool title', + 'crawlable' => true, ]; private const SHORT_URL_DOCS = [ 'shortCode' => 'ghi789', @@ -41,6 +42,7 @@ class ListShortUrlsTest extends ApiTestCase ], 'domain' => null, 'title' => null, + 'crawlable' => false, ]; private const SHORT_URL_CUSTOM_SLUG_AND_DOMAIN = [ 'shortCode' => 'custom-with-domain', @@ -56,6 +58,7 @@ class ListShortUrlsTest extends ApiTestCase ], 'domain' => 'some-domain.com', 'title' => null, + 'crawlable' => false, ]; private const SHORT_URL_META = [ 'shortCode' => 'def456', @@ -73,6 +76,7 @@ class ListShortUrlsTest extends ApiTestCase ], 'domain' => null, 'title' => null, + 'crawlable' => false, ]; private const SHORT_URL_CUSTOM_SLUG = [ 'shortCode' => 'custom', @@ -88,6 +92,7 @@ class ListShortUrlsTest extends ApiTestCase ], 'domain' => null, 'title' => null, + 'crawlable' => false, ]; private const SHORT_URL_CUSTOM_DOMAIN = [ 'shortCode' => 'ghi789', @@ -105,6 +110,7 @@ class ListShortUrlsTest extends ApiTestCase ], 'domain' => 'example.com', 'title' => null, + 'crawlable' => false, ]; /** diff --git a/module/Rest/test-api/Fixtures/ShortUrlsFixture.php b/module/Rest/test-api/Fixtures/ShortUrlsFixture.php index bfc65aa0..ccc83525 100644 --- a/module/Rest/test-api/Fixtures/ShortUrlsFixture.php +++ b/module/Rest/test-api/Fixtures/ShortUrlsFixture.php @@ -35,6 +35,7 @@ class ShortUrlsFixture extends AbstractFixture implements DependentFixtureInterf 'longUrl' => 'https://shlink.io', 'tags' => ['foo'], 'title' => 'My cool title', + 'crawlable' => true, ]), $relationResolver), '2018-05-01', );