Added logic to persist device long URLs while creating/editing a short URL

This commit is contained in:
Alejandro Celaya
2023-01-15 13:08:21 +01:00
parent fdadf3ba07
commit a93edf158e
22 changed files with 142 additions and 98 deletions

View File

@@ -51,7 +51,7 @@ class DomainService implements DomainServiceInterface
$repo = $this->em->getRepository(Domain::class);
$groups = group(
$repo->findDomains($apiKey),
fn (Domain $domain) => $domain->getAuthority() === $this->defaultDomain ? 'default' : 'domains',
fn (Domain $domain) => $domain->authority() === $this->defaultDomain ? 'default' : 'domains',
);
return [first($groups['default'] ?? []), $groups['domains'] ?? []];

View File

@@ -24,14 +24,14 @@ class Domain extends AbstractEntity implements JsonSerializable, NotFoundRedirec
return new self($authority);
}
public function getAuthority(): string
public function authority(): string
{
return $this->authority;
}
public function jsonSerialize(): string
{
return $this->getAuthority();
return $this->authority;
}
public function invalidShortUrlRedirect(): ?string

View File

@@ -20,7 +20,7 @@ final class DomainItem implements JsonSerializable
public static function forNonDefaultDomain(Domain $domain): self
{
return new self($domain->getAuthority(), $domain, false);
return new self($domain->authority(), $domain, false);
}
public static function forDefaultDomain(string $defaultDomain, NotFoundRedirectConfigInterface $config): self

View File

@@ -10,17 +10,16 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\DeviceLongUrlPair;
class DeviceLongUrl extends AbstractEntity
{
private ShortUrl $shortUrl; // @phpstan-ignore-line
private function __construct(
private readonly ShortUrl $shortUrl, // No need to read this field. It's used by doctrine
public readonly DeviceType $deviceType,
private string $longUrl,
) {
}
public static function fromPair(DeviceLongUrlPair $pair): self
public static function fromShortUrlAndPair(ShortUrl $shortUrl, DeviceLongUrlPair $pair): self
{
return new self($pair->deviceType, $pair->longUrl);
return new self($shortUrl, $pair->deviceType, $pair->longUrl);
}
public function longUrl(): string

View File

@@ -12,6 +12,7 @@ use Doctrine\Common\Collections\Selectable;
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
use Shlinkio\Shlink\Core\Domain\Entity\Domain;
use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException;
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\Validation\ShortUrlInputFilter;
@@ -24,6 +25,7 @@ use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
use function count;
use function Functional\map;
use function Shlinkio\Shlink\Core\generateRandomShortCode;
use function Shlinkio\Shlink\Core\normalizeDate;
use function Shlinkio\Shlink\Core\normalizeOptionalDate;
@@ -35,6 +37,8 @@ class ShortUrl extends AbstractEntity
private Chronos $dateCreated;
/** @var Collection<int, Visit> */
private Collection $visits;
/** @var Collection<int, DeviceLongUrl> */
private Collection $deviceLongUrls;
/** @var Collection<int, Tag> */
private Collection $tags;
private ?Chronos $validSince = null;
@@ -81,6 +85,10 @@ class ShortUrl extends AbstractEntity
$instance->longUrl = $creation->getLongUrl();
$instance->dateCreated = Chronos::now();
$instance->visits = new ArrayCollection();
$instance->deviceLongUrls = new ArrayCollection(map(
$creation->deviceLongUrls,
fn (DeviceLongUrlPair $pair) => DeviceLongUrl::fromShortUrlAndPair($instance, $pair),
));
$instance->tags = $relationResolver->resolveTags($creation->tags);
$instance->validSince = $creation->validSince;
$instance->validUntil = $creation->validUntil;
@@ -126,6 +134,53 @@ class ShortUrl extends AbstractEntity
return $instance;
}
public function update(
ShortUrlEdition $shortUrlEdit,
?ShortUrlRelationResolverInterface $relationResolver = null,
): void {
if ($shortUrlEdit->validSinceWasProvided()) {
$this->validSince = $shortUrlEdit->validSince;
}
if ($shortUrlEdit->validUntilWasProvided()) {
$this->validUntil = $shortUrlEdit->validUntil;
}
if ($shortUrlEdit->maxVisitsWasProvided()) {
$this->maxVisits = $shortUrlEdit->maxVisits;
}
if ($shortUrlEdit->longUrlWasProvided()) {
$this->longUrl = $shortUrlEdit->longUrl ?? $this->longUrl;
}
if ($shortUrlEdit->tagsWereProvided()) {
$relationResolver = $relationResolver ?? new SimpleShortUrlRelationResolver();
$this->tags = $relationResolver->resolveTags($shortUrlEdit->tags);
}
if ($shortUrlEdit->crawlableWasProvided()) {
$this->crawlable = $shortUrlEdit->crawlable;
}
if (
$this->title === null
|| $shortUrlEdit->titleWasProvided()
|| ($this->titleWasAutoResolved && $shortUrlEdit->titleWasAutoResolved())
) {
$this->title = $shortUrlEdit->title;
$this->titleWasAutoResolved = $shortUrlEdit->titleWasAutoResolved();
}
if ($shortUrlEdit->forwardQueryWasProvided()) {
$this->forwardQuery = $shortUrlEdit->forwardQuery;
}
foreach ($shortUrlEdit->deviceLongUrls as $deviceLongUrlPair) {
$deviceLongUrl = $this->deviceLongUrls->findFirst(
fn ($_, DeviceLongUrl $d) => $d->deviceType === $deviceLongUrlPair->deviceType,
);
if ($deviceLongUrl !== null) {
$deviceLongUrl->updateLongUrl($deviceLongUrlPair->longUrl);
} else {
$this->deviceLongUrls->add(DeviceLongUrl::fromShortUrlAndPair($this, $deviceLongUrlPair));
}
}
}
public function getLongUrl(): string
{
return $this->longUrl;
@@ -224,42 +279,6 @@ class ShortUrl extends AbstractEntity
return $this->forwardQuery;
}
public function update(
ShortUrlEdition $shortUrlEdit,
?ShortUrlRelationResolverInterface $relationResolver = null,
): void {
if ($shortUrlEdit->validSinceWasProvided()) {
$this->validSince = $shortUrlEdit->validSince;
}
if ($shortUrlEdit->validUntilWasProvided()) {
$this->validUntil = $shortUrlEdit->validUntil;
}
if ($shortUrlEdit->maxVisitsWasProvided()) {
$this->maxVisits = $shortUrlEdit->maxVisits;
}
if ($shortUrlEdit->longUrlWasProvided()) {
$this->longUrl = $shortUrlEdit->longUrl ?? $this->longUrl;
}
if ($shortUrlEdit->tagsWereProvided()) {
$relationResolver = $relationResolver ?? new SimpleShortUrlRelationResolver();
$this->tags = $relationResolver->resolveTags($shortUrlEdit->tags);
}
if ($shortUrlEdit->crawlableWasProvided()) {
$this->crawlable = $shortUrlEdit->crawlable;
}
if (
$this->title === null
|| $shortUrlEdit->titleWasProvided()
|| ($this->titleWasAutoResolved && $shortUrlEdit->titleWasAutoResolved())
) {
$this->title = $shortUrlEdit->title;
$this->titleWasAutoResolved = $shortUrlEdit->titleWasAutoResolved();
}
if ($shortUrlEdit->forwardQueryWasProvided()) {
$this->forwardQuery = $shortUrlEdit->forwardQuery;
}
}
/**
* @throws ShortCodeCannotBeRegeneratedException
*/
@@ -298,4 +317,14 @@ class ShortUrl extends AbstractEntity
return true;
}
public function deviceLongUrls(): array
{
$data = [];
foreach ($this->deviceLongUrls as $deviceUrl) {
$data[$deviceUrl->deviceType->value] = $deviceUrl->longUrl();
}
return $data;
}
}

