Merge pull request #2672 from acelaya-forks/mercure-1-support

Add support for Mercure 1.0
This commit is contained in:
Alejandro Celaya authored and GitHub committed 2026-10-13 00:04:11 +02:00
commit be5f5bb7ff
19 files changed
+427 -241

No files matched your search

+17
View File
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).
## [Unreleased]
### Added
* [#2671](https://github.com/shlinkio/shlink/issues/2671) Add support for Mercure 1.0.
### Changed
* *Nothing*
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* *Nothing*
## [5.1.7] - 2026-09-21
### Added
* *Nothing*
+2 -2
View File
@@ -42,11 +42,11 @@
"pagerfanta/core": "^3.8",
"ramsey/uuid": "^4.9",
"shlinkio/doctrine-specification": "^2.3",
"shlinkio/shlink-common": "^9.0.2",
"shlinkio/shlink-common": "dev-main#7e5d108 as 9.1.0",
"shlinkio/shlink-config": "^4.1.0",
"shlinkio/shlink-event-dispatcher": "^4.4.0",
"shlinkio/shlink-importer": "^5.8",
"shlinkio/shlink-installer": "^10.1",
"shlinkio/shlink-installer": "dev-develop#af7d57d as 10.2.0",
"shlinkio/shlink-ip-geolocation": "^5.0.0",
"shlinkio/shlink-json": "^1.3",
"spiral/roadrunner": "^2025.1",
+1
View File
@@ -40,6 +40,7 @@ return [
Option\Mercure\MercurePublicUrlConfigOption::class,
Option\Mercure\MercureInternalUrlConfigOption::class,
Option\Mercure\MercureJwtSecretConfigOption::class,
Option\Mercure\MercureVersionConfigOption::class,
Option\UrlShortener\GeoLiteLicenseKeyConfigOption::class,
Option\UrlShortener\RedirectStatusCodeConfigOption::class,
Option\UrlShortener\RedirectCacheLifeTimeConfigOption::class,
+1
View File
@@ -16,6 +16,7 @@ return [
'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL->loadFromEnv(),
'jwt_secret' => EnvVars::MERCURE_JWT_SECRET->loadFromEnv(),
'jwt_issuer' => 'Shlink',
'version' => EnvVars::MERCURE_VERSION->loadFromEnv(),
],
'dependencies' => [
+1
View File
@@ -64,6 +64,7 @@ return [
EnvVars::MERCURE_PUBLIC_HUB_URL->value => 'http://localhost:8002',
EnvVars::MERCURE_INTERNAL_HUB_URL->value => 'http://shlink_mercure_proxy',
EnvVars::MERCURE_JWT_SECRET->value => 'mercure_jwt_key_long_enough_to_avoid_error',
EnvVars::MERCURE_VERSION->value => 'stable',
// RabbitMQ
EnvVars::RABBITMQ_ENABLED->value => true,
+16 -1
View File
@@ -30,6 +30,7 @@ services:
- shlink_redis
- shlink_redis_acl
- shlink_mercure
- shlink_mercure_0
- shlink_mercure_proxy
- shlink_rabbitmq
- shlink_matomo
@@ -58,6 +59,7 @@ services:
- shlink_redis
- shlink_redis_acl
- shlink_mercure
- shlink_mercure_0
- shlink_mercure_proxy
- shlink_rabbitmq
- shlink_matomo
@@ -87,6 +89,7 @@ services:
- shlink_redis
- shlink_redis_acl
- shlink_mercure
- shlink_mercure_0
- shlink_mercure_proxy
- shlink_rabbitmq
- shlink_matomo
@@ -175,9 +178,21 @@ services:
shlink_mercure:
container_name: shlink_mercure
image: dunglas/mercure:v0.18
image: dunglas/mercure:v1
ports:
- "3080:80"
environment:
SERVER_NAME: ":80"
MERCURE_PUBLISHER_JWT_KEY: mercure_jwt_key_long_enough_to_avoid_error
MERCURE_SUBSCRIBER_JWT_KEY: mercure_jwt_key_long_enough_to_avoid_error
MERCURE_TRUSTED_ISSUERS: Shlink # Value of the JWT iss claim
MERCURE_EXTRA_DIRECTIVES: "cors_origins https://app.shlink.io http://localhost:3000 http://127.0.0.1:3000 http://localhost:3002 http://127.0.0.1:3002 http://localhost:3005 http://127.0.0.1:3005"
shlink_mercure_0:
container_name: shlink_mercure_0
image: dunglas/mercure:v0
ports:
- "3081:80"
environment:
SERVER_NAME: ":80"
MERCURE_PUBLISHER_JWT_KEY: mercure_jwt_key_long_enough_to_avoid_error
@@ -13,6 +13,11 @@
"jwtExpiration": {
"type": "string",
"description": "The date (in ISO-8601 format) in which the JWT will expire"
},
"version": {
"type": "string",
"enum": ["legacy", "stable"],
"description": "Indicates the version of the mercure hub that has been configured, so that consumers know how to subscribe"
}
}
}
+2 -1
View File
@@ -27,7 +27,8 @@
"example": {
"mercureHubUrl": "https://example.com/.well-known/mercure",
"jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTaGxpbmsiLCJpYXQiOjE1ODY2ODY3MzIsImV4cCI6MTU4Njk0NTkzMiwibWVyY3VyZSI6eyJzdWJzY3JpYmUiOltdfX0.P-519lgU7dFz0bbNlRG1CXyqugGbaHon4kw6fu4QBdQ",
"jwtExpiration": "2020-04-15T12:18:52+02:00"
"jwtExpiration": "2020-04-15T12:18:52+02:00",
"version": "stable"
}
}
}
+230 -80
View File
@@ -522,18 +522,6 @@ code = "mixed-operand"
message = "Cannot reliably increment a `mixed` operand."
count = 2
[[issues]]
file = "module/CLI/test/RedirectRule/RedirectRuleHandlerTest.php"
code = "redundant-comparison"
message = "Redundant `<` comparison: left-hand side is always less than right-hand side."
count = 1
[[issues]]
file = "module/CLI/test/RedirectRule/RedirectRuleHandlerTest.php"
code = "redundant-logical-operation"
message = "Redundant `&&` operation: left operand is always truthy and right operand is evaluated."
count = 1
[[issues]]
file = "module/CLI/test/Util/CliTestUtils.php"
code = "invalid-return-statement"
@@ -684,6 +672,66 @@ code = "possibly-static-access-on-interface"
message = "Potential static method call on interface `BackedEnum` via `class-string`."
count = 1
[[issues]]
file = "module/Core/migrations/Version20230211171904.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addIndex`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20230303164233.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addIndex`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20230303164233.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::dropIndex`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240220214031.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Column::getType`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240220214031.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Column::setType`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240220214031.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::modifyColumn`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240224115725.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Schema::createTable`.'
count = 2
[[issues]]
file = "module/Core/migrations/Version20240224115725.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Schema::dropTable`.'
count = 3
[[issues]]
file = "module/Core/migrations/Version20240224115725.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addColumn`.'
count = 9
[[issues]]
file = "module/Core/migrations/Version20240224115725.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addForeignKeyConstraint`.'
count = 3
[[issues]]
file = "module/Core/migrations/Version20240224115725.php"
code = "deprecated-method"
@@ -696,12 +744,72 @@ code = "mixed-assignment"
message = "Assigning `mixed` type to a variable may lead to unexpected behavior."
count = 2
[[issues]]
file = "module/Core/migrations/Version20240227080629.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Schema::createTable`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240227080629.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Schema::dropTable`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240227080629.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addColumn`.'
count = 4
[[issues]]
file = "module/Core/migrations/Version20240227080629.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addForeignKeyConstraint`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240227080629.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addUniqueIndex`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240227080629.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::setPrimaryKey`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240306132518.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Schema::createTable`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240306132518.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Schema::dropTable`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240306132518.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addColumn`.'
count = 5
[[issues]]
file = "module/Core/migrations/Version20240306132518.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addForeignKeyConstraint`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240306132518.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addUniqueIndex`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240306132518.php"
code = "deprecated-method"
@@ -732,6 +840,30 @@ code = "mixed-operand"
message = "Left operand in `>` comparison has `mixed` type."
count = 2
[[issues]]
file = "module/Core/migrations/Version20240331111103.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Schema::createTable`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240331111103.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Schema::dropTable`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240331111103.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addColumn`.'
count = 4
[[issues]]
file = "module/Core/migrations/Version20240331111103.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addUniqueIndex`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20240331111103.php"
code = "deprecated-method"
@@ -780,12 +912,96 @@ code = "mixed-assignment"
message = "Assigning `mixed` type to a variable may lead to unexpected behavior."
count = 1
[[issues]]
file = "module/Core/migrations/Version20241124112257.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addColumn`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20241124112257.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::dropColumn`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20241125213106.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addUniqueIndex`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20241125213106.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::dropIndex`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20241212131058.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Schema::createTable`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20241212131058.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Schema::dropTable`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20241212131058.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addColumn`.'
count = 7
[[issues]]
file = "module/Core/migrations/Version20241212131058.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addIndex`.'
count = 2
[[issues]]
file = "module/Core/migrations/Version20241212131058.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::setPrimaryKey`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20250215100756.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::dropIndex`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20250722060208.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Column::setNotnull`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20260524105410.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addColumn`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20260524105410.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::addIndex`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20260524105410.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::dropColumn`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20260524105410.php"
code = "deprecated-method"
message = 'Call to deprecated method: `Doctrine\DBAL\Schema\Table::dropIndex`.'
count = 1
[[issues]]
file = "module/Core/migrations/Version20260607082210.php"
code = "mixed-argument"
@@ -1065,7 +1281,7 @@ count = 2
[[issues]]
file = "module/Core/src/Domain/Repository/DomainRepository.php"
code = "mixed-argument"
message = 'Invalid argument type for argument #2 of `Happyr\DoctrineSpecification\Repository\EntitySpecificationRepositoryTrait::applySpecification`: expected `Happyr\DoctrineSpecification\Filter\Filter|Happyr\DoctrineSpecification\Query\QueryModifier`, but found `mixed`.'
message = 'Invalid argument type for argument #2 of `Happyr\DoctrineSpecification\Repository\EntitySpecificationRepositoryTrait::applySpecification`: expected `Happyr\DoctrineSpecification\Filter\Filter|Happyr\DoctrineSpecification\Query\QueryModifier|null`, but found `mixed`.'
count = 2
[[issues]]
@@ -1386,12 +1602,6 @@ code = "mixed-assignment"
message = "Assigning `mixed` type to a variable may lead to unexpected behavior."
count = 1
[[issues]]
file = "module/Core/src/ShortUrl/Repository/ShortUrlListRepository.php"
code = "possibly-null-argument"
message = 'Argument #2 of method `Happyr\DoctrineSpecification\Repository\EntitySpecificationRepositoryTrait::applySpecification` is possibly `null`, but parameter type `Happyr\DoctrineSpecification\Filter\Filter|Happyr\DoctrineSpecification\Query\QueryModifier` does not accept it.'
count = 1
[[issues]]
file = "module/Core/src/ShortUrl/Repository/ShortUrlRepository.php"
code = "mixed-assignment"
@@ -1422,12 +1632,6 @@ code = "mixed-return-statement"
message = 'Could not infer a precise return type for function `Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepository::findOne`. Saw type `mixed`.'
count = 1
[[issues]]
file = "module/Core/src/ShortUrl/Repository/ShortUrlRepository.php"
code = "possibly-null-argument"
message = 'Argument #2 of method `Happyr\DoctrineSpecification\Repository\EntitySpecificationRepositoryTrait::applySpecification` is possibly `null`, but parameter type `Happyr\DoctrineSpecification\Filter\Filter|Happyr\DoctrineSpecification\Query\QueryModifier` does not accept it.'
count = 1
[[issues]]
file = "module/Core/src/ShortUrl/Resolver/PersistenceShortUrlRelationResolver.php"
code = "less-specific-return-statement"
@@ -1524,12 +1728,6 @@ code = "mixed-return-statement"
message = 'Could not infer a precise return type for function `Shlinkio\Shlink\Core\Tag\Paginator\Adapter\TagsPaginatorAdapter::getSlice`. Saw type `mixed`.'
count = 1
[[issues]]
file = "module/Core/src/Tag/Repository/TagRepository.php"
code = "mixed-argument"
message = 'Invalid argument type for argument #1 of `Doctrine\DBAL\Connection::quote`: expected `string`, but found `mixed`.'
count = 1
[[issues]]
file = "module/Core/src/Tag/Repository/TagRepository.php"
code = "mixed-argument"
@@ -1638,12 +1836,6 @@ code = "mixed-argument"
message = 'Invalid argument type for argument #5 of `Shlinkio\Shlink\Core\Visit\Model\WithDomainVisitsParams::__construct`: expected `null|string`, but found `mixed`.'
count = 1
[[issues]]
file = "module/Core/src/Visit/Repository/ShortUrlVisitsCountRepository.php"
code = "possibly-null-argument"
message = 'Argument #2 of method `Happyr\DoctrineSpecification\Repository\EntitySpecificationRepositoryTrait::applySpecification` is possibly `null`, but parameter type `Happyr\DoctrineSpecification\Filter\Filter|Happyr\DoctrineSpecification\Query\QueryModifier` does not accept it.'
count = 1
[[issues]]
file = "module/Core/src/Visit/Repository/VisitDeleterRepository.php"
code = "mixed-return-statement"
@@ -1746,12 +1938,6 @@ code = "mixed-return-statement"
message = 'Could not infer a precise return type for function `Shlinkio\Shlink\Core\Visit\Repository\VisitRepository::resolveVisitsWithNativeQuery`. Saw type `mixed`.'
count = 1
[[issues]]
file = "module/Core/src/Visit/Repository/VisitRepository.php"
code = "possibly-null-argument"
message = 'Argument #2 of method `Happyr\DoctrineSpecification\Repository\EntitySpecificationRepositoryTrait::applySpecification` is possibly `null`, but parameter type `Happyr\DoctrineSpecification\Filter\Filter|Happyr\DoctrineSpecification\Query\QueryModifier` does not accept it.'
count = 3
[[issues]]
file = "module/Core/src/Visit/RequestTracker.php"
code = "mixed-assignment"
@@ -2028,12 +2214,6 @@ code = "less-specific-argument"
message = 'Argument type mismatch for argument #1 of `ShlinkioTest\Shlink\Core\RedirectRule\Entity\ShortUrlRedirectRuleTest::createRule`: expected `Doctrine\Common\Collections\ArrayCollection<int, Shlinkio\Shlink\Core\RedirectRule\Entity\RedirectCondition>`, but provided type `Doctrine\Common\Collections\ArrayCollection<array-key, mixed>` is less specific.'
count = 1
[[issues]]
file = "module/Core/test/ShortUrl/Helper/ShortCodeUniquenessHelperTest.php"
code = "redundant-comparison"
message = "Redundant `<` comparison: left-hand side is always less than right-hand side."
count = 1
[[issues]]
file = "module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php"
code = "less-specific-nested-argument-type"
@@ -2106,30 +2286,6 @@ code = "string-member-selector"
message = "This member selector uses a non-literal string type (`string`); its specific value cannot be statically determined."
count = 3
[[issues]]
file = "module/Rest/src/Action/MercureInfoAction.php"
code = "mixed-argument"
message = 'Invalid argument type for argument #1 of `Cake\Chronos\Chronos::addDays`: expected `int`, but found `nonnull`.'
count = 1
[[issues]]
file = "module/Rest/src/Action/MercureInfoAction.php"
code = "mixed-argument"
message = "Invalid argument type for argument #2 of `sprintf`: expected `Stringable|null|scalar`, but found `nonnull`."
count = 1
[[issues]]
file = "module/Rest/src/Action/MercureInfoAction.php"
code = "mixed-assignment"
message = "Assigning `mixed` type to a variable may lead to unexpected behavior."
count = 1
[[issues]]
file = "module/Rest/src/Action/MercureInfoAction.php"
code = "mixed-assignment"
message = "Assigning `nonnull` type to a variable may lead to unexpected behavior."
count = 1
[[issues]]
file = "module/Rest/src/Action/RedirectRule/SetRedirectRulesAction.php"
code = "invalid-type-cast"
@@ -2530,7 +2686,7 @@ count = 3
file = "module/Rest/test/Action/MercureInfoActionTest.php"
code = "mixed-array-access"
message = "Unsafe array access on type `mixed`."
count = 2
count = 3
[[issues]]
file = "module/Rest/test/Action/MercureInfoActionTest.php"
@@ -2669,9 +2825,3 @@ file = "module/Rest/test/Middleware/ShortUrl/ShortUrlOptionsPayloadMiddlewareTes
code = "invalid-type-cast"
message = "Object of type `object` cast to `array`. Property visibility (public, protected, private) affects the resulting array."
count = 1
[[issues]]
file = "module/Rest/test/Service/ApiKeyServiceTest.php"
code = "redundant-comparison"
message = "Redundant `<` comparison: left-hand side is always less than right-hand side."
count = 1
+16 -16
View File
@@ -43,23 +43,23 @@ class ListApiKeysTest extends CliTestCase
OUT;
yield 'no flags' => [[], <<<OUT
+--------------------+------------+---------------------------+--------------------------+
| Name | Is enabled | Expiration date | Roles |
+--------------------+------------+---------------------------+--------------------------+
| valid_api_key | +++ | - | Admin |
+--------------------+------------+---------------------------+--------------------------+
| disabled_api_key | --- | - | Admin |
+--------------------+------------+---------------------------+--------------------------+
| expired_api_key | --- | {$expiredApiKeyDate} | Admin |
+--------------------+------------+---------------------------+--------------------------+
| author_api_key | +++ | - | Author only |
+--------------------+------------+---------------------------+--------------------------+
| domain_api_key | +++ | - | Domain only: example.com |
+--------------------+------------+---------------------------+--------------------------+
| no_orphans_api_key | +++ | - | No orphan visits |
+--------------------+------------+---------------------------+--------------------------+
+--------------------+------------+---------------------------+--------------------------+
| Name | Is enabled | Expiration date | Roles |
+--------------------+------------+---------------------------+--------------------------+
| valid_api_key | +++ | - | Admin |
+--------------------+------------+---------------------------+--------------------------+
| disabled_api_key | --- | - | Admin |
+--------------------+------------+---------------------------+--------------------------+
| expired_api_key | --- | {$expiredApiKeyDate} | Admin |
+--------------------+------------+---------------------------+--------------------------+
| author_api_key | +++ | - | Author only |
+--------------------+------------+---------------------------+--------------------------+
| domain_api_key | +++ | - | Domain only: example.com |
+--------------------+------------+---------------------------+--------------------------+
| no_orphans_api_key | +++ | - | No orphan visits |
+--------------------+------------+---------------------------+--------------------------+
OUT];
OUT];
yield '-e' => [['-e'], $enabledOnlyOutput];
yield '--enabled-only' => [['--enabled-only'], $enabledOnlyOutput];
}
@@ -21,79 +21,79 @@ class ListShortUrlsTest extends CliTestCase
public static function provideFlagsAndOutput(): iterable
{
yield 'no flags' => [[], <<<OUTPUT
+--------------------+---------------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+--------------------+---------------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
| abc123 | My cool title | http://s.test/abc123 | https://shlink.io | 2018-05-01T00:00:00+00:00 | 3 |
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
+--------------------+---------------+-------------------------------------------+---------------------------- Page 1 of 1 ------------------------------------------------------------------+---------------------------+--------------+
OUTPUT];
+--------------------+---------------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+--------------------+---------------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
| abc123 | My cool title | http://s.test/abc123 | https://shlink.io | 2018-05-01T00:00:00+00:00 | 3 |
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
+--------------------+---------------+-------------------------------------------+---------------------------- Page 1 of 1 ------------------------------------------------------------------+---------------------------+--------------+
OUTPUT];
yield 'start date' => [['--start-date=2019-01'], <<<OUTPUT
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
+------------+-------+---------------------------+-------------------------------------------- Page 1 of 1 --------------------------------------------------+---------------------------+--------------+
OUTPUT];
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
+------------+-------+---------------------------+-------------------------------------------- Page 1 of 1 --------------------------------------------------+---------------------------+--------------+
OUTPUT];
yield 'end date' => [['-e 2018-12-01'], <<<OUTPUT
+--------------------+---------------+-------------------------------------------+----------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+--------------------+---------------+-------------------------------------------+----------------------------------+---------------------------+--------------+
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
| abc123 | My cool title | http://s.test/abc123 | https://shlink.io | 2018-05-01T00:00:00+00:00 | 3 |
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
+--------------------+---------------+----------------------------------- Page 1 of 1 ------------------------------+---------------------------+--------------+
OUTPUT];
+--------------------+---------------+-------------------------------------------+----------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+--------------------+---------------+-------------------------------------------+----------------------------------+---------------------------+--------------+
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
| abc123 | My cool title | http://s.test/abc123 | https://shlink.io | 2018-05-01T00:00:00+00:00 | 3 |
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
+--------------------+---------------+----------------------------------- Page 1 of 1 ------------------------------+---------------------------+--------------+
OUTPUT];
yield 'start and end date' => [['-s 2018-06-20', '--end-date=2019-01-01T00:00:20+00:00'], <<<OUTPUT
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
+--------------------+-------+-------------------------------------------+----------------------------- Page 1 of 1 -----------------------------------------------------------+---------------------------+--------------+
OUTPUT];
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
+--------------------+-------+-------------------------------------------+----------------------------- Page 1 of 1 -----------------------------------------------------------+---------------------------+--------------+
OUTPUT];
yield 'expired excluded' => [['--exclude-max-visits-reached', '--exclude-past-valid-until'], <<<OUTPUT
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
+--------------------+-------+-------------------------------------------+-------------------------------- Page 1 of 1 --------------------------------------------------------------+---------------------------+--------------+
OUTPUT];
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+--------------------+-------+-------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
+--------------------+-------+-------------------------------------------+-------------------------------- Page 1 of 1 --------------------------------------------------------------+---------------------------+--------------+
OUTPUT];
yield 'non-default domain' => [['--domain=example.com'], <<<OUTPUT
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
+------------+-------+---------------------------+-------------------------------------------- Page 1 of 1 --------------------------------------------------+---------------------------+--------------+
OUTPUT];
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+------------+-------+---------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------+--------------+
| ghi789 | | http://example.com/ghi789 | https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/ | 2019-01-01T00:00:30+00:00 | 0 |
+------------+-------+---------------------------+-------------------------------------------- Page 1 of 1 --------------------------------------------------+---------------------------+--------------+
OUTPUT];
yield 'default domain' => [['--domain=DEFAULT'], <<<OUTPUT
+------------+---------------+----------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+------------+---------------+----------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
| abc123 | My cool title | http://s.test/abc123 | https://shlink.io | 2018-05-01T00:00:00+00:00 | 3 |
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
+------------+---------------+----------------------+--------------------------------------- Page 1 of 1 -------------------------------------------------+---------------------------+--------------+
OUTPUT];
+------------+---------------+----------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+------------+---------------+----------------------+-----------------------------------------------------------------------------------------------------+---------------------------+--------------+
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
| def456 | | http://s.test/def456 | https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/ | 2019-01-01T00:00:10+00:00 | 2 |
| abc123 | My cool title | http://s.test/abc123 | https://shlink.io | 2018-05-01T00:00:00+00:00 | 3 |
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
+------------+---------------+----------------------+--------------------------------------- Page 1 of 1 -------------------------------------------------+---------------------------+--------------+
OUTPUT];
yield 'exclude tags' => [['--exclude-tag=foo'], <<<OUTPUT
+--------------------+-------+-------------------------------------------+----------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+--------------------+-------+-------------------------------------------+----------------------------------+---------------------------+--------------+
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
+--------------------+-------+--------------------------------------- Page 1 of 1 --------------------------+---------------------------+--------------+
OUTPUT];
+--------------------+-------+-------------------------------------------+----------------------------------+---------------------------+--------------+
| Short Code | Title | Short URL | Long URL | Date created | Visits count |
+--------------------+-------+-------------------------------------------+----------------------------------+---------------------------+--------------+
| custom | | http://s.test/custom | https://shlink.io | 2019-01-01T00:00:20+00:00 | 0 |
| custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 |
| ghi789 | | http://s.test/ghi789 | https://shlink.io/documentation/ | 2018-05-01T00:00:00+00:00 | 2 |
+--------------------+-------+--------------------------------------- Page 1 of 1 --------------------------+---------------------------+--------------+
OUTPUT];
}
}
@@ -49,8 +49,8 @@ class InitialApiKeyCommandTest extends TestCase
yield 'api key created, verbose' => [ApiKey::create(), true, ''];
yield 'no api key created, no verbose' => [null, false, ''];
yield 'no api key created, verbose' => [null, true, <<<OUT
Other API keys already exist. Initial API key creation skipped.
Other API keys already exist. Initial API key creation skipped.
OUT];
OUT];
}
}
+3
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Config;
use Shlinkio\Shlink\Common\Mercure\MercureVersion;
use Shlinkio\Shlink\Core\Config\Options\ExtraPathMode;
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode;
@@ -41,6 +42,7 @@ enum EnvVars: string
case MERCURE_PUBLIC_HUB_URL = 'MERCURE_PUBLIC_HUB_URL';
case MERCURE_INTERNAL_HUB_URL = 'MERCURE_INTERNAL_HUB_URL';
case MERCURE_JWT_SECRET = 'MERCURE_JWT_SECRET';
case MERCURE_VERSION = 'MERCURE_VERSION';
case RABBITMQ_ENABLED = 'RABBITMQ_ENABLED';
case RABBITMQ_HOST = 'RABBITMQ_HOST';
case RABBITMQ_PORT = 'RABBITMQ_PORT';
@@ -134,6 +136,7 @@ enum EnvVars: string
},
self::DB_USE_ENCRYPTION => false,
self::MERCURE_ENABLED => self::MERCURE_PUBLIC_HUB_URL->existsInEnv(),
self::MERCURE_VERSION => MercureVersion::LEGACY->value,
self::MERCURE_INTERNAL_HUB_URL => self::MERCURE_PUBLIC_HUB_URL->loadFromEnv(),
self::RABBITMQ_ENABLED, self::RABBITMQ_USE_SSL => false,
self::RABBITMQ_PORT => 5672,
+4 -2
View File
@@ -167,8 +167,10 @@ class ShortUrl extends AbstractEntity
if (
$this->title === null
|| $shortUrlEdit->titleWasProvided
|| ($this->titleWasAutoResolved
&& $shortUrlEdit->titleWasAutoResolved)
|| (
$this->titleWasAutoResolved
&& $shortUrlEdit->titleWasAutoResolved
)
) {
$this->title = $shortUrlEdit->title;
$this->titleWasAutoResolved = $shortUrlEdit->titleWasAutoResolved;
+34 -34
View File
@@ -43,34 +43,34 @@ class RobotsActionTest extends TestCase
public static function provideShortCodes(): iterable
{
yield 'three short codes' => [['foo', 'bar', 'baz'], new RobotsOptions(), <<<ROBOTS
# For more information about the robots.txt standard, see:
# https://www.robotstxt.org/orig.html
# For more information about the robots.txt standard, see:
# https://www.robotstxt.org/orig.html
User-agent: *
Allow: /foo
Allow: /bar
Allow: /baz
Disallow: /
ROBOTS];
User-agent: *
Allow: /foo
Allow: /bar
Allow: /baz
Disallow: /
ROBOTS];
yield 'five short codes' => [['foo', 'bar', 'some', 'thing', 'baz'], new RobotsOptions(), <<<ROBOTS
# For more information about the robots.txt standard, see:
# https://www.robotstxt.org/orig.html
# For more information about the robots.txt standard, see:
# https://www.robotstxt.org/orig.html
User-agent: *
Allow: /foo
Allow: /bar
Allow: /some
Allow: /thing
Allow: /baz
Disallow: /
ROBOTS];
User-agent: *
Allow: /foo
Allow: /bar
Allow: /some
Allow: /thing
Allow: /baz
Disallow: /
ROBOTS];
yield 'no short codes' => [[], new RobotsOptions(), <<<ROBOTS
# For more information about the robots.txt standard, see:
# https://www.robotstxt.org/orig.html
# For more information about the robots.txt standard, see:
# https://www.robotstxt.org/orig.html
User-agent: *
Disallow: /
ROBOTS];
User-agent: *
Disallow: /
ROBOTS];
yield 'three short codes and allow all short urls' => [
['foo', 'bar', 'some'],
new RobotsOptions(allowAllShortUrls: true),
@@ -83,20 +83,20 @@ class RobotsActionTest extends TestCase
ROBOTS,
];
yield 'no short codes and allow all short urls' => [[], new RobotsOptions(allowAllShortUrls: true), <<<ROBOTS
# For more information about the robots.txt standard, see:
# https://www.robotstxt.org/orig.html
# For more information about the robots.txt standard, see:
# https://www.robotstxt.org/orig.html
User-agent: *
Disallow: /rest/
ROBOTS];
User-agent: *
Disallow: /rest/
ROBOTS];
yield 'allow user agents' => [[], new RobotsOptions(userAgents: ['foo', 'bar']), <<<ROBOTS
# For more information about the robots.txt standard, see:
# https://www.robotstxt.org/orig.html
# For more information about the robots.txt standard, see:
# https://www.robotstxt.org/orig.html
User-agent: foo
User-agent: bar
Disallow: /
ROBOTS];
User-agent: foo
User-agent: bar
Disallow: /
ROBOTS];
}
private function action(RobotsOptions $options): RobotsAction
@@ -42,7 +42,7 @@ class MapperBuilderWithCacheDelegatorFactoryTest extends TestCase
{
putenv(EnvVars::APP_ENV->value . '=' . $env);
$builder = (new MapperBuilderWithCacheDelegatorFactory())($this->container, '', fn () => $this->baseBuilder);
$builder = new MapperBuilderWithCacheDelegatorFactory()($this->container, '', fn () => $this->baseBuilder);
$reflection = new ReflectionObject($builder);
/** @var Settings $settings */
$settings = $reflection->getProperty('settings')->getValue($builder);
@@ -56,7 +56,7 @@ class MapperBuilderWithCacheDelegatorFactoryTest extends TestCase
{
putenv(EnvVars::APP_ENV->value . '=prod');
$builder = (new MapperBuilderWithCacheDelegatorFactory())($this->container, '', fn () => $this->baseBuilder);
$builder = new MapperBuilderWithCacheDelegatorFactory()($this->container, '', fn () => $this->baseBuilder);
$reflection = new ReflectionObject($builder);
/** @var Settings $settings */
$settings = $reflection->getProperty('settings')->getValue($builder);
+2 -1
View File
@@ -12,6 +12,7 @@ use Mezzio\Router\Middleware\ImplicitOptionsMiddleware;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Common\Doctrine\EntityRepositoryFactory;
use Shlinkio\Shlink\Common\Mercure\LcobucciJwtProvider;
use Shlinkio\Shlink\Common\Mercure\MercureOptions;
use Shlinkio\Shlink\Core\Config;
use Shlinkio\Shlink\Core\Domain\DomainService;
use Shlinkio\Shlink\Core\RedirectRule;
@@ -68,7 +69,7 @@ return [
ApiKeyService::class => ['em', ApiKeyRepository::class],
Action\HealthAction::class => ['em', Config\Options\AppOptions::class],
Action\MercureInfoAction::class => [LcobucciJwtProvider::class, 'config.mercure'],
Action\MercureInfoAction::class => [LcobucciJwtProvider::class, MercureOptions::class],
Action\ShortUrl\CreateShortUrlAction::class => [
ShortUrl\UrlShortener::class,
ShortUrlDataTransformer::class,
+6 -4
View File
@@ -9,6 +9,7 @@ use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Common\Mercure\JwtProviderInterface;
use Shlinkio\Shlink\Common\Mercure\MercureOptions;
use Shlinkio\Shlink\Rest\Exception\MercureException;
use function sprintf;
@@ -20,24 +21,25 @@ class MercureInfoAction extends AbstractRestAction
public function __construct(
private readonly JwtProviderInterface $jwtProvider,
private readonly array $mercureConfig,
private readonly MercureOptions $mercureOptions,
) {}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$hubUrl = $this->mercureConfig['public_hub_url'] ?? null;
$hubUrl = $this->mercureOptions->publicHubUrl;
if ($hubUrl === null) {
throw MercureException::mercureNotConfigured();
}
$days = $this->mercureConfig['jwt_days_duration'] ?? 1;
$expiresAt = Chronos::now()->addDays($days);
// Make subscription tokens expire in 1 day
$expiresAt = Chronos::now()->addDays(1);
$jwt = $this->jwtProvider->buildSubscriptionToken($expiresAt);
return new JsonResponse([
'mercureHubUrl' => sprintf('%s/.well-known/mercure', $hubUrl),
'token' => $jwt,
'jwtExpiration' => $expiresAt->toAtomString(),
'version' => $this->mercureOptions->version->value,
]);
}
}
@@ -8,11 +8,13 @@ use Cake\Chronos\Chronos;
use Laminas\Diactoros\Response\JsonResponse;
use Laminas\Diactoros\ServerRequestFactory;
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Common\Mercure\JwtProviderInterface;
use Shlinkio\Shlink\Common\Mercure\MercureOptions;
use Shlinkio\Shlink\Common\Mercure\MercureVersion;
use Shlinkio\Shlink\Rest\Action\MercureInfoAction;
use Shlinkio\Shlink\Rest\Exception\MercureException;
@@ -25,39 +27,29 @@ class MercureInfoActionTest extends TestCase
$this->provider = $this->createMock(JwtProviderInterface::class);
}
#[Test, DataProvider('provideNoHostConfigs')]
public function throwsExceptionWhenConfigDoesNotHavePublicHost(array $mercureConfig): void
#[Test]
public function throwsExceptionWhenConfigDoesNotHavePublicHost(): void
{
$this->provider->expects($this->never())->method('buildSubscriptionToken');
$action = new MercureInfoAction($this->provider, $mercureConfig);
$action = new MercureInfoAction($this->provider, new MercureOptions());
$this->expectException(MercureException::class);
$action->handle(ServerRequestFactory::fromGlobals());
}
public static function provideNoHostConfigs(): iterable
{
yield 'host not defined' => [[]];
yield 'host is null' => [['public_hub_url' => null]];
}
public function provideValidConfigs(): iterable
{
yield 'days not defined' => [['public_hub_url' => 'http://foobar.com']];
yield 'days defined' => [['public_hub_url' => 'http://foobar.com', 'jwt_days_duration' => 20]];
}
#[Test, DataProvider('provideDays')]
public function returnsExpectedInfoWhenEverythingIsOk(int|null $days): void
#[Test]
#[TestWith([MercureVersion::LEGACY])]
#[TestWith([MercureVersion::STABLE])]
public function returnsExpectedInfoWhenEverythingIsOk(MercureVersion $version): void
{
$this->provider->expects($this->once())->method('buildSubscriptionToken')->willReturn('abc.123');
$action = new MercureInfoAction($this->provider, [
'public_hub_url' => 'http://foobar.com',
'jwt_days_duration' => $days,
]);
$action = new MercureInfoAction($this->provider, new MercureOptions(
publicHubUrl: 'http://foobar.com',
version: $version,
));
/** @var JsonResponse $resp */
$resp = $action->handle(ServerRequestFactory::fromGlobals());
@@ -68,15 +60,10 @@ class MercureInfoActionTest extends TestCase
self::assertArrayHasKey('token', $payload);
self::assertArrayHasKey('jwtExpiration', $payload);
self::assertEquals(
Chronos::now()->addDays($days ?? 1)->startOfDay(),
Chronos::now()->addDays(1)->startOfDay(),
Chronos::parse($payload['jwtExpiration'])->startOfDay(),
);
}
public static function provideDays(): iterable
{
yield 'days not defined' => [null];
yield 'days defined' => [10];
self::assertEquals($version->value, $payload['version']);
}
#[Test, AllowMockObjectsWithoutExpectations]