mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-25 18:45:27 -06:00
Implement logic to import redirect rules from other Shlink instances
This commit is contained in:
@@ -262,6 +262,7 @@ return [
|
||||
ShortUrl\Resolver\PersistenceShortUrlRelationResolver::class,
|
||||
ShortUrl\Helper\ShortCodeUniquenessHelper::class,
|
||||
Util\DoctrineBatchHelper::class,
|
||||
RedirectRule\ShortUrlRedirectRuleService::class,
|
||||
],
|
||||
|
||||
Crawling\CrawlingHelper::class => [ShortUrl\Repository\CrawlableShortCodesQuery::class],
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Core\Importer;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
||||
use Shlinkio\Shlink\Core\RedirectRule\ShortUrlRedirectRuleServiceInterface;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortCodeUniquenessHelperInterface;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepository;
|
||||
@@ -32,6 +33,7 @@ readonly class ImportedLinksProcessor implements ImportedLinksProcessorInterface
|
||||
private ShortUrlRelationResolverInterface $relationResolver,
|
||||
private ShortCodeUniquenessHelperInterface $shortCodeHelper,
|
||||
private DoctrineBatchHelperInterface $batchHelper,
|
||||
private ShortUrlRedirectRuleServiceInterface $redirectRuleService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -80,6 +82,7 @@ readonly class ImportedLinksProcessor implements ImportedLinksProcessorInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
$shortUrlImporting->importRedirectRules($importedUrl->redirectRules, $this->em, $this->redirectRuleService);
|
||||
$resultMessage = $shortUrlImporting->importVisits(
|
||||
$this->batchHelper->wrapIterable($importedUrl->visits, 100),
|
||||
$this->em,
|
||||
|
||||
@@ -4,11 +4,17 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Importer;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Shlinkio\Shlink\Core\RedirectRule\Entity\RedirectCondition;
|
||||
use Shlinkio\Shlink\Core\RedirectRule\Entity\ShortUrlRedirectRule;
|
||||
use Shlinkio\Shlink\Core\RedirectRule\ShortUrlRedirectRuleServiceInterface;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Importer\Model\ImportedShlinkRedirectRule;
|
||||
use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit;
|
||||
|
||||
use function Shlinkio\Shlink\Core\ArrayUtils\map;
|
||||
use function Shlinkio\Shlink\Core\normalizeDate;
|
||||
use function sprintf;
|
||||
|
||||
@@ -20,12 +26,12 @@ final readonly class ShortUrlImporting
|
||||
|
||||
public static function fromExistingShortUrl(ShortUrl $shortUrl): self
|
||||
{
|
||||
return new self($shortUrl, false);
|
||||
return new self($shortUrl, isNew: false);
|
||||
}
|
||||
|
||||
public static function fromNewShortUrl(ShortUrl $shortUrl): self
|
||||
{
|
||||
return new self($shortUrl, true);
|
||||
return new self($shortUrl, isNew: true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,6 +61,38 @@ final readonly class ShortUrlImporting
|
||||
: sprintf('<comment>Skipped</comment>. Imported <info>%s</info> visits', $importedVisits);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportedShlinkRedirectRule[] $rules
|
||||
*/
|
||||
public function importRedirectRules(
|
||||
array $rules,
|
||||
EntityManagerInterface $em,
|
||||
ShortUrlRedirectRuleServiceInterface $redirectRuleService,
|
||||
): void {
|
||||
$shortUrl = $this->resolveShortUrl($em);
|
||||
$redirectRules = map(
|
||||
$rules,
|
||||
function (ImportedShlinkRedirectRule $rule, int|string|float $index) use ($shortUrl): ShortUrlRedirectRule {
|
||||
$conditions = new ArrayCollection();
|
||||
foreach ($rule->conditions as $cond) {
|
||||
$redirectCondition = RedirectCondition::fromImport($cond);
|
||||
if ($redirectCondition !== null) {
|
||||
$conditions->add($redirectCondition);
|
||||
}
|
||||
}
|
||||
|
||||
return new ShortUrlRedirectRule(
|
||||
shortUrl: $shortUrl,
|
||||
priority: ((int) $index) + 1,
|
||||
longUrl:$rule->longUrl,
|
||||
conditions: $conditions,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
$redirectRuleService->saveRulesForShortUrl($shortUrl, $redirectRules);
|
||||
}
|
||||
|
||||
private function resolveShortUrl(EntityManagerInterface $em): ShortUrl
|
||||
{
|
||||
// If wrapped ShortUrl has no ID, avoid trying to query the EM, as it would fail in Postgres.
|
||||
|
||||
@@ -9,6 +9,7 @@ use Shlinkio\Shlink\Core\Model\DeviceType;
|
||||
use Shlinkio\Shlink\Core\RedirectRule\Model\RedirectConditionType;
|
||||
use Shlinkio\Shlink\Core\RedirectRule\Model\Validation\RedirectRulesInputFilter;
|
||||
use Shlinkio\Shlink\Core\Util\IpAddressUtils;
|
||||
use Shlinkio\Shlink\Importer\Model\ImportedShlinkRedirectCondition;
|
||||
|
||||
use function Shlinkio\Shlink\Core\acceptLanguageToLocales;
|
||||
use function Shlinkio\Shlink\Core\ArrayUtils\some;
|
||||
@@ -72,6 +73,23 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
||||
return new self($type, $value, $key);
|
||||
}
|
||||
|
||||
public static function fromImport(ImportedShlinkRedirectCondition $cond): self|null
|
||||
{
|
||||
$type = RedirectConditionType::tryFrom($cond->type);
|
||||
if ($type === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match ($type) {
|
||||
RedirectConditionType::QUERY_PARAM => self::forQueryParam($cond->matchKey ?? '', $cond->matchValue),
|
||||
RedirectConditionType::LANGUAGE => self::forLanguage($cond->matchValue),
|
||||
RedirectConditionType::DEVICE => self::forDevice(DeviceType::from($cond->matchValue)),
|
||||
RedirectConditionType::IP_ADDRESS => self::forIpAddress($cond->matchValue),
|
||||
RedirectConditionType::GEOLOCATION_COUNTRY_CODE => self::forGeolocationCountryCode($cond->matchValue),
|
||||
RedirectConditionType::GEOLOCATION_CITY_NAME => self::forGeolocationCityName($cond->matchValue),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if this condition matches provided request
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,7 @@ readonly class ShortUrlRedirectRuleService implements ShortUrlRedirectRuleServic
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShortUrlRedirectRule[]
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function rulesForShortUrl(ShortUrl $shortUrl): array
|
||||
{
|
||||
@@ -31,7 +31,7 @@ readonly class ShortUrlRedirectRuleService implements ShortUrlRedirectRuleServic
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShortUrlRedirectRule[]
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setRulesForShortUrl(ShortUrl $shortUrl, RedirectRulesData $data): array
|
||||
{
|
||||
@@ -55,7 +55,7 @@ readonly class ShortUrlRedirectRuleService implements ShortUrlRedirectRuleServic
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ShortUrlRedirectRule[] $rules
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function saveRulesForShortUrl(ShortUrl $shortUrl, array $rules): void
|
||||
{
|
||||
@@ -74,7 +74,7 @@ readonly class ShortUrlRedirectRuleService implements ShortUrlRedirectRuleServic
|
||||
/**
|
||||
* @param ShortUrlRedirectRule[] $rules
|
||||
*/
|
||||
public function doSetRulesForShortUrl(ShortUrl $shortUrl, array $rules): void
|
||||
private function doSetRulesForShortUrl(ShortUrl $shortUrl, array $rules): void
|
||||
{
|
||||
$this->em->wrapInTransaction(function () use ($shortUrl, $rules): void {
|
||||
// First, delete existing rules for the short URL
|
||||
|
||||
@@ -14,11 +14,13 @@ interface ShortUrlRedirectRuleServiceInterface
|
||||
public function rulesForShortUrl(ShortUrl $shortUrl): array;
|
||||
|
||||
/**
|
||||
* Resolve a set of redirect rules and attach them to a short URL, replacing any already existing rules.
|
||||
* @return ShortUrlRedirectRule[]
|
||||
*/
|
||||
public function setRulesForShortUrl(ShortUrl $shortUrl, RedirectRulesData $data): array;
|
||||
|
||||
/**
|
||||
* Save provided set of rules for a short URL, replacing any already existing rules.
|
||||
* @param ShortUrlRedirectRule[] $rules
|
||||
*/
|
||||
public function saveRulesForShortUrl(ShortUrl $shortUrl, array $rules): void;
|
||||
|
||||
@@ -76,7 +76,6 @@ class ShortUrl extends AbstractEntity
|
||||
|
||||
/**
|
||||
* @param non-empty-string $longUrl
|
||||
* @internal
|
||||
*/
|
||||
public static function withLongUrl(string $longUrl): self
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RuntimeException;
|
||||
use Shlinkio\Shlink\Core\Importer\ImportedLinksProcessor;
|
||||
use Shlinkio\Shlink\Core\RedirectRule\ShortUrlRedirectRuleServiceInterface;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortCodeUniquenessHelperInterface;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepository;
|
||||
@@ -44,13 +45,15 @@ class ImportedLinksProcessorTest extends TestCase
|
||||
private MockObject & ShortCodeUniquenessHelperInterface $shortCodeHelper;
|
||||
private MockObject & ShortUrlRepository $repo;
|
||||
private MockObject & StyleInterface $io;
|
||||
private MockObject & ShortUrlRedirectRuleServiceInterface $redirectRuleService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->em = $this->createMock(EntityManagerInterface::class);
|
||||
$this->repo = $this->createMock(ShortUrlRepository::class);
|
||||
|
||||
$this->shortCodeHelper = $this->createMock(ShortCodeUniquenessHelperInterface::class);
|
||||
$this->redirectRuleService = $this->createMock(ShortUrlRedirectRuleServiceInterface::class);
|
||||
|
||||
$batchHelper = $this->createMock(DoctrineBatchHelperInterface::class);
|
||||
$batchHelper->method('wrapIterable')->willReturnArgument(0);
|
||||
|
||||
@@ -59,6 +62,7 @@ class ImportedLinksProcessorTest extends TestCase
|
||||
new SimpleShortUrlRelationResolver(),
|
||||
$this->shortCodeHelper,
|
||||
$batchHelper,
|
||||
$this->redirectRuleService,
|
||||
);
|
||||
|
||||
$this->io = $this->createMock(StyleInterface::class);
|
||||
|
||||
Reference in New Issue
Block a user