View File

@@ -11,7 +11,7 @@ use function sprintf;
class ShortUrlStringifier implements ShortUrlStringifierInterface
{
public function __construct(private array $domainConfig, private string $basePath = '')
public function __construct(private readonly array $domainConfig, private readonly string $basePath = '')
{
}
@@ -28,6 +28,6 @@ class ShortUrlStringifier implements ShortUrlStringifierInterface
private function resolveDomain(ShortUrl $shortUrl): string
{
return $shortUrl->getDomain()?->getAuthority() ?? $this->domainConfig['hostname'] ?? '';
return $shortUrl->getDomain()?->authority() ?? $this->domainConfig['hostname'] ?? '';
}
}

View File

@@ -18,6 +18,7 @@ final class ShortUrlEdition implements TitleResolutionModelInterface
{
/**
* @param string[] $tags
* @param DeviceLongUrlPair[] $deviceLongUrls
*/
private function __construct(
private readonly bool $longUrlPropWasProvided = false,

View File

@@ -45,7 +45,7 @@ final class ShortUrlIdentifier
public static function fromShortUrl(ShortUrl $shortUrl): self
{
$domain = $shortUrl->getDomain();
$domainAuthority = $domain?->getAuthority();
$domainAuthority = $domain?->authority();
return new self($shortUrl->getShortCode(), $domainAuthority);
}

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Resolver;
use Doctrine\Common\Collections;
use Doctrine\Common\Collections\Collection;
use Shlinkio\Shlink\Core\Domain\Entity\Domain;
use Shlinkio\Shlink\Core\Tag\Entity\Tag;
@@ -20,7 +19,7 @@ class SimpleShortUrlRelationResolver implements ShortUrlRelationResolverInterfac
/**
* @param string[] $tags
* @return Collection<int, Tag>
* @return Collections\Collection<int, Tag>
*/
public function resolveTags(array $tags): Collections\Collection
{

View File

@@ -76,7 +76,7 @@ class UrlShortener implements UrlShortenerInterface
if (! $couldBeMadeUnique) {
$domain = $shortUrlToBeCreated->getDomain();
$domainAuthority = $domain?->getAuthority();
$domainAuthority = $domain?->authority();
throw NonUniqueSlugException::fromSlug($shortUrlToBeCreated->getShortCode(), $domainAuthority);
}

View File

@@ -15,9 +15,9 @@ use Shlinkio\Shlink\Core\Visit\Model\Visitor;
class VisitsTracker implements VisitsTrackerInterface
{
public function __construct(
private ORM\EntityManagerInterface $em,
private EventDispatcherInterface $eventDispatcher,
private TrackingOptions $options,
private readonly ORM\EntityManagerInterface $em,
private readonly EventDispatcherInterface $eventDispatcher,
private readonly TrackingOptions $options,
) {
}
@@ -62,6 +62,9 @@ class VisitsTracker implements VisitsTrackerInterface
$this->trackVisit($createVisit, $visitor);
}
/**
* @param callable(Visitor $visitor): Visit $createVisit
*/
private function trackVisit(callable $createVisit, Visitor $visitor): void
{
if ($this->options->disableTracking) {