mirror of
https://github.com/shlinkio/shlink.git
synced 2024-11-29 01:18:59 +03:00
Create endpoint to set redirect rules for a short URL
This commit is contained in:
parent
a7cde9364a
commit
d9286765e1
13 changed files with 397 additions and 9 deletions
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'mercure' => [
|
'mercure' => [
|
||||||
'public_hub_url' => 'http://localhost:8001',
|
'public_hub_url' => 'http://localhost:8002',
|
||||||
'internal_hub_url' => 'http://shlink_mercure_proxy',
|
'internal_hub_url' => 'http://shlink_mercure_proxy',
|
||||||
'jwt_secret' => 'mercure_jwt_key_long_enough_to_avoid_error',
|
'jwt_secret' => 'mercure_jwt_key_long_enough_to_avoid_error',
|
||||||
],
|
],
|
||||||
|
|
|
@ -46,6 +46,7 @@ return (static function (): array {
|
||||||
|
|
||||||
//Redirect rules
|
//Redirect rules
|
||||||
Action\RedirectRule\ListRedirectRulesAction::getRouteDef([$dropDomainMiddleware]),
|
Action\RedirectRule\ListRedirectRulesAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\RedirectRule\SetRedirectRulesAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
|
||||||
// Short URLs
|
// Short URLs
|
||||||
Action\ShortUrl\CreateShortUrlAction::getRouteDef([
|
Action\ShortUrl\CreateShortUrlAction::getRouteDef([
|
||||||
|
|
|
@ -131,7 +131,7 @@ services:
|
||||||
container_name: shlink_mercure_proxy
|
container_name: shlink_mercure_proxy
|
||||||
image: nginx:1.25-alpine
|
image: nginx:1.25-alpine
|
||||||
ports:
|
ports:
|
||||||
- "8001:80"
|
- "8002:80"
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/home/shlink/www
|
- ./:/home/shlink/www
|
||||||
- ./data/infra/mercure_proxy_vhost.conf:/etc/nginx/conf.d/default.conf
|
- ./data/infra/mercure_proxy_vhost.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
|
|
@ -15,12 +15,8 @@
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["name", "type", "matchKey", "matchValue"],
|
"required": ["type", "matchKey", "matchValue"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Unique condition name"
|
|
||||||
},
|
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["device", "language", "query"],
|
"enum": ["device", "language", "query"],
|
||||||
|
|
|
@ -137,5 +137,164 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"post": {
|
||||||
|
"operationId": "setShortUrlRedirectRules",
|
||||||
|
"tags": [
|
||||||
|
"Redirect rules"
|
||||||
|
],
|
||||||
|
"summary": "Set short URL redirect rules",
|
||||||
|
"description": "Overwrites redirect rules for a short URL with the ones provided here.",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"$ref": "../parameters/version.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "../parameters/shortCode.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "../parameters/domain.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKey": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "Request body.",
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"redirectRules": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "../definitions/ShortUrlRedirectRule.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "The list of rules",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["defaultLongUrl", "redirectRules"],
|
||||||
|
"properties": {
|
||||||
|
"defaultLongUrl": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"redirectRules": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "../definitions/ShortUrlRedirectRule.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"defaultLongUrl": "https://example.com",
|
||||||
|
"redirectRules": [
|
||||||
|
{
|
||||||
|
"longUrl": "https://example.com/android-en-us",
|
||||||
|
"priority": 1,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"type": "device",
|
||||||
|
"matchValue": "android",
|
||||||
|
"matchKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "language",
|
||||||
|
"matchValue": "en-US",
|
||||||
|
"matchKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"longUrl": "https://example.com/fr",
|
||||||
|
"priority": 2,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"type": "language",
|
||||||
|
"matchValue": "fr",
|
||||||
|
"matchKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"longUrl": "https://example.com/query-foo-bar-hello-world",
|
||||||
|
"priority": 3,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"type": "query",
|
||||||
|
"matchKey": "foo",
|
||||||
|
"matchValue": "bar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "query",
|
||||||
|
"matchKey": "hello",
|
||||||
|
"matchValue": "world"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "No URL was found for provided short code.",
|
||||||
|
"content": {
|
||||||
|
"application/problem+json": {
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["shortCode"],
|
||||||
|
"properties": {
|
||||||
|
"shortCode": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The short code with which we tried to find the short URL"
|
||||||
|
},
|
||||||
|
"domain": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The domain with which we tried to find the short URL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"Short URL not found": {
|
||||||
|
"$ref": "../examples/short-url-not-found-v3.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "Unexpected error.",
|
||||||
|
"content": {
|
||||||
|
"application/problem+json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,11 @@ class ShortUrlRedirectRule extends AbstractEntity implements JsonSerializable
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function clearConditions(): void
|
||||||
|
{
|
||||||
|
$this->conditions->clear();
|
||||||
|
}
|
||||||
|
|
||||||
public function jsonSerialize(): array
|
public function jsonSerialize(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|
25
module/Core/src/RedirectRule/Model/RedirectRulesData.php
Normal file
25
module/Core/src/RedirectRule/Model/RedirectRulesData.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Core\RedirectRule\Model;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Exception\ValidationException;
|
||||||
|
use Shlinkio\Shlink\Core\RedirectRule\Model\Validation\RedirectRulesInputFilter;
|
||||||
|
|
||||||
|
readonly class RedirectRulesData
|
||||||
|
{
|
||||||
|
private function __construct(public array $rules)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromRawData(array $rawData): self
|
||||||
|
{
|
||||||
|
$inputFilter = RedirectRulesInputFilter::initialize($rawData);
|
||||||
|
if (! $inputFilter->isValid()) {
|
||||||
|
throw ValidationException::fromInputFilter($inputFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new self($inputFilter->getValue(RedirectRulesInputFilter::REDIRECT_RULES));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Core\RedirectRule\Model\Validation;
|
||||||
|
|
||||||
|
use Laminas\InputFilter\CollectionInputFilter;
|
||||||
|
use Laminas\InputFilter\InputFilter;
|
||||||
|
use Laminas\Validator\Callback;
|
||||||
|
use Laminas\Validator\InArray;
|
||||||
|
use Shlinkio\Shlink\Common\Validation\InputFactory;
|
||||||
|
use Shlinkio\Shlink\Core\Model\DeviceType;
|
||||||
|
use Shlinkio\Shlink\Core\RedirectRule\Model\RedirectConditionType;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter;
|
||||||
|
|
||||||
|
use function Shlinkio\Shlink\Core\ArrayUtils\contains;
|
||||||
|
use function Shlinkio\Shlink\Core\enumValues;
|
||||||
|
|
||||||
|
class RedirectRulesInputFilter extends InputFilter
|
||||||
|
{
|
||||||
|
public const REDIRECT_RULES = 'redirectRules';
|
||||||
|
|
||||||
|
public const RULE_PRIORITY = 'priority';
|
||||||
|
public const RULE_LONG_URL = 'longUrl';
|
||||||
|
public const RULE_CONDITIONS = 'conditions';
|
||||||
|
|
||||||
|
public const CONDITION_TYPE = 'type';
|
||||||
|
public const CONDITION_MATCH_VALUE = 'matchValue';
|
||||||
|
public const CONDITION_MATCH_KEY = 'matchKey';
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function initialize(array $rawData): self
|
||||||
|
{
|
||||||
|
$redirectRulesInputFilter = new CollectionInputFilter();
|
||||||
|
$redirectRulesInputFilter->setInputFilter(self::createRedirectRuleInputFilter());
|
||||||
|
|
||||||
|
$instance = new self();
|
||||||
|
$instance->add($redirectRulesInputFilter, self::REDIRECT_RULES);
|
||||||
|
|
||||||
|
$instance->setData($rawData);
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function createRedirectRuleInputFilter(): InputFilter
|
||||||
|
{
|
||||||
|
$redirectRuleInputFilter = new InputFilter();
|
||||||
|
|
||||||
|
$redirectRuleInputFilter->add(InputFactory::numeric(self::RULE_PRIORITY, required: true));
|
||||||
|
|
||||||
|
$longUrl = InputFactory::basic(self::RULE_LONG_URL, required: true);
|
||||||
|
$longUrl->setValidatorChain(ShortUrlInputFilter::longUrlValidators());
|
||||||
|
$redirectRuleInputFilter->add($longUrl);
|
||||||
|
|
||||||
|
$conditionsInputFilter = new CollectionInputFilter();
|
||||||
|
$conditionsInputFilter->setInputFilter(self::createRedirectConditionInputFilter())
|
||||||
|
->setIsRequired(true);
|
||||||
|
$redirectRuleInputFilter->add($conditionsInputFilter, self::RULE_CONDITIONS);
|
||||||
|
|
||||||
|
return $redirectRuleInputFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function createRedirectConditionInputFilter(): InputFilter
|
||||||
|
{
|
||||||
|
$redirectConditionInputFilter = new InputFilter();
|
||||||
|
|
||||||
|
$type = InputFactory::basic(self::CONDITION_TYPE, required: true);
|
||||||
|
$type->getValidatorChain()->attach(new InArray([
|
||||||
|
'haystack' => enumValues(RedirectConditionType::class),
|
||||||
|
'strict' => InArray::COMPARE_STRICT,
|
||||||
|
]));
|
||||||
|
$redirectConditionInputFilter->add($type);
|
||||||
|
|
||||||
|
$value = InputFactory::basic(self::CONDITION_MATCH_VALUE, required: true);
|
||||||
|
$value->getValidatorChain()->attach(new Callback(function (string $value, array $context) {
|
||||||
|
if ($context[self::CONDITION_TYPE] === RedirectConditionType::DEVICE->value) {
|
||||||
|
return contains($value, enumValues(DeviceType::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
$redirectConditionInputFilter->add($value);
|
||||||
|
|
||||||
|
$redirectConditionInputFilter->add(
|
||||||
|
InputFactory::basic(self::CONDITION_MATCH_KEY, required: true)->setAllowEmpty(true),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $redirectConditionInputFilter;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,10 +2,18 @@
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Core\RedirectRule;
|
namespace Shlinkio\Shlink\Core\RedirectRule;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Shlinkio\Shlink\Core\Model\DeviceType;
|
||||||
|
use Shlinkio\Shlink\Core\RedirectRule\Entity\RedirectCondition;
|
||||||
use Shlinkio\Shlink\Core\RedirectRule\Entity\ShortUrlRedirectRule;
|
use Shlinkio\Shlink\Core\RedirectRule\Entity\ShortUrlRedirectRule;
|
||||||
|
use Shlinkio\Shlink\Core\RedirectRule\Model\RedirectConditionType;
|
||||||
|
use Shlinkio\Shlink\Core\RedirectRule\Model\RedirectRulesData;
|
||||||
|
use Shlinkio\Shlink\Core\RedirectRule\Model\Validation\RedirectRulesInputFilter;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||||
|
|
||||||
|
use function array_map;
|
||||||
|
|
||||||
readonly class ShortUrlRedirectRuleService implements ShortUrlRedirectRuleServiceInterface
|
readonly class ShortUrlRedirectRuleService implements ShortUrlRedirectRuleServiceInterface
|
||||||
{
|
{
|
||||||
public function __construct(private EntityManagerInterface $em)
|
public function __construct(private EntityManagerInterface $em)
|
||||||
|
@ -22,4 +30,52 @@ readonly class ShortUrlRedirectRuleService implements ShortUrlRedirectRuleServic
|
||||||
orderBy: ['priority' => 'ASC'],
|
orderBy: ['priority' => 'ASC'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ShortUrlRedirectRule[]
|
||||||
|
*/
|
||||||
|
public function setRulesForShortUrl(ShortUrl $shortUrl, RedirectRulesData $data): array
|
||||||
|
{
|
||||||
|
return $this->em->wrapInTransaction(function () use ($shortUrl, $data): array {
|
||||||
|
// First, delete existing rules for the short URL
|
||||||
|
$oldRules = $this->rulesForShortUrl($shortUrl);
|
||||||
|
foreach ($oldRules as $oldRule) {
|
||||||
|
$oldRule->clearConditions(); // This will trigger the orphan removal of old conditions
|
||||||
|
$this->em->remove($oldRule);
|
||||||
|
}
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
// Then insert new rules
|
||||||
|
$rules = [];
|
||||||
|
foreach ($data->rules as $rule) {
|
||||||
|
$rule = new ShortUrlRedirectRule(
|
||||||
|
shortUrl: $shortUrl,
|
||||||
|
priority: $rule[RedirectRulesInputFilter::RULE_PRIORITY],
|
||||||
|
longUrl: $rule[RedirectRulesInputFilter::RULE_LONG_URL],
|
||||||
|
conditions: new ArrayCollection(array_map(
|
||||||
|
fn (array $conditionData) => $this->createCondition($conditionData),
|
||||||
|
$rule[RedirectRulesInputFilter::RULE_CONDITIONS],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
$rules[] = $rule;
|
||||||
|
$this->em->persist($rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rules;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createCondition(array $rawConditionData): RedirectCondition
|
||||||
|
{
|
||||||
|
$type = RedirectConditionType::from($rawConditionData[RedirectRulesInputFilter::CONDITION_TYPE]);
|
||||||
|
$value = $rawConditionData[RedirectRulesInputFilter::CONDITION_MATCH_VALUE];
|
||||||
|
$key = $rawConditionData[RedirectRulesInputFilter::CONDITION_MATCH_KEY];
|
||||||
|
|
||||||
|
return match ($type) {
|
||||||
|
RedirectConditionType::DEVICE => RedirectCondition::forDevice(DeviceType::from($value)),
|
||||||
|
RedirectConditionType::LANGUAGE => RedirectCondition::forLanguage($value),
|
||||||
|
RedirectConditionType::QUERY_PARAM => RedirectCondition::forQueryParam($key, $value),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Shlinkio\Shlink\Core\RedirectRule;
|
namespace Shlinkio\Shlink\Core\RedirectRule;
|
||||||
|
|
||||||
use Shlinkio\Shlink\Core\RedirectRule\Entity\ShortUrlRedirectRule;
|
use Shlinkio\Shlink\Core\RedirectRule\Entity\ShortUrlRedirectRule;
|
||||||
|
use Shlinkio\Shlink\Core\RedirectRule\Model\RedirectRulesData;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||||
|
|
||||||
interface ShortUrlRedirectRuleServiceInterface
|
interface ShortUrlRedirectRuleServiceInterface
|
||||||
|
@ -11,4 +12,9 @@ interface ShortUrlRedirectRuleServiceInterface
|
||||||
* @return ShortUrlRedirectRule[]
|
* @return ShortUrlRedirectRule[]
|
||||||
*/
|
*/
|
||||||
public function rulesForShortUrl(ShortUrl $shortUrl): array;
|
public function rulesForShortUrl(ShortUrl $shortUrl): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ShortUrlRedirectRule[]
|
||||||
|
*/
|
||||||
|
public function setRulesForShortUrl(ShortUrl $shortUrl, RedirectRulesData $data): array;
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ class ShortUrlInputFilter extends InputFilter
|
||||||
private function initializeForEdition(bool $requireLongUrl = false): void
|
private function initializeForEdition(bool $requireLongUrl = false): void
|
||||||
{
|
{
|
||||||
$longUrlInput = InputFactory::basic(self::LONG_URL, required: $requireLongUrl);
|
$longUrlInput = InputFactory::basic(self::LONG_URL, required: $requireLongUrl);
|
||||||
$longUrlInput->getValidatorChain()->merge($this->longUrlValidators());
|
$longUrlInput->getValidatorChain()->merge(self::longUrlValidators(allowNull: ! $requireLongUrl));
|
||||||
$this->add($longUrlInput);
|
$this->add($longUrlInput);
|
||||||
|
|
||||||
$validSince = InputFactory::basic(self::VALID_SINCE);
|
$validSince = InputFactory::basic(self::VALID_SINCE);
|
||||||
|
@ -124,7 +124,7 @@ class ShortUrlInputFilter extends InputFilter
|
||||||
$this->add($apiKeyInput);
|
$this->add($apiKeyInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function longUrlValidators(bool $allowNull = false): Validator\ValidatorChain
|
public static function longUrlValidators(bool $allowNull = false): Validator\ValidatorChain
|
||||||
{
|
{
|
||||||
$emptyModifiers = [
|
$emptyModifiers = [
|
||||||
Validator\NotEmpty::OBJECT,
|
Validator\NotEmpty::OBJECT,
|
||||||
|
|
|
@ -48,6 +48,7 @@ return [
|
||||||
Action\Domain\ListDomainsAction::class => ConfigAbstractFactory::class,
|
Action\Domain\ListDomainsAction::class => ConfigAbstractFactory::class,
|
||||||
Action\Domain\DomainRedirectsAction::class => ConfigAbstractFactory::class,
|
Action\Domain\DomainRedirectsAction::class => ConfigAbstractFactory::class,
|
||||||
Action\RedirectRule\ListRedirectRulesAction::class => ConfigAbstractFactory::class,
|
Action\RedirectRule\ListRedirectRulesAction::class => ConfigAbstractFactory::class,
|
||||||
|
Action\RedirectRule\SetRedirectRulesAction::class => ConfigAbstractFactory::class,
|
||||||
|
|
||||||
ImplicitOptionsMiddleware::class => Middleware\EmptyResponseImplicitOptionsMiddlewareFactory::class,
|
ImplicitOptionsMiddleware::class => Middleware\EmptyResponseImplicitOptionsMiddlewareFactory::class,
|
||||||
Middleware\BodyParserMiddleware::class => InvokableFactory::class,
|
Middleware\BodyParserMiddleware::class => InvokableFactory::class,
|
||||||
|
@ -109,6 +110,10 @@ return [
|
||||||
ShortUrl\ShortUrlResolver::class,
|
ShortUrl\ShortUrlResolver::class,
|
||||||
RedirectRule\ShortUrlRedirectRuleService::class,
|
RedirectRule\ShortUrlRedirectRuleService::class,
|
||||||
],
|
],
|
||||||
|
Action\RedirectRule\SetRedirectRulesAction::class => [
|
||||||
|
ShortUrl\ShortUrlResolver::class,
|
||||||
|
RedirectRule\ShortUrlRedirectRuleService::class,
|
||||||
|
],
|
||||||
|
|
||||||
Middleware\CrossDomainMiddleware::class => ['config.cors'],
|
Middleware\CrossDomainMiddleware::class => ['config.cors'],
|
||||||
Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware::class => ['config.url_shortener.domain.hostname'],
|
Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware::class => ['config.url_shortener.domain.hostname'],
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Rest\Action\RedirectRule;
|
||||||
|
|
||||||
|
use Laminas\Diactoros\Response\JsonResponse;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Shlinkio\Shlink\Core\RedirectRule\Model\RedirectRulesData;
|
||||||
|
use Shlinkio\Shlink\Core\RedirectRule\ShortUrlRedirectRuleServiceInterface;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\ShortUrlResolverInterface;
|
||||||
|
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||||
|
use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware;
|
||||||
|
|
||||||
|
class SetRedirectRulesAction extends AbstractRestAction
|
||||||
|
{
|
||||||
|
protected const ROUTE_PATH = '/short-urls/{shortCode}/redirect-rules';
|
||||||
|
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_POST, self::METHOD_PATCH];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly ShortUrlResolverInterface $urlResolver,
|
||||||
|
private readonly ShortUrlRedirectRuleServiceInterface $ruleService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||||
|
{
|
||||||
|
$shortUrl = $this->urlResolver->resolveShortUrl(
|
||||||
|
ShortUrlIdentifier::fromApiRequest($request),
|
||||||
|
AuthenticationMiddleware::apiKeyFromRequest($request),
|
||||||
|
);
|
||||||
|
$data = RedirectRulesData::fromRawData((array) $request->getParsedBody());
|
||||||
|
|
||||||
|
$result = $this->ruleService->setRulesForShortUrl($shortUrl, $data);
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'defaultLongUrl' => $shortUrl->getLongUrl(),
|
||||||
|
'redirectRules' => $result,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue