Added support for an optional title field in short URLs

This commit is contained in:
Alejandro Celaya 2021-02-02 20:21:48 +01:00
parent 31a7212a71
commit 430c407106
8 changed files with 71 additions and 7 deletions

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
final class Version20210202181026 extends AbstractMigration
{
private const TITLE = 'title';
public function up(Schema $schema): void
{
$shortUrls = $schema->getTable('short_urls');
$this->skipIf($shortUrls->hasColumn(self::TITLE));
$shortUrls->addColumn(self::TITLE, Types::STRING, [
'notnull' => false,
'length' => 512,
]);
}
public function down(Schema $schema): void
{
$shortUrls = $schema->getTable('short_urls');
$this->skipIf(! $shortUrls->hasColumn(self::TITLE));
$shortUrls->dropColumn(self::TITLE);
}
}

View file

@ -84,4 +84,10 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
->build();
$builder->addUniqueConstraint(['short_code', 'domain_id'], 'unique_short_code_plus_domain');
$builder->createField('title', Types::STRING)
->columnName('title')
->length(512)
->nullable()
->build();
};

View file

@ -38,6 +38,7 @@ class ShortUrl extends AbstractEntity
private ?string $importSource = null;
private ?string $importOriginalShortCode = null;
private ?ApiKey $authorApiKey = null;
private ?string $title = null;
private function __construct()
{
@ -72,6 +73,7 @@ class ShortUrl extends AbstractEntity
$instance->shortCode = $meta->getCustomSlug() ?? generateRandomShortCode($instance->shortCodeLength);
$instance->domain = $relationResolver->resolveDomain($meta->getDomain());
$instance->authorApiKey = $meta->getApiKey();
$instance->title = $meta->getTitle();
return $instance;
}
@ -157,6 +159,11 @@ class ShortUrl extends AbstractEntity
return $this->maxVisits;
}
public function getTitle(): ?string
{
return $this->title;
}
public function update(
ShortUrlEdit $shortUrlEdit,
?ShortUrlRelationResolverInterface $relationResolver = null

View file

@ -28,6 +28,7 @@ final class ShortUrlMeta
private ?bool $validateUrl = null;
private ?ApiKey $apiKey = null;
private array $tags = [];
private ?string $title = null;
private function __construct()
{
@ -76,6 +77,7 @@ final class ShortUrlMeta
) ?? DEFAULT_SHORT_CODES_LENGTH;
$this->apiKey = $inputFilter->getValue(ShortUrlInputFilter::API_KEY);
$this->tags = $inputFilter->getValue(ShortUrlInputFilter::TAGS);
$this->title = $inputFilter->getValue(ShortUrlInputFilter::TITLE);
}
public function getLongUrl(): string
@ -160,4 +162,9 @@ final class ShortUrlMeta
{
return $this->tags;
}
public function getTitle(): ?string
{
return $this->title;
}
}

View file

@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Core\Model;
use Shlinkio\Shlink\Core\Exception\ValidationException;
use function array_pad;
use function explode;
use function is_array;
use function is_string;
@ -50,9 +51,9 @@ final class ShortUrlsOrdering
/** @var string|array $orderBy */
if (! $isArray) {
$parts = explode('-', $orderBy);
$this->orderField = $parts[0];
$this->orderDirection = $parts[1] ?? self::DEFAULT_ORDER_DIRECTION;
[$field, $dir] = array_pad(explode('-', $orderBy), 2, null);
$this->orderField = $field;
$this->orderDirection = $dir ?? self::DEFAULT_ORDER_DIRECTION;
} else {
$this->orderField = key($orderBy);
$this->orderDirection = $orderBy[$this->orderField];

View file

@ -34,6 +34,7 @@ class ShortUrlDataTransformer implements DataTransformerInterface
'tags' => invoke($shortUrl->getTags(), '__toString'),
'meta' => $this->buildMeta($shortUrl),
'domain' => $shortUrl->getDomain(),
'title' => $shortUrl->getTitle(),
];
}

View file

@ -31,6 +31,7 @@ class ShortUrlInputFilter extends InputFilter
public const VALIDATE_URL = 'validateUrl';
public const API_KEY = 'apiKey';
public const TAGS = 'tags';
public const TITLE = 'title';
private function __construct(array $data, bool $requireLongUrl)
{
@ -87,6 +88,8 @@ class ShortUrlInputFilter extends InputFilter
$this->add($this->createBooleanInput(self::FIND_IF_EXISTS, false));
// This cannot be defined as a boolean input because it can actually have 3 values, true, false and null.
// Defining it as boolean will make null fall back to false, which is not the desired behavior.
$this->add($this->createInput(self::VALIDATE_URL, false));
$domain = $this->createInput(self::DOMAIN, false);
@ -100,5 +103,7 @@ class ShortUrlInputFilter extends InputFilter
$this->add($apiKeyInput);
$this->add($this->createTagsInput(self::TAGS, false));
$this->add($this->createInput(self::TITLE, false));
}
}

View file

@ -28,9 +28,13 @@ class MercureUpdatesGeneratorTest extends TestCase
* @test
* @dataProvider provideMethod
*/
public function visitIsProperlySerializedIntoUpdate(string $method, string $expectedTopic): void
public function visitIsProperlySerializedIntoUpdate(string $method, string $expectedTopic, ?string $title): void
{
$shortUrl = ShortUrl::fromMeta(ShortUrlMeta::fromRawData(['customSlug' => 'foo', 'longUrl' => '']));
$shortUrl = ShortUrl::fromMeta(ShortUrlMeta::fromRawData([
'customSlug' => 'foo',
'longUrl' => '',
'title' => $title,
]));
$visit = new Visit($shortUrl, Visitor::emptyInstance());
$update = $this->generator->{$method}($visit);
@ -50,6 +54,7 @@ class MercureUpdatesGeneratorTest extends TestCase
'maxVisits' => null,
],
'domain' => null,
'title' => $title,
],
'visit' => [
'referer' => '',
@ -62,7 +67,7 @@ class MercureUpdatesGeneratorTest extends TestCase
public function provideMethod(): iterable
{
yield 'newVisitUpdate' => ['newVisitUpdate', 'https://shlink.io/new-visit'];
yield 'newShortUrlVisitUpdate' => ['newShortUrlVisitUpdate', 'https://shlink.io/new-visit/foo'];
yield 'newVisitUpdate' => ['newVisitUpdate', 'https://shlink.io/new-visit', 'the cool title'];
yield 'newShortUrlVisitUpdate' => ['newShortUrlVisitUpdate', 'https://shlink.io/new-visit/foo', null];
}
}