diff --git a/composer.json b/composer.json index c56d584a..c346e661 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,7 @@ "shlinkio/shlink-common": "^3.2.0", "shlinkio/shlink-config": "^1.0", "shlinkio/shlink-event-dispatcher": "^1.4", - "shlinkio/shlink-importer": "^2.0", + "shlinkio/shlink-importer": "^2.0.1", "shlinkio/shlink-installer": "^5.1.0", "shlinkio/shlink-ip-geolocation": "^1.5", "symfony/console": "^5.1", diff --git a/data/migrations/Version20201023090929.php b/data/migrations/Version20201023090929.php index 49a401ba..05d16c22 100644 --- a/data/migrations/Version20201023090929.php +++ b/data/migrations/Version20201023090929.php @@ -21,6 +21,15 @@ final class Version20201023090929 extends AbstractMigration 'length' => 255, 'notnull' => false, ]); + $shortUrls->addColumn('import_original_short_code', Types::STRING, [ + 'length' => 255, + 'notnull' => false, + ]); + + $shortUrls->addUniqueIndex( + [self::IMPORT_SOURCE_COLUMN, 'import_original_short_code', 'domain_id'], + 'unique_imports', + ); } public function down(Schema $schema): void @@ -29,5 +38,7 @@ final class Version20201023090929 extends AbstractMigration $this->skipIf(! $shortUrls->hasColumn(self::IMPORT_SOURCE_COLUMN)); $shortUrls->dropColumn(self::IMPORT_SOURCE_COLUMN); + $shortUrls->dropColumn('import_original_short_code'); + $shortUrls->dropIndex('unique_imports'); } } 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 55fa230e..4f9b3747 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 @@ -56,6 +56,11 @@ return static function (ClassMetadata $metadata, array $emConfig): void { ->nullable() ->build(); + $builder->createField('importOriginalShortCode', Types::STRING) + ->columnName('import_original_short_code') + ->nullable() + ->build(); + $builder->createOneToMany('visits', Entity\Visit::class) ->mappedBy('shortUrl') ->fetchExtraLazy() diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 8afa8206..2d5cb6de 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -36,6 +36,7 @@ class ShortUrl extends AbstractEntity private bool $customSlugWasProvided; private int $shortCodeLength; private ?string $importSource = null; + private ?string $importOriginalShortCode = null; public function __construct( string $longUrl, @@ -72,6 +73,7 @@ class ShortUrl extends AbstractEntity $instance = new self($url->longUrl(), ShortUrlMeta::fromRawData($meta), $domainResolver); $instance->importSource = $url->source(); + $instance->importOriginalShortCode = $url->shortCode(); $instance->dateCreated = Chronos::instance($url->createdAt()); return $instance; diff --git a/module/Core/src/Importer/ImportedLinksProcessor.php b/module/Core/src/Importer/ImportedLinksProcessor.php index 90866057..b76ada9a 100644 --- a/module/Core/src/Importer/ImportedLinksProcessor.php +++ b/module/Core/src/Importer/ImportedLinksProcessor.php @@ -50,7 +50,7 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface $longUrl = $url->longUrl(); // Skip already imported URLs - if ($shortUrlRepo->importedUrlExists($url, $importShortCodes)) { + if ($shortUrlRepo->importedUrlExists($url)) { $io->text(sprintf('%s: Skipped', $longUrl)); continue; } @@ -58,7 +58,7 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface $shortUrl = ShortUrl::fromImport($url, $importShortCodes, $this->domainResolver); $shortUrl->setTags($this->tagNamesToEntities($this->em, $url->tags())); - if (! $this->handleShortcodeUniqueness($url, $shortUrl, $io, $importShortCodes)) { + if (! $this->handleShortCodeUniqueness($url, $shortUrl, $io, $importShortCodes)) { continue; } @@ -67,7 +67,7 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface } } - private function handleShortcodeUniqueness( + private function handleShortCodeUniqueness( ImportedShlinkUrl $url, ShortUrl $shortUrl, StyleInterface $io, @@ -90,6 +90,6 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface return false; } - return $this->handleShortcodeUniqueness($url, $shortUrl, $io, false); + return $this->handleShortCodeUniqueness($url, $shortUrl, $io, false); } } diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index 39e15d79..27dac54b 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -190,13 +190,7 @@ DQL; ->setParameter('slug', $slug) ->setMaxResults(1); - if ($domain !== null) { - $qb->join('s.domain', 'd') - ->andWhere($qb->expr()->eq('d.authority', ':authority')) - ->setParameter('authority', $domain); - } else { - $qb->andWhere($qb->expr()->isNull('s.domain')); - } + $this->whereDomainIs($qb, $domain); return $qb; } @@ -256,15 +250,31 @@ DQL; return $qb->getQuery()->getOneOrNullResult(); } - public function importedUrlExists(ImportedShlinkUrl $url, bool $importShortCodes): bool + public function importedUrlExists(ImportedShlinkUrl $url): bool { - $findConditions = ['importSource' => $url->source()]; - if ($importShortCodes) { - $findConditions['shortCode'] = $url->shortCode(); - } else { - $findConditions['longUrl'] = $url->longUrl(); - } + $qb = $this->getEntityManager()->createQueryBuilder(); + $qb->select('COUNT(DISTINCT s.id)') + ->from(ShortUrl::class, 's') + ->andWhere($qb->expr()->eq('s.importOriginalShortCode', ':shortCode')) + ->setParameter('shortCode', $url->shortCode()) + ->andWhere($qb->expr()->eq('s.importSource', ':importSource')) + ->setParameter('importSource', $url->source()) + ->setMaxResults(1); - return $this->count($findConditions) > 0; + $this->whereDomainIs($qb, $url->domain()); + + $result = (int) $qb->getQuery()->getSingleScalarResult(); + return $result > 0; + } + + private function whereDomainIs(QueryBuilder $qb, ?string $domain): void + { + if ($domain !== null) { + $qb->join('s.domain', 'd') + ->andWhere($qb->expr()->eq('d.authority', ':authority')) + ->setParameter('authority', $domain); + } else { + $qb->andWhere($qb->expr()->isNull('s.domain')); + } } } diff --git a/module/Core/src/Repository/ShortUrlRepositoryInterface.php b/module/Core/src/Repository/ShortUrlRepositoryInterface.php index b6790543..1d6f38a8 100644 --- a/module/Core/src/Repository/ShortUrlRepositoryInterface.php +++ b/module/Core/src/Repository/ShortUrlRepositoryInterface.php @@ -32,5 +32,5 @@ interface ShortUrlRepositoryInterface extends ObjectRepository public function findOneMatching(string $url, array $tags, ShortUrlMeta $meta): ?ShortUrl; - public function importedUrlExists(ImportedShlinkUrl $url, bool $importShortCodes): bool; + public function importedUrlExists(ImportedShlinkUrl $url): bool; }