Compare commits

...

431 Commits

Author SHA1 Message Date
Alejandro Celaya
5cec697be3 Merge pull request #1683 from shlinkio/develop
Release 3.5.0
2023-01-28 11:10:49 +01:00
Alejandro Celaya
587bbfdd73 Add SemVer-compliant constraints for shlink libs 2023-01-28 10:48:34 +01:00
Alejandro Celaya
b3a2ceedea Merge pull request #1680 from acelaya-forks/feature/loosly-mode
Feature/loosly mode
2023-01-28 10:36:19 +01:00
Alejandro Celaya
621f18bf40 Recover DB test only for platforms in which it passes 2023-01-28 10:20:57 +01:00
Alejandro Celaya
99c1a59dd4 Refactor CustomSlugFilter for simplicity 2023-01-28 10:16:53 +01:00
Alejandro Celaya
3a149c9edc Update changelog 2023-01-28 10:09:54 +01:00
Alejandro Celaya
fdaf5fb2f3 Add support for short URL mode in installer, and handle loosely mode in custom slugs 2023-01-28 10:06:11 +01:00
Alejandro Celaya
2f83e90c8b Add option to do loosely matches on short URLs when mode is loosely 2023-01-26 20:45:36 +01:00
Alejandro Celaya
05acd4ae88 Add two modes for short URLs 2023-01-25 20:33:07 +01:00
Alejandro Celaya
87007677ed Merge pull request #1679 from acelaya-forks/feature/deprecate-url-validation
Deprecated validateUrl option on short URL creation/edition
2023-01-23 20:45:13 +01:00
Alejandro Celaya
4ee0032c2a Deprecated validateUrl option on short URL creation/edition 2023-01-23 20:30:12 +01:00
Alejandro Celaya
06583a0bc1 Merge pull request #1677 from acelaya-forks/feature/openswoole-4.12.1
Updated to openswoole 4.12.1
2023-01-23 08:07:15 +01:00
Alejandro Celaya
024c9c1a7a Fixed paths glob patterns in some workflows 2023-01-22 21:01:46 +01:00
Alejandro Celaya
f3855dbc6f Updated to openswoole 4.12.1 2023-01-22 20:57:48 +01:00
Alejandro Celaya
758dac47c3 Merge pull request #1668 from acelaya-forks/feature/device-long-urls
Feature/device long urls
2023-01-22 12:50:33 +01:00
Alejandro Celaya
81393a76b4 Ensure GITHUB_TOKEN is exposed to roadrunner api tests workflow 2023-01-22 12:43:03 +01:00
Alejandro Celaya
9949bb654d Set more accurate swagger docs in terms of what props are required/nullable for device long URLs 2023-01-22 12:35:07 +01:00
Alejandro Celaya
b0b9902f40 Add unit test to cover device URLs edition, and fix bug thanks to it 2023-01-22 12:18:36 +01:00
Alejandro Celaya
5aa8de11f4 Update version on user agent used to validate URLsç 2023-01-22 12:00:16 +01:00
Alejandro Celaya
b18c9e495f Add API test for short URL edition with device long URLs 2023-01-22 11:47:45 +01:00
Alejandro Celaya
d3590234a3 Add API test for short URL creation with device long URLs 2023-01-22 11:36:00 +01:00
Alejandro Celaya
39adef8ab8 Make it impossible to create a short URL with an empty long URL 2023-01-22 11:27:16 +01:00
Alejandro Celaya
13e443880a Allow device long URLs to be removed from short URLs by providing null value 2023-01-22 11:03:05 +01:00
Alejandro Celaya
45961144b9 Update changelog 2023-01-22 09:47:15 +01:00
Alejandro Celaya
34129b8d24 Update async API docs with device long URLs 2023-01-21 12:09:38 +01:00
Alejandro Celaya
48bd97fe41 Return deviceLongUrls as part of the short URL data and document API changes 2023-01-21 12:05:54 +01:00
Alejandro Celaya
b1b67c497e Add logic to dynamically resolve the long URL to redirect to based on requesting device 2023-01-21 11:15:42 +01:00
Alejandro Celaya
237fb95b4b Update ShortUrlRedirectionBuilder to accept a request object instead of a raw query array 2023-01-21 10:37:12 +01:00
Alejandro Celaya
c1b7c6ba6c Updated to shlink-common with support for proxies for entities with public readonly props 2023-01-21 10:12:52 +01:00
Alejandro Celaya
d8add9291f Removed public readonly prop from entity, as it can cause errors when a proxy is generated 2023-01-21 10:12:52 +01:00
Alejandro Celaya
a93edf158e Added logic to persist device long URLs while creating/editing a short URL 2023-01-21 10:12:52 +01:00
Alejandro Celaya
fdadf3ba07 Created unit test for DeviceLongUrlsValidator 2023-01-21 10:12:52 +01:00
Alejandro Celaya
3e26f1113d Extract device long URL validation to its own validation class 2023-01-21 10:12:52 +01:00
Alejandro Celaya
822652cac3 Allow providing device long URLs during short URL edition 2023-01-21 10:12:52 +01:00
Alejandro Celaya
1447687ebe Add deviceLongUrls to short URL creation 2023-01-21 10:12:52 +01:00
Alejandro Celaya
12150f775d Created persistence for device long URLs 2023-01-21 10:12:52 +01:00
Alejandro Celaya
5f2f179581 Merge pull request #1675 from acelaya-forks/feature/gh-build-improve
Extract docker image building during CI to its own workflow
2023-01-21 10:11:48 +01:00
Alejandro Celaya
407134bab1 Extract docker image building during CI to its own workflow 2023-01-21 09:59:43 +01:00
Alejandro Celaya
de5b895fad Merge pull request #1672 from acelaya-forks/feature/domain
Replace references to doma.in with s.test
2023-01-19 09:30:28 +01:00
Alejandro Celaya
80e3f01562 Replace references to doma.in with s.test 2023-01-19 09:05:52 +01:00
Alejandro Celaya
6904dcfed0 Merge pull request #1665 from acelaya-forks/feature/openswoole-env
Add support to load openswoole-specific config via env vars
2023-01-12 20:10:21 +01:00
Alejandro Celaya
21863e8de6 Add support to load openswoole-specific config via env vars 2023-01-12 19:39:26 +01:00
Alejandro Celaya
d75be372cb Merge pull request #1657 from acelaya-forks/feature/extra-method-redirects
Feature/extra method redirects
2023-01-07 17:20:17 +01:00
Alejandro Celaya
edaf999bf5 Fixed constant assignment on enum which is not valid for PHP 8.1 2023-01-07 17:09:53 +01:00
Alejandro Celaya
3e98485c8b Updated to installer supporting redirect status codes 308 and 307 2023-01-07 17:02:34 +01:00
Alejandro Celaya
cc292886a6 Updated changelog 2023-01-07 13:55:46 +01:00
Alejandro Celaya
0c1b36d0d4 Added config post-processor which sets proper allowed methods based on redirect status codes 2023-01-07 13:51:35 +01:00
Alejandro Celaya
a06957e9fa Moved config post-processors to their own sub-namespace 2023-01-07 13:04:46 +01:00
Alejandro Celaya
390bc59d99 Added support for redirect status code 307 and 308 2023-01-07 11:27:15 +01:00
Alejandro Celaya
85464f0fbb Added ADR with options to support other HTTP methods in short URLs 2023-01-07 10:44:08 +01:00
Alejandro Celaya
42f7a68ba5 Updated dev container base images 2023-01-05 18:50:49 +01:00
Alejandro Celaya
e3397a7c90 Merge pull request #1652 from acelaya-forks/feature/extended-tags-stats
Feature/extended tags stats
2023-01-02 20:25:50 +01:00
Alejandro Celaya
46b4a21617 Fixed missing null check 2023-01-02 20:17:29 +01:00
Alejandro Celaya
fc0aba6311 Updated changelog 2023-01-02 20:03:30 +01:00
Alejandro Celaya
0b96a79c41 Updated async API docs 2023-01-02 20:02:50 +01:00
Alejandro Celaya
a5929ebb29 Added swagger docs for visits summary in tags with stats 2023-01-02 19:58:02 +01:00
Alejandro Celaya
ce9ec0d738 Fixed ordering in tags supporting more fields 2023-01-02 19:49:54 +01:00
Alejandro Celaya
961178fd82 Added amount of bots, non-bots and total visits to the list of tags with stats 2023-01-02 19:28:32 +01:00
Alejandro Celaya
49c73a9590 Merge pull request #1650 from acelaya-forks/feature/handle-malformed-body
Feature/handle malformed body
2023-01-02 13:54:52 +01:00
Alejandro Celaya
92c80e7833 Removed superfluous exception code by using named args 2023-01-02 13:47:16 +01:00
Alejandro Celaya
6d5bce0078 Updated changelog 2023-01-02 13:39:13 +01:00
Alejandro Celaya
112cbb9039 Added API test for malformed request JSON body 2023-01-02 13:38:04 +01:00
Alejandro Celaya
812c5f4993 Added new handled error for when request body is not valid JSON 2023-01-02 13:33:24 +01:00
Alejandro Celaya
921f303404 Merge pull request #1649 from acelaya-forks/feature/detailed-visits-stats
Feature/detailed visits stats
2023-01-02 13:11:20 +01:00
Alejandro Celaya
e0a9f8120c Fixed unintended change in phpdoc 2023-01-02 12:48:23 +01:00
Alejandro Celaya
8ecc241a4b Added API test for the visits stats endpoint 2023-01-02 12:45:08 +01:00
Alejandro Celaya
30e34151ed Updated changelog 2023-01-02 12:36:25 +01:00
Alejandro Celaya
d734578f74 Reflected changes to visits stats in the swagger docs 2023-01-02 12:35:15 +01:00
Alejandro Celaya
37c8328eed Added split info about bots, non-bots and total visits to the visits stats 2023-01-02 12:28:34 +01:00
Alejandro Celaya
e71f6bb528 Documented support for PHP 8.2 in readme 2022-12-29 16:35:20 +01:00
Alejandro Celaya
f7ae52f86e Fixed build badge in README 2022-12-17 10:59:42 +01:00
Alejandro Celaya
067d1cc41c Merge pull request #1637 from shlinkio/develop
Release 3.4.0
2022-12-16 22:55:02 +01:00
Alejandro Celaya
b97af7efb9 Added v3.4.0 to changelog 2022-12-16 22:33:16 +01:00
Alejandro Celaya
fd0ecc05b2 Merge pull request #1634 from acelaya-forks/feature/non-bot-count
Feature/non bot count
2022-12-16 21:02:22 +01:00
Alejandro Celaya
5b934c3f9a Updated changelog 2022-12-16 19:47:17 +01:00
Alejandro Celaya
c7a2f499e0 Added support to order short URLs list by amount of non-bot visits 2022-12-16 19:42:46 +01:00
Alejandro Celaya
713f7e7bc9 Added missing dock block 2022-12-16 18:18:09 +01:00
Alejandro Celaya
09078e4c6a Updated short URL API docs including new visitsSummary 2022-12-16 13:34:40 +01:00
Alejandro Celaya
1f66ec2af5 Fixed API tests 2022-12-16 10:53:44 +01:00
Alejandro Celaya
936e5b3b86 Fixed PublishingUpdatesGeneratorTest 2022-12-16 10:36:09 +01:00
Alejandro Celaya
99f28b569b Created method to get non-bot visits count for a short URL 2022-12-16 10:06:39 +01:00
Alejandro Celaya
0c83dea8b7 Merge pull request #1629 from acelaya-forks/feature/docker-8.2
Feature/docker 8.2
2022-12-14 18:55:37 +01:00
Alejandro Celaya
30edfdbdc5 Updated docker images to PHP 8.2 2022-12-14 15:01:00 +01:00
Alejandro Celaya
60ef98b836 Extracted method to find crawlable short codes to its own query object 2022-12-14 14:38:22 +01:00
Alejandro Celaya
73c8b53882 Split some logic from VisitRepository into its own injectable repository 2022-12-14 12:28:23 +01:00
Alejandro Celaya
425d8f0a3f Merge pull request #1628 from acelaya-forks/feature/split-repos-poc
Split short URL listing capabilities on its own repo and service
2022-12-13 20:48:34 +01:00
Alejandro Celaya
92a83b82a0 Split short URL listing capabilities on its own repo and service 2022-12-13 19:37:02 +01:00
Alejandro Celaya
d1ec15febf Merge pull request #1627 from acelaya-forks/feature/redis-credentials
Feature/redis credentials
2022-12-12 21:00:40 +01:00
Alejandro Celaya
dd345c82ea Updated changelog 2022-12-12 20:51:43 +01:00
Alejandro Celaya
2bf3e6a13b Addedsupport for credentials on redis 2022-12-12 20:50:21 +01:00
Alejandro Celaya
0b04476c99 Merge pull request #1622 from acelaya-forks/feature/filter-out-disabled
Feature/filter out disabled
2022-12-11 18:44:42 +01:00
Alejandro Celaya
229dc93132 Fixed typo 2022-12-11 18:36:46 +01:00
Alejandro Celaya
0952c488be Added exclusion flags to ListShortUrlsCommand 2022-12-11 18:33:40 +01:00
Alejandro Celaya
c4f28b3a32 Renamed ShortUrl::fromMeta to ShortUrl::create 2022-12-11 18:24:47 +01:00
Alejandro Celaya
201f25e0ad Improved API tests to cover exlucding disabled URLs from lists 2022-12-11 13:38:11 +01:00
Alejandro Celaya
0c3523c34a Fixed E2E test suites 2022-12-11 13:22:16 +01:00
Alejandro Celaya
0d7a0ee9ea Fixed more coding styles 2022-12-11 13:11:43 +01:00
Alejandro Celaya
931bdb0cd7 Fixed coding styles 2022-12-11 13:03:19 +01:00
Alejandro Celaya
8807a78463 Improved performance when filtering out shortUrls which reached their limit by using a sub-query 2022-12-11 13:00:06 +01:00
Alejandro Celaya
d832133410 Enhanced db tests for expired short urls filtering 2022-12-11 12:33:17 +01:00
Alejandro Celaya
cdde59b543 Added db test for filtering of disabled short URLs 2022-12-11 11:41:37 +01:00
Alejandro Celaya
463dfe9729 Added support to filter out expired short URLs from list 2022-12-11 10:26:04 +01:00
Alejandro Celaya
805c8c87ba Fixed nasty typo 2022-12-10 19:59:30 +01:00
Alejandro Celaya
7ba2cfc010 Moved true before false in swagger docs 2022-12-10 19:59:30 +01:00
Alejandro Celaya
40794c476f Updated API docs with new short URLs list filters 2022-12-10 19:59:30 +01:00
Alejandro Celaya
c3ab871366 Exposed new short URLs list filtering params 2022-12-10 19:59:30 +01:00
Alejandro Celaya
42a5296f93 Added new params to short URLs list to filter out 'disabled' short ones 2022-12-10 19:59:30 +01:00
Alejandro Celaya
183db4ff80 Merge pull request #1626 from acelaya-forks/feature/fix-ms-sql
Feature/fix ms sql
2022-12-10 19:57:50 +01:00
Alejandro Celaya
0bc9bd9281 Added TrustServerCertificate=true to mssql connections 2022-12-10 19:40:33 +01:00
Alejandro Celaya
9bed7ef156 Updated docker images to MS ODBC 18 for PDO MSSQL 2022-12-10 19:15:38 +01:00
Alejandro Celaya
8f68e4b9f5 Merge pull request #1624 from acelaya-forks/feature/php-8.2-full-support
Feature/php 8.2 full support
2022-12-10 18:37:18 +01:00
Alejandro Celaya
6589c8fce6 Downgraded docker images to latest php 8.1 2022-12-10 17:58:10 +01:00
Alejandro Celaya
38b313a25d Updated changelog 2022-12-10 17:30:35 +01:00
Alejandro Celaya
dab0ebeb99 Updated dockerimages to PHP 8.2 and added full support for this version 2022-12-10 17:29:52 +01:00
Alejandro Celaya
27bf7220b9 Merge pull request #1623 from acelaya-forks/feature/fix-flush-redis
Added missing namespace for cache adapters, causing full cache to be …
2022-12-10 10:21:46 +01:00
Alejandro Celaya
e68ef87c66 Renamed config file from redis to cache 2022-12-10 10:12:56 +01:00
Alejandro Celaya
29b747c192 Added missing namespace for cache adapters, causing full cache to be flushed in some circumstances 2022-12-10 10:11:25 +01:00
Alejandro Celaya
2047d6b772 Merge pull request #1621 from acelaya-forks/feature/default-domain-search
Feature/default domain search
2022-12-08 20:43:42 +01:00
Alejandro Celaya
71e7938b7a Updated changelog 2022-12-08 20:33:59 +01:00
Alejandro Celaya
6bce219eb3 Added test to cover searching short URLs by default domain 2022-12-08 20:32:48 +01:00
Alejandro Celaya
dfcac525bc Enabled search by default domain 2022-12-08 20:22:50 +01:00
Alejandro Celaya
da307aee0a Merge pull request #1620 from acelaya-forks/feature/empty-domain-fix
Feature/empty domain fix
2022-12-07 19:15:29 +01:00
Alejandro Celaya
edf2b5b4c2 Updated changelog 2022-12-07 19:06:58 +01:00
Alejandro Celaya
f41d947cf7 Ensured empty string is ignored as the domain during short URL creation 2022-12-07 19:06:05 +01:00
Alejandro Celaya
54bc169525 Merge pull request #1619 from acelaya-forks/feature/import-orphan-visits
Feature/import orphan visits
2022-12-05 15:03:28 +01:00
Alejandro Celaya
05d55c4000 Added one more case to cover import orphan visits when visits already exist 2022-12-05 14:48:24 +01:00
Alejandro Celaya
739f5eb421 Added test for orphan visits import 2022-12-05 14:42:26 +01:00
Alejandro Celaya
0aab1bdc4e Added test for findMostRecentOrphanVisit 2022-12-04 20:42:28 +01:00
Alejandro Celaya
47f99cf6cc Updated changelog 2022-12-04 20:38:07 +01:00
Alejandro Celaya
55c9773a02 Added logic to import orphan visits 2022-12-04 20:35:38 +01:00
Alejandro Celaya
4b66aaba5c Updated to latest shlink-importer 2022-12-04 12:28:44 +01:00
Alejandro Celaya
4223408090 Updated to common-config with support for valinor 1.0.0 2022-11-28 15:47:59 +01:00
Alejandro Celaya
58e6b0b683 Added badge for Mastodon follow 2022-11-17 19:57:47 +01:00
Alejandro Celaya
891438c672 Updated shlink-config 2022-11-11 16:33:02 +01:00
Alejandro Celaya
910864eaaf Reduced required MSI to 80 2022-11-05 10:54:12 +01:00
Alejandro Celaya
598c0757be Merge pull request #1587 from acelaya-forks/feature/phpstan-phpunit
Feature/phpstan phpunit
2022-10-24 20:34:11 +02:00
Alejandro Celaya
01e0a95e14 Added rest of tests to phpstan check 2022-10-24 20:25:06 +02:00
Alejandro Celaya
f459a99e7e Added db tests to phpstan checks 2022-10-24 20:14:48 +02:00
Alejandro Celaya
85e18a4754 Fixed all phpstan inspections on tests 2022-10-24 20:11:25 +02:00
Alejandro Celaya
1650499a38 Added more stricter types for mocks 2022-10-24 19:59:03 +02:00
Alejandro Celaya
51f243995a Added stricter types for mocks 2022-10-24 19:53:13 +02:00
Alejandro Celaya
aeafb244d9 Merge pull request #1586 from acelaya-forks/feature/phpunit-mocks
Feature/phpunit mocks
2022-10-23 23:18:49 +02:00
Alejandro Celaya
142417dda1 Updated changelog 2022-10-23 23:08:54 +02:00
Alejandro Celaya
da658185c3 Fixed coding styles 2022-10-23 23:07:50 +02:00
Alejandro Celaya
ef82158368 Migrated ApiKeyServiceTest to use PHPUnit mocks 2022-10-23 23:07:17 +02:00
Alejandro Celaya
083ccd36b7 Migrated OverrideDomainMiddlewareTest to use PHPUnit mocks 2022-10-23 23:00:57 +02:00
Alejandro Celaya
d61c79da84 Migrated DropDefaultDomainFromRequestMiddlewareTest to use PHPUnit mocks 2022-10-23 22:56:12 +02:00
Alejandro Celaya
8f76c3e202 Migrated DefaultShortCodesLengthMiddlewareTest to use PHPUnit mocks 2022-10-23 22:55:11 +02:00
Alejandro Celaya
23aa7a015c Migrated CreateShortUrlContentNegotiationMiddlewareTest to use PHPUnit mocks 2022-10-23 22:53:48 +02:00
Alejandro Celaya
674a4416cf Migrated NotConfiguredMercureErrorHandlerTest to use PHPUnit mocks 2022-10-23 22:51:38 +02:00
Alejandro Celaya
db85915c2f Migrated BackwardsCompatibleProblemDetailsHandlerTest to use PHPUnit mocks 2022-10-23 22:48:30 +02:00
Alejandro Celaya
dfc8e8d74e Migrated CrossDomainMiddlewareTest to use PHPUnit mocks 2022-10-23 22:47:34 +02:00
Alejandro Celaya
b2b424a4ed Migrated BodyParserMiddlewareTest to use PHPUnit mocks 2022-10-23 22:45:23 +02:00
Alejandro Celaya
3433899577 Migrated AuthenticationMiddlewareTest to use PHPUnit mocks 2022-10-23 22:40:14 +02:00
Alejandro Celaya
b1f814e118 Migrated InitialApiKeyDelegatorTest to use PHPUnit mocks 2022-10-23 22:36:16 +02:00
Alejandro Celaya
7aa6afeb30 Migrated TagVisitsActionTest to use PHPUnit mocks 2022-10-23 22:28:29 +02:00
Alejandro Celaya
d414496a3c Migrated ShortUrlVisitsActionTest to use PHPUnit mocks 2022-10-23 22:27:27 +02:00
Alejandro Celaya
d4684fd01f Migrated OrphanVisitsActionTest to use PHPUnit mocks 2022-10-23 22:25:54 +02:00
Alejandro Celaya
bb444a02fe Migrated NonOrphanVisitsActionTest to use PHPUnit mocks 2022-10-23 22:24:06 +02:00
Alejandro Celaya
e980a8d121 Migrated GlobalVisitsActionTest to use PHPUnit mocks 2022-10-23 22:23:08 +02:00
Alejandro Celaya
f493baaf2b Migrated DomainVisitsActionTest to use PHPUnit mocks 2022-10-23 22:22:14 +02:00
Alejandro Celaya
28f26920dd Migrated UpdateTagActionTest to use PHPUnit mocks 2022-10-23 22:21:23 +02:00
Alejandro Celaya
69e994c067 Migrated TagsStatsActionTest to use PHPUnit mocks 2022-10-23 22:20:21 +02:00
Alejandro Celaya
656083cb6f Migrated ListTagsActionTest to use PHPUnit mocks 2022-10-23 22:19:14 +02:00
Alejandro Celaya
ab9ea887d2 Migrated DeleteTagsActionTest to use PHPUnit mocks 2022-10-23 22:17:35 +02:00
Alejandro Celaya
9ac6a50e66 Migrated SingleStepCreateShortUrlActionTest to use PHPUnit mocks 2022-10-23 22:16:19 +02:00
Alejandro Celaya
acc9cb94b5 Migrated ResolveShortUrlActionTest to use PHPUnit mocks 2022-10-23 22:14:28 +02:00
Alejandro Celaya
01829c82ee Migrated ListShortUrlsActionTest to use PHPUnit mocks 2022-10-23 22:13:27 +02:00
Alejandro Celaya
9c02ea8799 Migrated EditShortUrlActionTest to use PHPUnit mocks 2022-10-23 22:12:27 +02:00
Alejandro Celaya
d202538581 Migrated DeleteShortUrlActionTest to use PHPUnit mocks 2022-10-23 22:10:41 +02:00
Alejandro Celaya
a84b642ba5 Migrated CreateShortUrlActionTest to use PHPUnit mocks 2022-10-23 22:09:37 +02:00
Alejandro Celaya
74176c298f Migrated ListDomainsActionTest to use PHPUnit mocks 2022-10-23 22:06:48 +02:00
Alejandro Celaya
91e21441f7 Migrated DomainRedirectsActionTest to use PHPUnit mocks 2022-10-23 22:05:51 +02:00
Alejandro Celaya
896b7f2d73 Migrated MercureInfoActionTest to use PHPUnit mocks 2022-10-23 22:04:00 +02:00
Alejandro Celaya
66ed152358 Migrated HealthActionTest to use PHPUnit mocks 2022-10-23 22:02:31 +02:00
Alejandro Celaya
257134cd80 Migrated VisitsForTagPaginatorAdapterTest to use PHPUnit mocks 2022-10-23 21:59:18 +02:00
Alejandro Celaya
a4373aee91 Migrated OrphanVisitsPaginatorAdapterTest to use PHPUnit mocks 2022-10-23 21:56:34 +02:00
Alejandro Celaya
7442905873 Migrated NonOrphanVisitsPaginatorAdapterTest to use PHPUnit mocks 2022-10-23 21:55:06 +02:00
Alejandro Celaya
d3af51f684 Migrated VisitToLocationHelperTest to use PHPUnit mocks 2022-10-23 21:24:30 +02:00
Alejandro Celaya
04419a7242 Migrated VisitLocatorTest to use PHPUnit mocks 2022-10-23 21:21:23 +02:00
Alejandro Celaya
a45d6e6b44 Migrated VisitsTrackerTest to use PHPUnit mocks 2022-10-23 21:08:58 +02:00
Alejandro Celaya
37b1306eb3 Migrated VisitsStatsHelperTest to use PHPUnit mocks 2022-10-23 21:05:13 +02:00
Alejandro Celaya
cff6573767 Migrated RequestTrackerTest to use PHPUnit mocks 2022-10-23 20:45:56 +02:00
Alejandro Celaya
a2f34e02ad Migrated UrlValidatorTest to use PHPUnit mocks 2022-10-23 20:39:06 +02:00
Alejandro Celaya
796543d194 Migrated DoctrineBatchHelperTest to use PHPUnit mocks 2022-10-23 20:32:13 +02:00
Alejandro Celaya
3b25fb27fe Migrated TagsPaginatorAdapterTest to use PHPUnit mocks 2022-10-23 20:28:45 +02:00
Alejandro Celaya
3b20f955ff Migrated TagsInfoPaginatorAdapterTest to use PHPUnit mocks 2022-10-23 20:27:51 +02:00
Alejandro Celaya
c81ae9c40d Migrated TagServiceTest to use PHPUnit mocks 2022-10-23 20:26:44 +02:00
Alejandro Celaya
7ceae7af87 Merge pull request #1585 from acelaya-forks/feature/phpunit-mocks
Feature/phpunit mocks
2022-10-23 20:19:22 +02:00
Alejandro Celaya
5e02cfe375 Fixed coding styles 2022-10-23 18:29:32 +02:00
Alejandro Celaya
6e836b5fd9 Migrated PersistenceShortUrlRelationResolverTest to use PHPUnit mocks 2022-10-23 18:28:28 +02:00
Alejandro Celaya
8753e3a77f Migrated ShortUrlRepositoryAdapterTest to use PHPUnit mocks 2022-10-23 18:17:29 +02:00
Alejandro Celaya
6a2227efc5 Removed all uinnecessary usages of equalsTo param constraint 2022-10-23 18:15:57 +02:00
Alejandro Celaya
1fbcea7a06 Migrated ExtraPathRedirectMiddlewareTest to use PHPUnit mocks 2022-10-23 17:53:09 +02:00
Alejandro Celaya
168c839cf1 Migrated TrimTrailingSlashMiddlewareTest to use PHPUnit mocks 2022-10-23 17:39:57 +02:00
Alejandro Celaya
162e913cc4 Migrated ShortUrlTitleResolutionHelperTest to use PHPUnit mocks 2022-10-23 17:38:04 +02:00
Alejandro Celaya
5aaf50d68e Migrated ShortCodeUniquenessHelperTest to use PHPUnit mocks 2022-10-23 17:35:50 +02:00
Alejandro Celaya
d2f5be1d18 Migrated UrlShortenerTest to use PHPUnit mocks 2022-10-23 11:32:13 +02:00
Alejandro Celaya
36ab455a49 Migrated ShortUrlServiceTest to use PHPUnit mocks 2022-10-23 11:14:01 +02:00
Alejandro Celaya
ee8cab8455 Migrated ShortUrlResolverTest to use PHPUnit mocks 2022-10-23 11:09:40 +02:00
Alejandro Celaya
bd884e85d4 Migrated DeleteShortUrlServiceTest to use PHPUnit mocks 2022-10-23 11:03:47 +02:00
Alejandro Celaya
5ceb6fb740 No longer let pipelines pass on error with PHP 8.2 2022-10-23 11:00:50 +02:00
Alejandro Celaya
0d6155e8bc Merge pull request #1584 from acelaya-forks/feature/phpunit-mocks
Feature/phpunit mocks
2022-10-22 20:51:01 +02:00
Alejandro Celaya
a78c59c11a Fixed coding styles 2022-10-22 20:41:17 +02:00
Alejandro Celaya
173420c608 Migrated ImportedLinksProcessorTest to use PHPUnit mocks 2022-10-22 20:39:55 +02:00
Alejandro Celaya
10b0ec301b Migrated ValidationExceptionTest to use PHPUnit mocks 2022-10-22 20:05:06 +02:00
Alejandro Celaya
1706a869d9 Migrated NotifyVisitToRedisTest to use PHPUnit mocks 2022-10-22 20:04:12 +02:00
Alejandro Celaya
d0393799d2 Migrated NotifyNewShortUrlToRedisTest to use PHPUnit mocks 2022-10-22 19:59:32 +02:00
Alejandro Celaya
739433ba8b Migrated NotifyVisitToRabbitMqTest to use PHPUnit mocks 2022-10-22 19:05:34 +02:00
Alejandro Celaya
a15e9c29c8 Migrated NotifyNewShortUrlToRabbitMqTest to use PHPUnit mocks 2022-10-22 18:49:43 +02:00
Alejandro Celaya
d58f89aa26 Merge pull request #1583 from acelaya-forks/feature/phpunit-mocks
Feature/phpunit mocks
2022-10-22 15:14:57 +02:00
Alejandro Celaya
b7671f70da Fixed coding styles 2022-10-22 14:41:42 +02:00
Alejandro Celaya
52366b9dd4 Removed last reference to prophecytrait in CLI module 2022-10-22 14:41:22 +02:00
Alejandro Celaya
32417e40cb Migrated ShlinkTableTest to use PHPUnit mocks 2022-10-22 14:40:35 +02:00
Alejandro Celaya
4cb44be9a0 Migrated ProcessRunnerTest to use PHPUnit mocks 2022-10-22 14:37:13 +02:00
Alejandro Celaya
a484455b0b Migrated GeolocationDbUpdaterTest to use PHPUnit mocks 2022-10-22 14:27:07 +02:00
Alejandro Celaya
4b3ed2b7ba Migrated LocateVisitsCommandTest to use PHPUnit mocks 2022-10-22 14:16:42 +02:00
Alejandro Celaya
e2986a7b4c Migrated GetOrphanVisitsCommandTest to use PHPUnit mocks 2022-10-22 14:06:54 +02:00
Alejandro Celaya
82e04800aa Migrated GetNonOrphanVisitsCommandTest to use PHPUnit mocks 2022-10-22 14:06:00 +02:00
Alejandro Celaya
5d367da626 Migrated DownloadGeoLiteDbCommandTest to use PHPUnit mocks 2022-10-22 14:02:38 +02:00
Alejandro Celaya
59de5a5f55 Migrated RenameTagCommandTest to use PHPUnit mocks 2022-10-22 13:53:45 +02:00
Alejandro Celaya
0855104068 Migrated ListTagsCommandTest to use PHPUnit mocks 2022-10-22 13:49:11 +02:00
Alejandro Celaya
8c6f97c4e2 Migrated GetTagVisitsCommandTest to use PHPUnit mocks 2022-10-22 13:47:28 +02:00
Alejandro Celaya
2d16856582 Migrated DeleteTagsCommandTest to use PHPUnit mocks 2022-10-22 13:45:11 +02:00
Alejandro Celaya
41e903cf26 Migrated ResolveUrlCommandTest to use PHPUnit mocks 2022-10-22 13:44:10 +02:00
Alejandro Celaya
4872bd3a92 Migrated ListShortUrlsCommandTest to use PHPUnit mocks 2022-10-22 13:42:46 +02:00
Alejandro Celaya
8b675f55cc Migrated GetShortUrlVisitsCommandTest to use PHPUnit mocks 2022-10-22 13:38:46 +02:00
Alejandro Celaya
acda7f02c6 Migrated DeleteShortUrlCommandTest to use PHPUnit mocks 2022-10-22 13:36:33 +02:00
Alejandro Celaya
184ff90b9f Migrated CreateShortUrlCommandTest to use PHPUnit mocks 2022-10-22 13:27:48 +02:00
Alejandro Celaya
d8be3c28cb Migrated ListDomainsCommandTest to use PHPUnit mocks 2022-10-22 13:21:54 +02:00
Alejandro Celaya
3d358ab046 Migrated GetDomainVisitsCommandTest to use PHPUnit mocks 2022-10-22 13:21:00 +02:00
Alejandro Celaya
960bdfc232 Migrated DomainRedirectsCommandTest to use PHPUnit mocks 2022-10-22 13:17:12 +02:00
Alejandro Celaya
101b4daff4 Migrated MigrateDatabaseCommandTest to use PHPUnit mocks 2022-10-22 13:08:05 +02:00
Alejandro Celaya
13431ff8cf Migrated CreateDatabaseCommandTest to use PHPUnit mocks 2022-10-22 13:05:36 +02:00
Alejandro Celaya
4cdcad29df Migrated ListKeysCommandTest to use PHPUnit mocks 2022-10-22 12:53:28 +02:00
Alejandro Celaya
a4c34ff7be Migrated GenerateKeyCommandTest to use PHPUnit mocks 2022-10-22 12:52:11 +02:00
Alejandro Celaya
2b7b5e9a8f Migrated DisableKeyCommandTest to use PHPUnit mocks 2022-10-22 12:48:17 +02:00
Alejandro Celaya
58db902084 Migrated CliTestUtilsTrait to use PHPUnit mocks 2022-10-22 12:46:16 +02:00
Alejandro Celaya
983e3c9eaa Merge pull request #1582 from acelaya-forks/feature/phpunit-mocks
Feature/phpunit mocks
2022-10-22 10:11:30 +02:00
Alejandro Celaya
dbe35cf567 Fixed coding styles 2022-10-22 10:03:28 +02:00
Alejandro Celaya
8298f9d491 Migrated NotifyVisitToMercureTest to use PHPUnit mocks 2022-10-22 10:03:05 +02:00
Alejandro Celaya
16a951b938 Migrated NotifyNewShortUrlToMercureTest to use PHPUnit mocks 2022-10-22 09:50:12 +02:00
Alejandro Celaya
51fcbfb3c2 Migrated UpdateGeoLiteDbTest to use PHPUnit mocks 2022-10-22 09:42:21 +02:00
Alejandro Celaya
e01e370d16 Migrated NotifyVisitToWebHooksTest to use PHPUnit mocks 2022-10-22 08:08:49 +02:00
Alejandro Celaya
736ac8ba90 Migrated LocateVisitTest to use PHPUnit mocks 2022-10-22 07:54:57 +02:00
Alejandro Celaya
d07104b8d9 Migrated LocateUnlocatedVisitsTest to use PHPUnit mocks 2022-10-22 07:34:38 +02:00
Alejandro Celaya
cad53e397a Migrated CloseDbConnectionEventListenerTest to use PHPUnit mocks 2022-10-22 07:32:37 +02:00
Alejandro Celaya
3608a6d068 Migrated CloseDbConnectionEventListenerDelegatorTest to use PHPUnit mocks 2022-10-22 07:28:15 +02:00
Alejandro Celaya
92ddd2eebe Merge pull request #1581 from acelaya-forks/feature/phpunit-mocks
Feature/phpunit mocks
2022-10-21 19:42:58 +02:00
Alejandro Celaya
bf0b58b344 Migrated NotFoundTypeResolverMiddlewareTest to use PHPUnit mocks 2022-10-21 19:32:25 +02:00
Alejandro Celaya
ff543b151c Migrated NotFoundTrackerMiddlewareTest to use PHPUnit mocks 2022-10-21 19:29:02 +02:00
Alejandro Celaya
d842025835 Migrated NotFoundTemplateHandlerTest to use PHPUnit mocks 2022-10-21 19:25:29 +02:00
Alejandro Celaya
230e56370a Migrated NotFoundRedirectHandlerTest to use PHPUnit mocks 2022-10-21 19:24:39 +02:00
Alejandro Celaya
a8514a9ae4 Migrated DomainServiceTest to use PHPUnit mocks 2022-10-21 19:01:41 +02:00
Alejandro Celaya
148f7a9cfe Migrated CrawlingHelperTest to use PHPUnit mocks 2022-10-21 18:49:47 +02:00
Alejandro Celaya
29d50cabc2 Migrated NotFoundRedirectResolverTest to use PHPUnit mocks 2022-10-21 18:47:10 +02:00
Alejandro Celaya
a8f8297131 Migrated RedirectActionTest to use PHPUnit mocks 2022-10-21 18:44:55 +02:00
Alejandro Celaya
cd4b632d75 Migrated QrActionTest to use PHPUnit mocks 2022-10-21 18:39:22 +02:00
Alejandro Celaya
843754b7e7 Migrated PixelActionTest to use PHPUnit mocks 2022-10-21 18:32:34 +02:00
Alejandro Celaya
847cc2bc50 Updated shlink-config 2022-10-19 14:19:03 +02:00
Alejandro Celaya
751bd15785 Fixed merge conflicts 2022-10-18 19:08:20 +02:00
Alejandro Celaya
c12db7567e Merge pull request #1577 from acelaya-forks/feature/fix-enum-hydration
Feature/fix enum hydration
2022-10-18 19:05:07 +02:00
Alejandro Celaya
e8069a10ba Updated changelog 2022-10-18 18:49:52 +02:00
Alejandro Celaya
9742bf13e4 Upgraded to latest doctrine/orm 2022-10-18 18:48:35 +02:00
Alejandro Celaya
6441707c76 Merge branch 'develop' of github.com:shlinkio/shlink into develop 2022-10-18 18:32:02 +02:00
Alejandro Celaya
23bcba4fd9 Updated shlink-ip-geolocation 2022-10-12 19:07:05 +02:00
Alejandro Celaya
9049a205b7 Merge pull request #1570 from acelaya-forks/feature/phpunit-mocks
Migrated to PHPUnit mocks in RobotsActionTest
2022-10-12 18:56:24 +02:00
Alejandro Celaya
8cfa0b595c Migrated to PHPUnit mocks in RobotsActionTest 2022-10-12 18:23:36 +02:00
Alejandro Celaya
4b958e8b87 Merge pull request #1568 from acelaya-forks/feature/phpunit-mocks-experiment
Used PHPUnit mocks in RoleResolverTest instead of prophezy
2022-10-12 12:55:34 +02:00
Alejandro Celaya
bcd5d2848d Used PHPUnit mocks in RoleResolverTest instead of prophezy 2022-10-12 12:47:58 +02:00
Alejandro Celaya
b59cbeceac Updated deps 2022-10-12 08:49:58 +02:00
Alejandro Celaya
46f948a584 Merge pull request #1565 from acelaya-forks/feature/command-reusable-args
Feature/command reusable args
2022-10-06 21:38:19 +02:00
Alejandro Celaya
14bf3a134b Updated changelog 2022-10-06 21:30:23 +02:00
Alejandro Celaya
1557438fdf Moved logic to reuse command options to option classes instead of base abstract command classes 2022-10-06 21:29:27 +02:00
Alejandro Celaya
27b680e0cd Created CLI test for short URLs list 2022-10-06 21:01:11 +02:00
Alejandro Celaya
14314ef939 Updated shlink deps 2022-10-06 19:49:32 +02:00
Alejandro Celaya
bf5c168d7d Merge pull request #1560 from acelaya-forks/feature/openswoole-4.12
Updated to openswoole 4.12
2022-10-03 20:26:25 +02:00
Alejandro Celaya
1e0791416d Downgraded openswoole ide helper 2022-10-03 20:05:43 +02:00
Alejandro Celaya
ab8d42b609 Updated to openswoole 4.12 in main Dockerfile 2022-10-03 20:01:46 +02:00
Alejandro Celaya
96dbdbe7c9 Updated to openswoole 4.12 2022-10-03 20:00:31 +02:00
Alejandro Celaya
6f135ad6ab Fixed typo 2022-09-30 17:45:36 +02:00
Alejandro Celaya
5b9a1e1978 Merge pull request #1559 from shlinkio/develop
Release 3.3.1
2022-09-30 17:37:29 +02:00
Alejandro Celaya
4ba3522e79 Merge pull request #1558 from acelaya-forks/feature/multisegment-trailing-slash
Feature/multisegment trailing slash
2022-09-30 17:35:01 +02:00
Alejandro Celaya
d3faa22b78 Fixed usage of enum where the enum's value should be used 2022-09-30 17:26:22 +02:00
Alejandro Celaya
1daad334a5 Updated changelog 2022-09-30 17:21:27 +02:00
Alejandro Celaya
3dda49dab4 Created middleware which ensures trailing slash and multi-segment features work properly together 2022-09-30 17:19:07 +02:00
Alejandro Celaya
c6c4e5580b Merge pull request #1554 from acelaya-forks/feature/php-8.2
Added PHP 8.2 to build matrixes
2022-09-24 08:26:54 +02:00
Alejandro Celaya
3f808e3813 Updated changelog 2022-09-24 08:17:14 +02:00
Alejandro Celaya
e5107c40f9 Ignored platform req during roadrunner API tests in CI workflow when using PHP 8.2 2022-09-24 08:14:59 +02:00
Alejandro Celaya
0871ca884e Fixed typo 2022-09-24 08:06:41 +02:00
Alejandro Celaya
62ce9311bf Added PHP 8.2 to build matrixes 2022-09-24 08:03:38 +02:00
Alejandro Celaya
70b15a7ab0 Merge pull request #1553 from acelaya-forks/feature/organize-namespaces
Feature/organize namespaces
2022-09-24 07:35:53 +02:00
Alejandro Celaya
708bff20f0 Updated changelog 2022-09-23 19:09:38 +02:00
Alejandro Celaya
369628ee95 Migrated infection config files to json5 2022-09-23 19:08:54 +02:00
Alejandro Celaya
0c6f8f1136 Refactored global entities into their own proper namespaces 2022-09-23 19:03:32 +02:00
Alejandro Celaya
9f9d011d46 Moved ShortCodeUniquenessHelper to ShortUrl\Helper namespace 2022-09-23 18:46:51 +02:00
Alejandro Celaya
e28b73c130 Refactored global services into their own proper namespaces 2022-09-23 18:42:38 +02:00
Alejandro Celaya
56f953ab2f Refactored global validations into their own proper namespaces 2022-09-23 18:30:07 +02:00
Alejandro Celaya
3ad8be175c Refactored global repositories into their own proper namespaces 2022-09-23 18:24:14 +02:00
Alejandro Celaya
f5f990511c Refactored global models into their own proper namespaces 2022-09-23 18:05:17 +02:00
Alejandro Celaya
1e3ccba503 Merge pull request #1552 from acelaya-forks/feature/visit-geolocation-namespace
Feature/visit geolocation namespace
2022-09-23 15:07:42 +02:00
Alejandro Celaya
a842b5b7cd Updated changelog 2022-09-23 14:58:00 +02:00
Alejandro Celaya
909e42b0be Moved services related to geolocating visits to the Visit\Geolocation namespace 2022-09-23 14:50:26 +02:00
Alejandro Celaya
c8acb5de68 Merge pull request #1549 from shlinkio/develop
Release 3.3.0
2022-09-18 19:36:55 +02:00
Alejandro Celaya
53b9e3ddc1 Merge pull request #1548 from acelaya-forks/feature/deferred-geolite-download
Feature/deferred geolite download
2022-09-18 19:35:00 +02:00
Alejandro Celaya
68e1c61e7f Removed unnecessary ADR entry 2022-09-18 19:28:14 +02:00
Alejandro Celaya
8605b35b57 Removed unneeded injected dependency 2022-09-18 19:22:57 +02:00
Alejandro Celaya
36680e82aa Reduced duplication in LocateVisitsCommand by reusing VisitToLocationHelper 2022-09-18 19:21:59 +02:00
Alejandro Celaya
83b7d5a5f1 Extracted logic to geolocate a visit, handling possible domain errors 2022-09-18 18:44:01 +02:00
Alejandro Celaya
fe41e9d573 Updated changelog 2022-09-18 17:12:21 +02:00
Alejandro Celaya
d76e6647d2 Added real version for composer dependencies 2022-09-18 17:10:11 +02:00
Alejandro Celaya
6f17f70137 Allowed to delay GeoLite2 db download on docker images 2022-09-18 17:00:03 +02:00
Alejandro Celaya
ef01754ad5 Added event dispatching to UpdateGeoLiteDb dispatcher so that it locates visits when file has just been created 2022-09-18 11:17:17 +02:00
Alejandro Celaya
eab9347522 Created enum to determine what was the result of updating a geolite DB 2022-09-18 10:31:14 +02:00
Alejandro Celaya
59bcd62717 Moved Geolocation services to its own namespace inside CLI module 2022-09-18 10:01:22 +02:00
Alejandro Celaya
3f01fad12f Ensured empty initial PAI keys are discarded 2022-09-18 09:29:38 +02:00
Alejandro Celaya
c7f0d14c1b Merge pull request #1546 from acelaya-forks/feature/immutable-options
Feature/immutable options
2022-09-17 16:02:36 +02:00
Alejandro Celaya
2408829627 Updated changelog 2022-09-17 15:55:54 +02:00
Alejandro Celaya
8d244c8d34 Migrated UrlShortenerOptions to immutable object 2022-09-17 15:54:43 +02:00
Alejandro Celaya
42af057316 Migrated RedirectOptions to immutable object 2022-09-17 15:36:40 +02:00
Alejandro Celaya
8f68078835 Migrated RabbitMqOptions to immutable object 2022-09-17 13:56:59 +02:00
Alejandro Celaya
0c34032fd3 Migrated QrCodeOptions to immutable object 2022-09-17 13:45:09 +02:00
Alejandro Celaya
20f457a3e9 Migrated NotFoundRedirectOptions to immutable object 2022-09-17 13:32:40 +02:00
Alejandro Celaya
39693ca1fe Added --thread=max to infection command 2022-09-17 13:19:17 +02:00
Alejandro Celaya
784908420e Migrated DeleteShortUrlsOptions to immutable object 2022-09-17 13:04:49 +02:00
Alejandro Celaya
9685929824 Migrated AppOptions to immutable object 2022-09-17 13:01:28 +02:00
Alejandro Celaya
fe4b2c4ae4 Migrated TrackingOptions to immutable object 2022-09-17 12:57:04 +02:00
Alejandro Celaya
5f87bb13f8 Fixed tracking config 2022-09-17 11:27:56 +02:00
Alejandro Celaya
a87f6c6709 Merge pull request #1541 from acelaya-forks/feature/initial-api-key
Feature/initial api key
2022-09-11 13:23:44 +02:00
Alejandro Celaya
da3ee6b65e Updated installer with support for API key generation 2022-09-11 13:14:36 +02:00
Alejandro Celaya
c5eda37bda Updated changelog 2022-09-11 12:36:47 +02:00
Alejandro Celaya
1966367caf Fixed ApiKeyRepository for MS and Postgres 2022-09-11 12:33:28 +02:00
Alejandro Celaya
eed7b6e565 Added db test for ApiKeyRepository 2022-09-11 12:18:04 +02:00
Alejandro Celaya
0e54ed691d Created InitialApiKeyDelegatorTest 2022-09-11 12:11:39 +02:00
Alejandro Celaya
997289da02 Changed all public setUp methods in tests to be protected 2022-09-11 12:02:49 +02:00
Alejandro Celaya
c841e57db5 Reduced duplication in ApiKeyRepository 2022-09-11 11:59:49 +02:00
Alejandro Celaya
f5138385be Created new env var to programatically provide an initial API key 2022-09-11 10:45:03 +02:00
Alejandro Celaya
63ceba199d Removed mention to improvement on mutation tests in changelog 2022-09-11 09:03:25 +02:00
Alejandro Celaya
e6ee4ceae2 Simplified mapping of TagInfo objects 2022-09-08 20:50:11 +02:00
Alejandro Celaya
19a9d815eb Merge pull request #1538 from acelaya-forks/feature/doctrine-cli
Feature/doctrine cli
2022-09-08 17:42:38 +02:00
Alejandro Celaya
5b78b363f0 Updated changelog 2022-09-08 14:11:01 +02:00
Alejandro Celaya
b078c00492 Migrated to custom doctrine cli entry point, as the built-in is deprecated 2022-09-08 14:10:09 +02:00
Alejandro Celaya
e712efd008 Simplified exception 2022-09-06 21:56:18 +02:00
Alejandro Celaya
ab27c0ce53 Merge pull request #1532 from acelaya-forks/feature/trailing-slash-support
Feature/trailing slash support
2022-09-05 21:21:09 +02:00
Alejandro Celaya
d97cabbe79 Updated changelog 2022-09-05 21:14:53 +02:00
Alejandro Celaya
c3c7ffad25 Updated to installer supporting trailing slash option 2022-09-05 21:12:05 +02:00
Alejandro Celaya
fe4329d730 Allowed trailing slashes support to be enabled for the short URLs route 2022-09-05 20:59:16 +02:00
Alejandro Celaya
c53ba7b119 Exported defaults for env vars in docker only when the runtime is RoadRunner 2022-09-03 17:02:57 +02:00
Alejandro Celaya
025eec6c70 Merge remote-tracking branch 'upstream/develop' into develop 2022-08-28 10:20:26 +02:00
Alejandro Celaya
40e1670314 Fixed default port in docker entry point 2022-08-28 10:18:02 +02:00
Alejandro Celaya
2bca260627 What're RoadRunner docker images is not tried to build for arm/v7 2022-08-27 21:49:20 +02:00
Alejandro Celaya
463d8e8950 Updated changelog 2022-08-27 19:51:59 +02:00
Alejandro Celaya
e2eed8a728 Merge pull request #1523 from acelaya-forks/feature/roadrunner-support
Feature/roadrunner support
2022-08-27 19:48:48 +02:00
Alejandro Celaya
f97effcfe0 Fixed rr E2E tests 2022-08-27 19:40:52 +02:00
Alejandro Celaya
2cf21ab3bd Fixed openswoole E2E tests 2022-08-27 19:38:05 +02:00
Alejandro Celaya
7daa602630 Removed accidental flag in build script 2022-08-27 17:51:14 +02:00
Alejandro Celaya
7b637d6a61 Ensured RoadRunner deps are removed when building openswoole dist file 2022-08-27 17:48:59 +02:00
Alejandro Celaya
a4f979be08 Enabled support for static files from public dir via RoadRunner 2022-08-27 17:27:54 +02:00
Alejandro Celaya
8852739111 Ensured some dynamic RR config for prod env, based on env vars 2022-08-27 17:17:37 +02:00
Alejandro Celaya
2099ea16ec Added stage to build docker images for roadrunner 2022-08-27 13:14:27 +02:00
Alejandro Celaya
a739eb6d60 Added support to build the docker image with RoadRunner instead of openswoole 2022-08-27 13:01:38 +02:00
Alejandro Celaya
529ddacafe Removed usage of bash again from tests in CI, as it does nothing really 2022-08-27 09:54:15 +02:00
Alejandro Celaya
f71c95b74a Another attempt to make API tests script sh compatible 2022-08-27 09:49:32 +02:00
Alejandro Celaya
8260a0843b Undone changes for sh on API tests 2022-08-27 09:43:20 +02:00
Alejandro Celaya
bfbeb7b1fb Improved run-api-tests.sh script to make it compatible with sh 2022-08-27 09:36:37 +02:00
Alejandro Celaya
df70810aa6 Ensured tests are run in bash in CI 2022-08-27 09:23:45 +02:00
Alejandro Celaya
aca5804f98 Fixed usage of inputs instead of matrix on CI workflow 2022-08-27 09:21:17 +02:00
Alejandro Celaya
b7f7288a4b Fixed reference to unknown job in CI workflow 2022-08-27 09:19:55 +02:00
Alejandro Celaya
d54a2bde0f Fixed reference to unknown job in CI workflow 2022-08-27 09:18:46 +02:00
Alejandro Celaya
679bb8d357 Added API tests over roadrunner on CI 2022-08-27 09:15:58 +02:00
Alejandro Celaya
ca515998e4 Added support to run API tests on roadrunner 2022-08-27 09:09:14 +02:00
Alejandro Celaya
c5b6d203f5 Simplified RoadRunner worker, and fixed RoadRunner reloading config 2022-08-27 08:01:57 +02:00
Alejandro Celaya
86159c5d86 Updated to latest event dispatcher lib 2022-08-26 19:17:10 +02:00
Alejandro Celaya
846802c003 Slight improvements on RoadRunner config 2022-08-26 17:58:25 +02:00
Alejandro Celaya
e9ec32b3c3 Added support to dispatch async event listeners as RoadRunner jobs 2022-08-26 14:59:27 +02:00
Alejandro Celaya
4882bec118 Added roadrunner to the project 2022-08-21 13:19:27 +02:00
Alejandro Celaya
89ff259be0 Merge pull request #1517 from acelaya-forks/feature/reusable-docker-build
Moved to docker build on reusable workflow
2022-08-19 15:18:20 +02:00
Alejandro Celaya
60ece7fbf7 Moved to docker build on reusable workflow 2022-08-19 15:11:46 +02:00
Alejandro Celaya
0c110f574a Merge pull request #1516 from acelaya-forks/feature/reduce-ci-artifact-download
Ensured every mutation test job only downloads the specific coverage …
2022-08-17 19:27:52 +02:00
Alejandro Celaya
dbca5b2a7e Ensured every mutation test job only downloads the specific coverage report 2022-08-17 19:16:33 +02:00
Alejandro Celaya
3088298e6b Merge pull request #1515 from acelaya-forks/feature/reusable-docker-build
Migrated docker build to a reusable workflow
2022-08-14 18:35:59 +02:00
Alejandro Celaya
a9c6a12182 Migrated docker build to a reusable workflow 2022-08-14 17:36:58 +02:00
Alejandro Celaya
fa5b512629 Merge pull request #1514 from acelaya-forks/feature/ghcr-support
Feature/ghcr support
2022-08-14 17:12:56 +02:00
Alejandro Celaya
5c2061a6e6 Updated changelog 2022-08-14 17:07:07 +02:00
Alejandro Celaya
cf0fc956c9 Added publishing of the docker image in GHCR 2022-08-14 17:05:13 +02:00
Alejandro Celaya
a0517dfbeb Merge pull request #1512 from acelaya-forks/feature/api-v3
Feature/api v3
2022-08-14 14:07:32 +02:00
Alejandro Celaya
39c71638e6 Updated changelog 2022-08-14 14:02:09 +02:00
Alejandro Celaya
672b728379 Updated swagger docs, with new API v3 error types 2022-08-14 13:55:43 +02:00
Alejandro Celaya
750a546faf Disabled mutation tests filtering until it properly works 2022-08-14 13:18:29 +02:00
Alejandro Celaya
a41835573b Centralized prefix for problem detail types 2022-08-14 13:12:10 +02:00
Alejandro Celaya
2650cb89b5 Created BackwardsCompatibleProblemDetailsExceptionTest 2022-08-14 12:39:05 +02:00
Alejandro Celaya
4a122e0209 Added remaining API tests covering error type convertions 2022-08-14 10:51:12 +02:00
Alejandro Celaya
ce4bf62d75 Added more granular resolution of arguments for infection based on branch 2022-08-14 10:34:27 +02:00
Alejandro Celaya
40bbcb3250 Added some API tests for v3 API errors 2022-08-13 17:49:00 +02:00
Alejandro Celaya
905f51fbd0 Added logic to properly map all existing errors from v3 to v2 in the API 2022-08-13 17:15:04 +02:00
Alejandro Celaya
cd4fe4362b Created middleware to keep backwards compatibility on errors when using v1 and 2 of the API 2022-08-13 16:50:19 +02:00
Alejandro Celaya
ed7be6eb99 Updated changelog 2022-08-13 12:37:15 +02:00
Alejandro Celaya
555007ab16 Merge pull request #1511 from acelaya-forks/feature/only-changed-mutants
Ensured only mutants for changed lines are executed in CI mutation tests
2022-08-13 12:34:52 +02:00
Alejandro Celaya
bd31b99324 Ensured only mutants for changed lines are executed in CI mutation tests 2022-08-13 12:31:12 +02:00
Alejandro Celaya
60237c3c0b Merge pull request #1509 from acelaya-forks/feature/local-composed-action
Extracted all steps for setting up to a reusable action
2022-08-13 12:02:28 +02:00
Alejandro Celaya
eb21833d94 Used ci-setup composite action as much as possible in ci workflow 2022-08-13 11:56:46 +02:00
Alejandro Celaya
763002ae14 Fixed typo when reading etxnesions input on ci-setup action 2022-08-13 11:18:50 +02:00
Alejandro Celaya
ae2dc39a78 Fixed ci-setup local composite action 2022-08-13 11:16:33 +02:00
Alejandro Celaya
fe4ced2709 Moved checkout step back to workflow 2022-08-13 11:06:41 +02:00
Alejandro Celaya
9075d68b7c Fixed reference to local composed action 2022-08-13 11:03:40 +02:00
Alejandro Celaya
759c0ea957 Extracted all steps for setting up to a reusable action 2022-08-13 10:53:24 +02:00
Alejandro Celaya
67b393d4a3 Merge pull request #1508 from acelaya-forks/feature/improve-cache-key-ci
Improved cache keys for extensions in CI workflow to support several …
2022-08-13 10:27:13 +02:00
Alejandro Celaya
de71821759 Updated to latest actions/checkout version 2022-08-13 10:19:38 +02:00
Alejandro Celaya
0c2bcaee34 Fixed typo 2022-08-13 10:12:52 +02:00
Alejandro Celaya
1613975e0e Improved cache keys for extensions in CI workflow to support several PHP versions when needed 2022-08-13 10:11:41 +02:00
Alejandro Celaya
be82204df2 Merge pull request #1507 from acelaya-forks/feature/improve-ci-uploads
Feature/improve ci uploads
2022-08-12 20:52:50 +02:00
Alejandro Celaya
14c2ff5545 Ensured unique cache key 2022-08-12 20:35:59 +02:00
Alejandro Celaya
d7d0e11f2c Added cache for PHP extensions in CI pipeline 2022-08-12 20:32:16 +02:00
Alejandro Celaya
6654f45cb8 Updated upload/download artifact actions 2022-08-12 18:19:12 +02:00
Alejandro Celaya
23f92179ad Optimized how and when code coverage reports are generated for different types of tests 2022-08-12 18:10:45 +02:00
Alejandro Celaya
7377917642 Merge pull request #1506 from acelaya-forks/feature/local-reusable-workflows
Extracted definition of unit tests job to local reusable workflow
2022-08-12 09:52:01 +02:00
Alejandro Celaya
0f796859f2 Fixed typo in ci workflow 2022-08-12 09:32:30 +02:00
Alejandro Celaya
6383230678 Extracted DB tests and mutation tests to reusable workflows 2022-08-12 09:30:52 +02:00
Alejandro Celaya
51536f8746 Moved reusable ci tests workflow to workflows folder 2022-08-12 09:13:04 +02:00
Alejandro Celaya
e3b6c061c4 Extracted definition of unit tests job to local reusable workflow 2022-08-12 08:35:10 +02:00
Alejandro Celaya
4bd3fa74d1 Merge pull request #1502 from acelaya-forks/feature/cli-tests
Feature/cli tests
2022-08-10 17:39:27 +02:00
Alejandro Celaya
71553988d5 Added cli mutation tests to pipeline, and referenced CLI tests in CONTRIBUTING file 2022-08-10 17:21:55 +02:00
Alejandro Celaya
761b24e614 Added CLI tests to to CI pipeline 2022-08-10 17:13:21 +02:00
Alejandro Celaya
10974902b5 Updated changelog 2022-08-10 17:09:54 +02:00
Alejandro Celaya
474407dbc2 Ensured proper coverage is generated during CLI tests 2022-08-10 17:08:42 +02:00
Alejandro Celaya
95d84f354d Simplified tests config 2022-08-09 19:48:43 +02:00
Alejandro Celaya
db47a9a253 Added mutation tests for CLI E2E tests 2022-08-09 19:15:49 +02:00
Alejandro Celaya
709a4639b3 Fixed merge conflicts 2022-08-09 18:59:55 +02:00
Alejandro Celaya
4d082a87a1 Added preliminary config to export coverage for CLI tests 2022-02-27 08:11:33 +01:00
Alejandro Celaya
1b6512fc8d Replaced deprecated transactional function with wrapTransaction 2022-02-27 08:10:18 +01:00
Alejandro Celaya
9e32886f60 Created first CLI E2E tests 2022-02-13 12:20:02 +01:00
506 changed files with 11363 additions and 7652 deletions

View File

@@ -1,3 +1,4 @@
bin/rr
config/autoload/*local*
data/infra
data/cache/*
@@ -22,4 +23,4 @@ infection*
**/test*
build*
**/.*
bin/helper
!config/roadrunner/.rr.yml

47
.github/actions/ci-setup/action.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: CI setup
description: 'Sets up the environment to run CI actions for Shlink'
inputs:
install-deps:
description: 'Tells if dependencies should be installed with composer. Default value is "yes"'
required: true
default: 'yes'
php-version:
description: 'The PHP version to be setup'
required: true
php-extensions:
description: 'The PHP extensions to install'
required: false
default: ''
extensions-cache-key:
description: 'The key used to cache PHP extensions. If empty value is provided, extension caching is disabled'
required: true
runs:
using: composite
steps:
- name: Setup cache environment
id: extcache
uses: shivammathur/cache-extensions@v1
with:
php-version: ${{ inputs.php-version }}
extensions: ${{ inputs.php-extensions }}
key: ${{ inputs.extensions-cache-key }}
- name: Cache extensions
uses: actions/cache@v2
with:
path: ${{ steps.extcache.outputs.dir }}
key: ${{ steps.extcache.outputs.key }}
restore-keys: ${{ steps.extcache.outputs.key }}
- name: Use PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ inputs.php-version }}
tools: composer
extensions: ${{ inputs.php-extensions }}
coverage: pcov
ini-values: pcov.directory=module
- name: Install dependencies
if: ${{ inputs.install-deps == 'yes' }}
run: composer install --no-interaction --prefer-dist
shell: bash

44
.github/workflows/ci-db-tests.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Database tests
on:
workflow_call:
inputs:
platform:
type: string
required: true
description: One of sqlite:ci, mysql, maria, postgres or ms
jobs:
db-tests:
runs-on: ubuntu-22.04
strategy:
matrix:
php-version: ['8.1', '8.2']
env:
LC_ALL: C
steps:
- uses: actions/checkout@v3
- name: Install MSSQL ODBC
if: ${{ inputs.platform == 'ms' }}
run: sudo ./data/infra/ci/install-ms-odbc.sh
- name: Start database server
if: ${{ inputs.platform != 'sqlite:ci' }}
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_${{ inputs.platform }}
- uses: './.github/actions/ci-setup'
with:
php-version: ${{ matrix.php-version }}
php-extensions: openswoole-4.12.1, pdo_sqlsrv-5.10.1
extensions-cache-key: db-tests-extensions-${{ matrix.php-version }}-${{ inputs.platform }}
- name: Create test database
if: ${{ inputs.platform == 'ms' }}
run: docker-compose exec -T shlink_db_ms /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Passw0rd!' -Q "CREATE DATABASE shlink_test;"
- name: Run tests
run: composer test:db:${{ inputs.platform }}
- name: Upload code coverage
uses: actions/upload-artifact@v3
if: ${{ matrix.php-version == '8.1' && inputs.platform == 'sqlite:ci' }}
with:
name: coverage-db
path: |
build/coverage-db
build/coverage-db.cov

View File

@@ -0,0 +1,14 @@
name: Build docker image
on:
pull_request:
paths:
- 'Dockerfile'
jobs:
build-docker-image:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v3
- run: docker build -t shlink-docker-image:temp .

45
.github/workflows/ci-mutation-tests.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Mutation tests
on:
workflow_call:
inputs:
test-group:
type: string
required: true
description: One of unit, db, api or cli
jobs:
mutation-tests:
runs-on: ubuntu-22.04
strategy:
matrix:
php-version: ['8.1', '8.2']
steps:
- uses: actions/checkout@v3
- uses: './.github/actions/ci-setup'
with:
php-version: ${{ matrix.php-version }}
php-extensions: openswoole-4.12.1
extensions-cache-key: mutation-tests-extensions-${{ matrix.php-version }}-${{ inputs.test-group }}
- uses: actions/download-artifact@v3
with:
name: coverage-${{ inputs.test-group }}
path: build
- name: Resolve infection args
id: infection_args
run: echo "::set-output name=args::--logger-github=false"
# TODO Try to filter mutation tests to improve execution times. Investigate why --git-diff-lines --git-diff-base=develop does not work
# run: |
# BRANCH="${GITHUB_REF#refs/heads/}" |
# if [[ $BRANCH == 'main' || $BRANCH == 'develop' ]]; then
# echo "::set-output name=args::--logger-github=false"
# else
# echo "::set-output name=args::--logger-github=false --git-diff-lines --git-diff-base=develop"
# fi;
shell: bash
- if: ${{ inputs.test-group == 'unit' }}
run: composer infect:ci:unit -- ${{ steps.infection_args.outputs.args }}
env:
INFECTION_BADGE_API_KEY: ${{ secrets.INFECTION_BADGE_API_KEY }}
- if: ${{ inputs.test-group != 'unit' }}
run: composer infect:ci:${{ inputs.test-group }} -- ${{ steps.infection_args.outputs.args }}

37
.github/workflows/ci-tests.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Tests
on:
workflow_call:
inputs:
test-group:
type: string
required: true
description: One of unit, api or cli
jobs:
tests:
runs-on: ubuntu-22.04
strategy:
matrix:
php-version: ['8.1', '8.2']
steps:
- uses: actions/checkout@v3
- name: Start postgres database server
if: ${{ inputs.test-group == 'api' }}
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
- name: Start maria database server
if: ${{ inputs.test-group == 'cli' }}
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_maria
- uses: './.github/actions/ci-setup'
with:
php-version: ${{ matrix.php-version }}
php-extensions: openswoole-4.12.1
extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ inputs.test-group }}
- run: composer test:${{ inputs.test-group }}:ci
- uses: actions/upload-artifact@v3
if: ${{ matrix.php-version == '8.1' }}
with:
name: coverage-${{ inputs.test-group }}
path: |
build/coverage-${{ inputs.test-group }}
build/coverage-${{ inputs.test-group }}.cov

View File

@@ -1,12 +1,28 @@
name: Continuous integration
on:
pull_request: null
pull_request:
paths-ignore:
- 'LICENSE'
- '.*'
- '*.md'
- '*.xml'
- '*.yml*'
- '*.json5'
- '*.neon'
push:
branches:
- main
- develop
- 2.x
paths-ignore:
- 'LICENSE'
- '.*'
- '*.md'
- '*.xml'
- '*.yml*'
- '*.json5'
- '*.neon'
jobs:
static-analysis:
@@ -16,146 +32,126 @@ jobs:
php-version: ['8.1']
command: ['cs', 'stan', 'swagger:validate']
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Use PHP
uses: shivammathur/setup-php@v2
- uses: actions/checkout@v3
- uses: './.github/actions/ci-setup'
with:
php-version: ${{ matrix.php-version }}
tools: composer
extensions: openswoole-4.11.1
coverage: none
- name: Install dependencies
run: composer install --no-interaction --prefer-dist
php-extensions: openswoole-4.12.1
extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ matrix.command }}
- run: composer ${{ matrix.command }}
tests:
runs-on: ubuntu-22.04
strategy:
matrix:
php-version: ['8.1']
test-group: ['unit', 'api']
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Start database server
if: ${{ matrix.test-group == 'api' }}
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
- name: Use PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
tools: composer
extensions: openswoole-4.11.1
coverage: pcov
ini-values: pcov.directory=module
- name: Install dependencies
run: composer install --no-interaction --prefer-dist
- run: composer test:${{ matrix.test-group }}:ci
- uses: actions/upload-artifact@v2
if: ${{ matrix.php-version == '8.1' }}
with:
name: coverage-${{ matrix.test-group }}
path: |
build/coverage-${{ matrix.test-group }}
build/coverage-${{ matrix.test-group }}.cov
unit-tests:
uses: './.github/workflows/ci-tests.yml'
with:
test-group: unit
db-tests:
cli-tests:
uses: './.github/workflows/ci-tests.yml'
with:
test-group: cli
openswoole-api-tests:
uses: './.github/workflows/ci-tests.yml'
with:
test-group: api
roadrunner-api-tests:
runs-on: ubuntu-22.04
strategy:
matrix:
php-version: ['8.1']
platform: ['sqlite:ci', 'mysql', 'maria', 'postgres', 'ms']
php-version: ['8.1', '8.2']
env:
LC_ALL: C
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # rr get-binary picks this env automatically
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install MSSQL ODBC
if: ${{ matrix.platform == 'ms' }}
run: sudo ./data/infra/ci/install-ms-odbc.sh
- name: Start database server
if: ${{ matrix.platform != 'sqlite:ci' }}
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_${{ matrix.platform }}
- name: Use PHP
uses: shivammathur/setup-php@v2
- uses: actions/checkout@v3
- run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
tools: composer
extensions: openswoole-4.11.1, pdo_sqlsrv-5.10.1
coverage: pcov
ini-values: pcov.directory=module
- name: Install dependencies
run: composer install --no-interaction --prefer-dist
- name: Create test database
if: ${{ matrix.platform == 'ms' }}
run: docker-compose exec -T shlink_db_ms /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Passw0rd!' -Q "CREATE DATABASE shlink_test;"
- name: Run tests
run: composer test:db:${{ matrix.platform }}
- name: Upload code coverage
uses: actions/upload-artifact@v2
if: ${{ matrix.php-version == '8.1' && matrix.platform == 'sqlite:ci' }}
with:
name: coverage-db
path: |
build/coverage-db
build/coverage-db.cov
- run: composer install --no-interaction --prefer-dist
- run: ./vendor/bin/rr get --no-interaction --location bin/ && chmod +x bin/rr
- run: composer test:api:rr
mutation-tests:
sqlite-db-tests:
uses: './.github/workflows/ci-db-tests.yml'
with:
platform: 'sqlite:ci'
mysql-db-tests:
uses: './.github/workflows/ci-db-tests.yml'
with:
platform: 'mysql'
maria-db-tests:
uses: './.github/workflows/ci-db-tests.yml'
with:
platform: 'maria'
postgres-db-tests:
uses: './.github/workflows/ci-db-tests.yml'
with:
platform: 'postgres'
ms-db-tests:
uses: './.github/workflows/ci-db-tests.yml'
with:
platform: 'ms'
unit-mutation-tests:
needs:
- tests
- db-tests
runs-on: ubuntu-22.04
strategy:
matrix:
php-version: ['8.1']
test-group: ['unit', 'db', 'api']
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Use PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
tools: composer
extensions: openswoole-4.11.1
coverage: pcov
ini-values: pcov.directory=module
- name: Install dependencies
run: composer install --no-interaction --prefer-dist
- uses: actions/download-artifact@v2
with:
path: build
- if: ${{ matrix.test-group == 'unit' }}
run: composer infect:ci:unit
env:
INFECTION_BADGE_API_KEY: ${{ secrets.INFECTION_BADGE_API_KEY }}
- if: ${{ matrix.test-group != 'unit' }}
run: composer infect:ci:${{ matrix.test-group }}
- unit-tests
uses: './.github/workflows/ci-mutation-tests.yml'
with:
test-group: unit
db-mutation-tests:
needs:
- sqlite-db-tests
uses: './.github/workflows/ci-mutation-tests.yml'
with:
test-group: db
api-mutation-tests:
needs:
- openswoole-api-tests
uses: './.github/workflows/ci-mutation-tests.yml'
with:
test-group: api
cli-mutation-tests:
needs:
- cli-tests
uses: './.github/workflows/ci-mutation-tests.yml'
with:
test-group: cli
upload-coverage:
needs:
- tests
- db-tests
- unit-tests
- openswoole-api-tests
- cli-tests
- sqlite-db-tests
runs-on: ubuntu-22.04
strategy:
matrix:
php-version: ['8.1']
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Use PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
coverage: pcov
ini-values: pcov.directory=module
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v3
with:
path: build
- run: mv build/coverage-unit/coverage-unit.cov build/coverage-unit.cov
- run: mv build/coverage-db/coverage-db.cov build/coverage-db.cov
- run: mv build/coverage-api/coverage-api.cov build/coverage-api.cov
- run: mv build/coverage-cli/coverage-cli.cov build/coverage-cli.cov
- run: wget https://phar.phpunit.de/phpcov-8.2.1.phar
- run: php phpcov-8.2.1.phar merge build --clover build/clover.xml
- name: Publish coverage
@@ -165,7 +161,10 @@ jobs:
delete-artifacts:
needs:
- mutation-tests
- unit-mutation-tests
- db-mutation-tests
- api-mutation-tests
- cli-mutation-tests
- upload-coverage
runs-on: ubuntu-22.04
steps:
@@ -175,19 +174,4 @@ jobs:
coverage-unit
coverage-db
coverage-api
build-docker-image:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
fetch-depth: 100
- uses: marceloprado/has-changed-path@v1
id: changed-dockerfile
with:
paths: ./Dockerfile
- if: ${{ steps.changed-dockerfile.outputs.changed == 'true' }}
run: docker build -t shlink-docker-image:temp .
- if: ${{ steps.changed-dockerfile.outputs.changed != 'true' }}
run: echo "Dockerfile didn't change. Skipped"
coverage-cli

View File

@@ -1,28 +0,0 @@
name: Build docker image
on:
push:
branches:
- develop
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
version: latest
- name: Login to docker hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build the image
run: bash ./docker/build

View File

@@ -0,0 +1,35 @@
name: Build and publish docker image
on:
push:
branches:
- develop
paths-ignore:
- 'LICENSE'
- '.*'
- '*.md'
- '*.xml'
- '*.yml*'
- '*.json5'
- '*.neon'
tags:
- 'v*'
jobs:
build-openswoole:
uses: shlinkio/github-actions/.github/workflows/docker-build-and-publish.yml@main
secrets: inherit
with:
image-name: shlinkio/shlink
version-arg-name: SHLINK_VERSION
build-roadrunner:
uses: shlinkio/github-actions/.github/workflows/docker-build-and-publish.yml@main
secrets: inherit
with:
image-name: shlinkio/shlink
version-arg-name: SHLINK_VERSION
platforms: 'linux/arm64/v8,linux/amd64'
tags-suffix: roadrunner
extra-build-args: |
SHLINK_RUNTIME=rr

View File

@@ -10,22 +10,21 @@ jobs:
runs-on: ubuntu-22.04
strategy:
matrix:
php-version: ['8.1']
php-version: ['8.1', '8.2']
swoole: ['yes', 'no']
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Use PHP
uses: shivammathur/setup-php@v2
- uses: actions/checkout@v3
- uses: './.github/actions/ci-setup'
with:
php-version: ${{ matrix.php-version }}
tools: composer
extensions: openswoole-4.11.1
php-extensions: openswoole-4.12.1
extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }}
install-deps: 'no'
- if: ${{ matrix.swoole == 'yes' }}
run: ./build.sh ${GITHUB_REF#refs/tags/v}
- if: ${{ matrix.swoole == 'no' }}
run: ./build.sh ${GITHUB_REF#refs/tags/v} --no-swoole
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: dist-files-${{ matrix.php-version }}-${{ matrix.swoole }}
path: build
@@ -34,9 +33,8 @@ jobs:
needs: ['build']
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v2
- uses: actions/download-artifact@v2
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
with:
path: build
- name: Publish release with assets
@@ -53,7 +51,7 @@ jobs:
runs-on: ubuntu-22.04
strategy:
matrix:
php-version: ['8.1']
php-version: ['8.1', '8.2']
swoole: ['yes', 'no']
steps:
- uses: geekyeggo/delete-artifact@v1

View File

@@ -12,20 +12,16 @@ jobs:
matrix:
php-version: ['8.1']
steps:
- name: Checkout code
uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Determine version
id: determine_version
run: echo "::set-output name=version::${GITHUB_REF#refs/tags/}"
shell: bash
- name: Use PHP
uses: shivammathur/setup-php@v2
- uses: './.github/actions/ci-setup'
with:
php-version: ${{ matrix.php-version }}
tools: composer
extensions: openswoole-4.11.1
coverage: none
- run: composer install --no-interaction --prefer-dist
php-extensions: openswoole-4.12.1
extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }}
- run: composer swagger:inline
- run: mkdir ${{ steps.determine_version.outputs.version }}
- run: mv docs/swagger/swagger-inlined.json ${{ steps.determine_version.outputs.version }}/open-api-spec.json

3
.gitignore vendored
View File

@@ -1,4 +1,7 @@
.idea
bin/.rr.*
bin/rr
config/roadrunner/.pid
build
!docker/build
composer.lock

View File

@@ -4,6 +4,166 @@ 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).
## [3.5.0] - 2023-01-28
### Added
* [#1557](https://github.com/shlinkio/shlink/issues/1557) Added support to dynamically redirect to different long URLs based on the visitor's device type.
For the moment, only `android`, `ios` and `desktop` can have their own specific long URL, and when the visitor cannot be matched against any of them, the regular long URL will be used.
In the future, more granular device types could be added if appropriate (iOS tablet, android table, tablet, mobile phone, Linux, Mac, Windows, etc).
In order to match the visitor's device, the `User-Agent` header is used.
* [#1632](https://github.com/shlinkio/shlink/issues/1632) Added amount of bots, non-bots and total visits to the visits summary endpoint.
* [#1633](https://github.com/shlinkio/shlink/issues/1633) Added amount of bots, non-bots and total visits to the tag stats endpoint.
* [#1653](https://github.com/shlinkio/shlink/issues/1653) Added support for all HTTP methods in short URLs, together with two new redirect status codes, 307 and 308.
Existing Shlink instances will continue to work the same. However, if you decide to set the redirect status codes as 307 or 308, Shlink will also return a redirect for short URLs even when the request method is different from `GET`.
The status 308 is equivalent to 301, and 307 is equivalent to 302. The difference is that the spec requires the client to respect the original HTTP method when performing the redirect. With 301 and 302, some old clients might perform a `GET` request during the redirect, regardless the original request method.
* [#1662](https://github.com/shlinkio/shlink/issues/1662) Added support to provide openswoole-specific config options via env vars prefixed with `OPENSWOOLE_`.
* [#1389](https://github.com/shlinkio/shlink/issues/1389) and [#706](https://github.com/shlinkio/shlink/issues/706) Added support for case-insensitive short URLs.
In order to achieve this, a new env var/config option has been implemented (`SHORT_URL_MODE`), which allows either `strict` or `loosely`.
Default value is `strict`, but if `loosely` is provided, then short URLs will be matched in a case-insensitive way, and new short URLs will be generated with short-codes in lowercase only.
### Changed
* *Nothing*
### Deprecated
* [#1676](https://github.com/shlinkio/shlink/issues/1676) Deprecated `GET /short-urls/shorten` endpoint. Use `POST /short-urls` to create short URLs instead.
* [#1678](https://github.com/shlinkio/shlink/issues/1678) Deprecated `validateUrl` option on URL creation/edition.
### Removed
* *Nothing*
### Fixed
* [#1639](https://github.com/shlinkio/shlink/issues/1639) Fixed 500 error returned when request body is not valid JSON, instead of a proper descriptive error.
## [3.4.0] - 2022-12-16
### Added
* [#1612](https://github.com/shlinkio/shlink/issues/1612) Allowed to filter short URLs out of lists, when `validUntil` date is in the past or have reached their maximum amount of visits.
This can be done by:
* Providing `excludeMaxVisitsReached=true` and/or `excludePastValidUntil=true` to the `GET /short-urls` endpoint.
* Providing `--exclude-max-visits-reached` and/or `--exclude-past-valid-until` to the `short-urls:list` command.
* [#1613](https://github.com/shlinkio/shlink/issues/1613) Added amount of visits coming from bots, non-bots and total to every short URL in the short URLs list.
Additionally, added option to order by non-bot visits, by passing `nonBotVisits-DESC` or `nonBotVisits-ASC`.
* [#1599](https://github.com/shlinkio/shlink/issues/1599) Added support for credentials on redis DSNs, either only password, or both username and password.
* [#1616](https://github.com/shlinkio/shlink/issues/1616) Added support to import orphan visits when importing short URLs from another Shlink instance.
* [#1519](https://github.com/shlinkio/shlink/issues/1519) Allowing to search short URLs by default domain.
* [#1555](https://github.com/shlinkio/shlink/issues/1555) and [#1625](https://github.com/shlinkio/shlink/issues/1625) Added full support for PHP 8.2, updating the docker image to this version.
### Changed
* [#1563](https://github.com/shlinkio/shlink/issues/1563) Moved logic to reuse command options to option classes instead of base abstract command classes.
* [#1569](https://github.com/shlinkio/shlink/issues/1569) Migrated test doubles from phpspec/prophecy to PHPUnit mocks.
* [#1329](https://github.com/shlinkio/shlink/issues/1329) Split some logic from `VisitRepository` and `ShortUrlRepository` into separated repository classes.
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* [#1618](https://github.com/shlinkio/shlink/issues/1618) Fixed imported short URLs and visits dates not being set to the target server timezone.
* [#1578](https://github.com/shlinkio/shlink/issues/1578) Fixed short URL allowing an empty string as the domain during creation.
* [#1580](https://github.com/shlinkio/shlink/issues/1580) Fixed `FLUSHDB` being run on Shlink docker start-up when using redis, causing full cache to be flushed.
## [3.3.2] - 2022-10-18
### Added
* *Nothing*
### Changed
* *Nothing*
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* [#1576](https://github.com/shlinkio/shlink/issues/1576) Fixed error when trying to retry visits location from CLI.
## [3.3.1] - 2022-09-30
### Added
* *Nothing*
### Changed
* [#1474](https://github.com/shlinkio/shlink/issues/1474) Added preliminary support for PHP 8.2 during CI workflow.
* [#1551](https://github.com/shlinkio/shlink/issues/1551) Moved services related to geolocating visits to the `Visit\Geolocation` namespace.
* [#1550](https://github.com/shlinkio/shlink/issues/1550) Reorganized main namespaces from Core module.
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* [#1556](https://github.com/shlinkio/shlink/issues/1556) Fixed trailing slash not working when enabling multi-segment slashes.
## [3.3.0] - 2022-09-18
### Added
* [#1221](https://github.com/shlinkio/shlink/issues/1221) Added experimental support to run Shlink with [RoadRunner](https://roadrunner.dev) instead of openswoole.
* [#1531](https://github.com/shlinkio/shlink/issues/1531) and [#1090](https://github.com/shlinkio/shlink/issues/1090) Added support for trailing slashes in short URLs.
* [#1406](https://github.com/shlinkio/shlink/issues/1406) Added new REST API version 3.
When making requests to the REST API with `/rest/v3/...` and an error occurs, all error types will be different, with the next correlation:
* `INVALID_ARGUMENT` -> `https://shlink.io/api/error/invalid-data`
* `INVALID_SHORT_URL_DELETION` -> `https://shlink.io/api/error/invalid-short-url-deletion`
* `DOMAIN_NOT_FOUND` -> `https://shlink.io/api/error/domain-not-found`
* `FORBIDDEN_OPERATION` -> `https://shlink.io/api/error/forbidden-tag-operation`
* `INVALID_URL` -> `https://shlink.io/api/error/invalid-url`
* `INVALID_SLUG` -> `https://shlink.io/api/error/non-unique-slug`
* `INVALID_SHORTCODE` -> `https://shlink.io/api/error/short-url-not-found`
* `TAG_CONFLICT` -> `https://shlink.io/api/error/tag-conflict`
* `TAG_NOT_FOUND` -> `https://shlink.io/api/error/tag-not-found`
* `MERCURE_NOT_CONFIGURED` -> `https://shlink.io/api/error/mercure-not-configured`
* `INVALID_AUTHORIZATION` -> `https://shlink.io/api/error/missing-authentication`
* `INVALID_API_KEY` -> `https://shlink.io/api/error/invalid-api-key`
If you make a request to the API with v2 or v1, the old error types will be returned, until Shlink 4 is released, when only the new ones will be used.
Non-error responses are not affected.
* [#1513](https://github.com/shlinkio/shlink/issues/1513) Added publishing of the docker image in GHCR.
* [#1114](https://github.com/shlinkio/shlink/issues/1114) Added support to provide an initial API key via `INITIAL_API_KEY` env var, when running Shlink with openswoole or RoadRunner.
Also, the installer tool now allows to generate an initial API key that can be copy-pasted (this tool is run interactively), in case you use php-fpm or you don't want to use env vars.
* [#1528](https://github.com/shlinkio/shlink/issues/1528) Added support to delay when the GeoLite2 DB file is downloaded in docker images, speeding up its startup time.
In order to do it, pass `SKIP_INITIAL_GEOLITE_DOWNLOAD=true` when creating the container.
### Changed
* [#1339](https://github.com/shlinkio/shlink/issues/1339) Added new test suite for CLI E2E tests.
* [#1503](https://github.com/shlinkio/shlink/issues/1503) Drastically improved build time in GitHub Actions, by optimizing parallelization and adding php extensions cache.
* [#1525](https://github.com/shlinkio/shlink/issues/1525) Migrated to custom doctrine CLI entry point.
* [#1492](https://github.com/shlinkio/shlink/issues/1492) Migrated to immutable options objects, mapped with [cuyz/valinor](https://github.com/CuyZ/Valinor).
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* *Nothing*
## [3.2.1] - 2022-08-08
### Added
* *Nothing*
@@ -1307,7 +1467,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
Endpoints and commands which create short URLs support providing the `domain` now (via query param or CLI flag). If not provided, the short URLs will still be "attached" to the default domain.
Custom slugs can be created on multiple domains, allowing to share links like `https://doma.in/my-campaign` and `https://example.com/my-campaign`, under the same shlink instance.
Custom slugs can be created on multiple domains, allowing to share links like `https://s.test/my-campaign` and `https://example.com/my-campaign`, under the same shlink instance.
When resolving a short URL to redirect end users, the following rules are applied:
@@ -1770,7 +1930,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
```json
{
"shortCode": "12Kb3",
"shortUrl": "https://doma.in/12Kb3",
"shortUrl": "https://s.test/12Kb3",
"longUrl": "https://shlink.io",
"dateCreated": "2016-05-01T20:34:16+02:00",
"visitsCount": 1029,
@@ -1837,7 +1997,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
* [#174](https://github.com/shlinkio/shlink/issues/174) Fixed geolocation not working due to a deprecation on used service.
* [#172](https://github.com/shlinkio/shlink/issues/172) Documented missing filtering params for `[GET] /short-codes/{shortCode}/visits` API endpoint, which allow the list to be filtered by date range.
For example: `https://doma.in/rest/v1/short-urls/abc123/visits?startDate=2017-05-23&endDate=2017-10-05`
For example: `https://s.test/rest/v1/short-urls/abc123/visits?startDate=2017-05-23&endDate=2017-10-05`
* [#169](https://github.com/shlinkio/shlink/issues/169) Fixed unhandled error when parsing `ShortUrlMeta` and date fields are already `DateTime` instances.
@@ -1909,7 +2069,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
This eases integration with third party services.
With this feature, a simple request to a URL like `https://doma.in/rest/v1/short-codes/shorten?apiKey=[YOUR_API_KEY]&longUrl=[URL_TO_BE_SHORTENED]` would return the shortened one in JSON or plain text format.
With this feature, a simple request to a URL like `https://s.test/rest/v1/short-codes/shorten?apiKey=[YOUR_API_KEY]&longUrl=[URL_TO_BE_SHORTENED]` would return the shortened one in JSON or plain text format.
### Changed
* *Nothing*
@@ -1945,7 +2105,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
### Added
* [#125](https://github.com/shlinkio/shlink/issues/125) Implemented a path which returns a 1px image instead of a redirection.
Useful to track emails. Just add an image pointing to a URL like `https://doma.in/abc123/track` to any email and an invisible image will be generated tracking every time the email is opened.
Useful to track emails. Just add an image pointing to a URL like `https://s.test/abc123/track` to any email and an invisible image will be generated tracking every time the email is opened.
* [#132](https://github.com/shlinkio/shlink/issues/132) Added infection to improve tests
@@ -2226,7 +2386,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
### Added
* [#46](https://github.com/shlinkio/shlink/issues/46) Defined a route that returns a QR code representing the shortened URL.
In order to get the QR code URL, use a pattern like `https://doma.in/abc123/qr-code`
In order to get the QR code URL, use a pattern like `https://s.test/abc123/qr-code`
* [#32](https://github.com/shlinkio/shlink/issues/32) Added support for other cache adapters by improving the Cache factory
* [#14](https://github.com/shlinkio/shlink/issues/14) Added logger and enabled errors logging

View File

@@ -102,7 +102,9 @@ In order to ensure stability and no regressions are introduced while developing
Since the app instance is run on a process different from the one running the tests, when a test fails it might not be obvious why. To help debugging that, the app will dump all its logs inside `data/log/api-tests`, where you will find the `shlink.log` and `access.log` files.
* **CLI tests**: *TBD. Once included, its purpose will be the same as API tests, but running through the command line*
* **CLI tests**: These are E2E tests too, but they test console commands instead of REST endpoints.
They use Maria DB as the database engine, and include the same fixtures as the API tests, that ensure the same data exists at the beginning of the execution.
Depending on the kind of contribution, maybe not all kinds of tests are needed, but the more you provide, the better.
@@ -119,9 +121,9 @@ Depending on the kind of contribution, maybe not all kinds of tests are needed,
For example, `test:db:postgres`.
* Run `./indocker composer test:api` to run API E2E tests. For these, the Postgres database engine is used.
* Run `./indocker composer test:cli` to run CLI E2E tests. For these, the Maria DB database engine is used.
* Run `./indocker composer infect:test` to run both unit and database tests (over sqlite) and then apply mutations to them with [infection](https://infection.github.io/).
* Run `./indocker composer ci` to run all previous commands together. This command is run during the project's continuous integration.
* Run `./indocker composer ci:parallel` to do the same as in previous case, but parallelizing non-conflicting tasks as much as possible.
* Run `./indocker composer ci` to run all previous commands together, parallelizing non-conflicting tasks as much as possible.
## Pull request process
@@ -133,7 +135,7 @@ Once everything is clear, to provide a pull request to this project, you should
The base branch should always be `develop`, and the target branch for the pull request should also be `develop`.
Before your branch can be merged, all the checks described in [Running code checks](#running-code-checks) have to be passing. You can verify that manually by running `./indocker composer ci:parallel`, or wait for the build to be run automatically after the pull request is created.
Before your branch can be merged, all the checks described in [Running code checks](#running-code-checks) have to be passing. You can verify that manually by running `./indocker composer ci`, or wait for the build to be run automatically after the pull request is created.
## Architectural Decision Records

View File

@@ -1,10 +1,13 @@
FROM php:8.1.9-alpine3.16 as base
FROM php:8.2-alpine3.17 as base
ARG SHLINK_VERSION=latest
ENV SHLINK_VERSION ${SHLINK_VERSION}
ENV OPENSWOOLE_VERSION 4.11.1
ARG SHLINK_RUNTIME=openswoole
ENV SHLINK_RUNTIME ${SHLINK_RUNTIME}
ENV OPENSWOOLE_VERSION 4.12.1
ENV PDO_SQLSRV_VERSION 5.10.1
ENV MS_ODBC_SQL_VERSION 17.5.2.2
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
ENV LC_ALL "C"
WORKDIR /etc/shlink
@@ -12,7 +15,7 @@ WORKDIR /etc/shlink
# Install required PHP extensions
RUN \
# Temp install dev dependencies needed to compile the extensions
apk add --no-cache --virtual .dev-deps sqlite-dev postgresql-dev icu-dev libzip-dev zlib-dev libpng-dev && \
apk add --no-cache --virtual .dev-deps sqlite-dev postgresql-dev icu-dev libzip-dev zlib-dev libpng-dev linux-headers && \
docker-php-ext-install -j"$(nproc)" pdo_mysql pdo_pgsql intl calendar sockets bcmath zip gd && \
apk add --no-cache sqlite-libs && \
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
@@ -22,14 +25,16 @@ RUN \
# Install openswoole and sqlsrv driver for x86_64 builds
RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} unixodbc-dev && \
pecl install openswoole-${OPENSWOOLE_VERSION} && \
docker-php-ext-enable openswoole && \
if [ "$SHLINK_RUNTIME" == 'openswoole' ]; then \
pecl install openswoole-${OPENSWOOLE_VERSION} && \
docker-php-ext-enable openswoole ; \
fi; \
if [ $(uname -m) == "x86_64" ]; then \
wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
apk add --no-cache --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} && \
docker-php-ext-enable pdo_sqlsrv && \
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk ; \
rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk ; \
fi; \
apk del .phpize-deps
@@ -38,7 +43,12 @@ FROM base as builder
COPY . .
COPY --from=composer:2 /usr/bin/composer ./composer.phar
RUN apk add --no-cache git && \
php composer.phar install --no-dev --optimize-autoloader --prefer-dist --no-progress --no-interaction && \
php composer.phar install --no-dev --prefer-dist --optimize-autoloader --no-progress --no-interaction && \
if [ "$SHLINK_RUNTIME" == 'openswoole' ]; then \
php composer.phar remove spiral/roadrunner spiral/roadrunner-jobs --with-all-dependencies --update-no-dev --optimize-autoloader --no-progress --no-interactionc ; \
elif [ $SHLINK_RUNTIME == 'rr' ]; then \
php composer.phar remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev --optimize-autoloader --no-progress --no-interaction ; \
fi; \
php composer.phar clear-cache && \
rm -r docker composer.* && \
sed -i "s/%SHLINK_VERSION%/${SHLINK_VERSION}/g" config/autoload/app_options.global.php
@@ -49,9 +59,12 @@ FROM base
LABEL maintainer="Alejandro Celaya <alejandro@alejandrocelaya.com>"
COPY --from=builder /etc/shlink .
RUN ln -s /etc/shlink/bin/cli /usr/local/bin/shlink
RUN ln -s /etc/shlink/bin/cli /usr/local/bin/shlink && \
if [ "$SHLINK_RUNTIME" == 'rr' ]; then \
php ./vendor/bin/rr get --no-interaction --location bin/ && chmod +x bin/rr ; \
fi;
# Expose default openswoole port
# Expose default port
EXPOSE 8080
# Copy config specific for the image

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2016-2021 Alejandro Celaya
Copyright (c) 2016-2023 Alejandro Celaya
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,12 +1,13 @@
![Shlink](https://raw.githubusercontent.com/shlinkio/shlink.io/main/public/images/shlink-hero.png)
[![Build Status](https://img.shields.io/github/workflow/status/shlinkio/shlink/Continuous%20integration/develop?logo=github&style=flat-square)](https://github.com/shlinkio/shlink/actions?query=workflow%3A%22Continuous+integration%22)
[![Build Status](https://img.shields.io/github/actions/workflow/status/shlinkio/shlink/ci.yml?branch=develop&logo=github&style=flat-square)](https://github.com/shlinkio/shlink/actions/workflows/ci.yml?query=workflow%3A%22Continuous+integration%22)
[![Code Coverage](https://img.shields.io/codecov/c/gh/shlinkio/shlink/develop?style=flat-square)](https://app.codecov.io/gh/shlinkio/shlink)
[![Infection MSI](https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fshlinkio%2Fshlink%2Fdevelop)](https://dashboard.stryker-mutator.io/reports/github.com/shlinkio/shlink/develop)
[![Latest Stable Version](https://img.shields.io/github/release/shlinkio/shlink.svg?style=flat-square)](https://packagist.org/packages/shlinkio/shlink)
[![Docker pulls](https://img.shields.io/docker/pulls/shlinkio/shlink.svg?logo=docker&style=flat-square)](https://hub.docker.com/r/shlinkio/shlink/)
[![License](https://img.shields.io/github/license/shlinkio/shlink.svg?style=flat-square)](https://github.com/shlinkio/shlink/blob/main/LICENSE)
[![Twitter](https://img.shields.io/twitter/follow/shlinkio?color=blue&label=follow&logo=twitter&style=flat-square)](https://twitter.com/shlinkio)
[![Mastodon](https://img.shields.io/mastodon/follow/109329425426175098?color=%236364ff&domain=https%3A%2F%2Ffosstodon.org&label=follow&logo=mastodon&logoColor=white&style=flat-square)](https://fosstodon.org/@shlinkio)
[![Paypal donate](https://img.shields.io/badge/Donate-paypal-blue.svg?style=flat-square&logo=paypal&colorA=aaaaaa)](https://slnk.to/donate)
A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own domain.
@@ -15,7 +16,7 @@ A PHP-based self-hosted URL shortener that can be used to serve shortened URLs u
- [Full documentation](#full-documentation)
- [Docker image](#docker-image)
- [Self hosted](#self-hosted)
- [Self-hosted](#self-hosted)
- [Download](#download)
- [Configure](#configure)
- [Using shlink](#using-shlink)
@@ -35,7 +36,7 @@ The idea is that you can just generate a container using the image and provide t
First, make sure the host where you are going to run shlink fulfills these requirements:
* PHP 8.1
* PHP 8.1 or 8.2
* The next PHP extensions: json, curl, pdo, intl, gd and gmp/bcmath.
* apcu extension is recommended if you don't plan to use openswoole.
* xml extension is required if you want to generate QR codes in svg format.

12
bin/doctrine Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
/** @var EntityManager $app */
$em = require __DIR__ . '/../config/entity-manager.php';
ConsoleRunner::run(new SingleManagerProvider($em));

32
bin/roadrunner-worker.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
use Mezzio\Application;
use Psr\Container\ContainerInterface;
use Shlinkio\Shlink\EventDispatcher\RoadRunner\RoadRunnerTaskConsumerToListener;
use Spiral\RoadRunner\Http\PSR7Worker;
use function Shlinkio\Shlink\Config\env;
(static function (): void {
/** @var ContainerInterface $container */
$container = include __DIR__ . '/../config/container.php';
$rrMode = env('RR_MODE');
if ($rrMode === 'http') {
// This was spin-up as a web worker
$app = $container->get(Application::class);
$worker = $container->get(PSR7Worker::class);
while ($req = $worker->waitRequest()) {
try {
$worker->respond($app->handle($req));
} catch (Throwable $e) {
$worker->getWorker()->error((string) $e);
}
}
} else {
$container->get(RoadRunnerTaskConsumerToListener::class)->listenForTasks();
}
})();

View File

@@ -1,25 +1,38 @@
#!/usr/bin/env sh
export APP_ENV=test
export DB_DRIVER=postgres
export TEST_ENV=api
export GENERATE_COVERAGE=${GENERATE_COVERAGE:-"no"}
export TEST_RUNTIME="${TEST_RUNTIME:-"openswoole"}"
export DB_DRIVER="${DB_DRIVER:-"postgres"}"
export GENERATE_COVERAGE="${GENERATE_COVERAGE:-"no"}"
# Reset logs
OUTPUT_LOGS=data/log/api-tests/output.log
rm -rf data/log/api-tests
mkdir data/log/api-tests
touch data/log/api-tests/output.log
touch $OUTPUT_LOGS
# Try to stop server just in case it hanged in last execution
vendor/bin/laminas mezzio:swoole:stop
[ "$TEST_RUNTIME" = 'openswoole' ] && vendor/bin/laminas mezzio:swoole:stop
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -f
echo 'Starting server...'
vendor/bin/laminas mezzio:swoole:start -d
sleep 2
[ "$TEST_RUNTIME" = 'openswoole' ] && vendor/bin/laminas mezzio:swoole:start -d
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr serve -p -c=config/roadrunner/.rr.dev.yml \
-o=http.address=0.0.0.0:9999 \
-o=logs.encoding=json \
-o=logs.channels.http.encoding=json \
-o=logs.channels.server.encoding=json \
-o=logs.output="${PWD}/${OUTPUT_LOGS}" \
-o=logs.channels.http.output="${PWD}/${OUTPUT_LOGS}" \
-o=logs.channels.server.output="${PWD}/${OUTPUT_LOGS}" &
sleep 2 # Let's give the server a couple of seconds to start
vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --colors=always --log-junit=build/coverage-api/junit.xml $*
testsExitCode=$?
vendor/bin/laminas mezzio:swoole:stop
[ "$TEST_RUNTIME" = 'openswoole' ] && vendor/bin/laminas mezzio:swoole:stop
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -c config/roadrunner/.rr.dev.yml -o=http.address=0.0.0.0:9999
# Exit this script with the same code as the tests. If tests failed, this script has to fail
exit $testsExitCode

View File

@@ -24,6 +24,7 @@ rsync -av * "${builtContent}" \
--exclude=*docker* \
--exclude=Dockerfile \
--include=.htaccess \
--include=config/roadrunner/.rr.yml \
--exclude-from=./.dockerignore
cd "${builtContent}"
@@ -36,6 +37,9 @@ ${composerBin} install --no-dev --prefer-dist $composerFlags
if [[ $noSwoole ]]; then
# If generating a dist not for openswoole, uninstall mezzio-swoole
${composerBin} remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev $composerFlags
else
# If generating a dist for openswoole, uninstall RoadRunner
${composerBin} remove spiral/roadrunner spiral/roadrunner-jobs --with-all-dependencies --update-no-dev $composerFlags
fi
# Delete development files

View File

@@ -13,42 +13,47 @@
],
"require": {
"php": "^8.1",
"ext-curl": "*",
"ext-gd": "*",
"ext-json": "*",
"ext-pdo": "*",
"akrabat/ip-address-middleware": "^2.1",
"cakephp/chronos": "^2.3",
"doctrine/migrations": "^3.5",
"doctrine/orm": "^2.12",
"endroid/qr-code": "^4.4",
"geoip2/geoip2": "^2.12",
"guzzlehttp/guzzle": "^7.4",
"doctrine/orm": "^2.13.3",
"endroid/qr-code": "^4.6",
"geoip2/geoip2": "^2.13",
"guzzlehttp/guzzle": "^7.5",
"happyr/doctrine-specification": "^2.0",
"jaybizzle/crawler-detect": "^1.2.110",
"jaybizzle/crawler-detect": "^1.2.112",
"laminas/laminas-config": "^3.7",
"laminas/laminas-config-aggregator": "^1.8",
"laminas/laminas-diactoros": "^2.14",
"laminas/laminas-inputfilter": "^2.19",
"laminas/laminas-servicemanager": "^3.16",
"laminas/laminas-stdlib": "^3.11",
"lcobucci/jwt": "^4.1",
"league/uri": "^6.7",
"laminas/laminas-config-aggregator": "^1.11",
"laminas/laminas-diactoros": "^2.19",
"laminas/laminas-inputfilter": "^2.22",
"laminas/laminas-servicemanager": "^3.19",
"laminas/laminas-stdlib": "^3.15",
"lcobucci/jwt": "^4.2",
"league/uri": "^6.8",
"lstrojny/functional-php": "^1.17",
"mezzio/mezzio": "^3.11",
"mezzio/mezzio-fastroute": "^3.5",
"mezzio/mezzio-problem-details": "^1.6",
"mezzio/mezzio-swoole": "^4.3",
"mezzio/mezzio": "^3.13",
"mezzio/mezzio-fastroute": "^3.7",
"mezzio/mezzio-problem-details": "^1.7",
"mezzio/mezzio-swoole": "^4.5",
"mlocati/ip-lib": "^1.18",
"mobiledetect/mobiledetectlib": "^3.74",
"ocramius/proxy-manager": "^2.14",
"pagerfanta/core": "^3.6",
"php-middleware/request-id": "^4.1",
"pugx/shortid-php": "^1.0",
"ramsey/uuid": "^4.3",
"shlinkio/shlink-common": "^5.0",
"shlinkio/shlink-config": "^2.0",
"shlinkio/shlink-event-dispatcher": "^2.5",
"shlinkio/shlink-importer": "^4.0",
"shlinkio/shlink-installer": "^8.1",
"shlinkio/shlink-ip-geolocation": "^3.0",
"pugx/shortid-php": "^1.1",
"ramsey/uuid": "^4.5",
"shlinkio/shlink-common": "^5.3",
"shlinkio/shlink-config": "^2.4",
"shlinkio/shlink-event-dispatcher": "^2.6",
"shlinkio/shlink-importer": "^5.0",
"shlinkio/shlink-installer": "^8.3",
"shlinkio/shlink-ip-geolocation": "^3.2",
"spiral/roadrunner": "^2.11",
"spiral/roadrunner-jobs": "^2.5",
"symfony/console": "^6.1",
"symfony/filesystem": "^6.1",
"symfony/lock": "^6.1",
@@ -58,18 +63,18 @@
"require-dev": {
"cebe/php-openapi": "^1.7",
"devster/ubench": "^2.1",
"dms/phpunit-arraysubset-asserts": "^0.3.0",
"infection/infection": "^0.26.5",
"openswoole/ide-helper": "~4.11.1",
"phpspec/prophecy-phpunit": "^2.0",
"dms/phpunit-arraysubset-asserts": "^0.4.0",
"infection/infection": "^0.26.15",
"openswoole/ide-helper": "~4.11.5",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-doctrine": "^1.3",
"phpstan/phpstan-phpunit": "^1.1",
"phpstan/phpstan-symfony": "^1.2",
"phpunit/php-code-coverage": "^9.2",
"phpunit/phpunit": "^9.5",
"roave/security-advisories": "dev-master",
"shlinkio/php-coding-standard": "~2.3.0",
"shlinkio/shlink-test-utils": "^3.1.0",
"shlinkio/shlink-test-utils": "^3.4",
"symfony/var-dumper": "^6.1",
"veewee/composer-run-parallel": "^1.1"
},
@@ -87,10 +92,13 @@
"autoload-dev": {
"psr-4": {
"ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test",
"ShlinkioCliTest\\Shlink\\CLI\\": "module/CLI/test-cli",
"ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test",
"ShlinkioApiTest\\Shlink\\Rest\\": "module/Rest/test-api",
"ShlinkioDbTest\\Shlink\\Rest\\": "module/Rest/test-db",
"ShlinkioTest\\Shlink\\Core\\": "module/Core/test",
"ShlinkioDbTest\\Shlink\\Core\\": "module/Core/test-db"
"ShlinkioDbTest\\Shlink\\Core\\": "module/Core/test-db",
"ShlinkioApiTest\\Shlink\\Core\\": "module/Core/test-api"
},
"files": [
"config/test/constants.php"
@@ -98,31 +106,18 @@
},
"scripts": {
"ci": [
"@cs",
"@stan",
"@swagger:validate",
"@test:ci",
"@infect:ci"
],
"ci:parallel": [
"@parallel cs stan swagger:validate test:unit:ci test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
"@parallel infect:test:api infect:ci:unit infect:ci:db"
"@parallel infect:test:api infect:test:cli infect:ci:unit infect:ci:db"
],
"cs": "phpcs",
"cs:fix": "phpcbf",
"stan": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/config config docker/config data/migrations --level=8",
"stan": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/test* module/*/config config docker/config data/migrations --level=8",
"test": [
"@test:unit",
"@test:db",
"@test:api"
"@parallel test:unit test:db",
"@parallel test:api test:cli"
],
"test:ci": [
"@test:unit:ci",
"@test:db",
"@test:api:ci"
],
"test:unit": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --testdox",
"test:unit:ci": "@test:unit --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml",
"test:unit": "@php vendor/bin/phpunit --order-by=random --colors=always --testdox",
"test:unit:ci": "@test:unit --coverage-php=build/coverage-unit.cov --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml",
"test:unit:pretty": "@test:unit --coverage-html build/coverage-unit/coverage-html",
"test:db": "@parallel test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
"test:db:sqlite": "APP_ENV=test php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-db.xml",
@@ -132,12 +127,18 @@
"test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite",
"test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite",
"test:api": "bin/test/run-api-tests.sh",
"test:api:rr": "TEST_RUNTIME=rr bin/test/run-api-tests.sh",
"test:api:ci": "GENERATE_COVERAGE=yes composer test:api",
"infect:ci:base": "infection --threads=4 --log-verbosity=default --only-covered --only-covering-test-cases --skip-initial-tests",
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=84",
"infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json",
"infect:ci:api": "@infect:ci:base --coverage=build/coverage-api --min-msi=80 --configuration=infection-api.json",
"infect:ci": "@parallel infect:ci:unit infect:ci:db infect:ci:api",
"test:api:pretty": "GENERATE_COVERAGE=pretty composer test:api",
"test:cli": "APP_ENV=test DB_DRIVER=maria TEST_ENV=cli php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-cli.xml --log-junit=build/coverage-cli/junit.xml",
"test:cli:ci": "GENERATE_COVERAGE=yes composer test:cli",
"test:cli:pretty": "GENERATE_COVERAGE=pretty composer test:cli",
"infect:ci:base": "infection --threads=max --only-covered --only-covering-test-cases --skip-initial-tests",
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=80",
"infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json5",
"infect:ci:api": "@infect:ci:base --coverage=build/coverage-api --min-msi=80 --configuration=infection-api.json5",
"infect:ci:cli": "@infect:ci:base --coverage=build/coverage-cli --min-msi=90 --configuration=infection-cli.json5",
"infect:ci": "@parallel infect:ci:unit infect:ci:db infect:ci:api infect:ci:cli",
"infect:test": [
"@parallel test:unit:ci test:db:sqlite:ci test:api:ci",
"@infect:ci"
@@ -150,18 +151,20 @@
"@test:api:ci",
"@infect:ci:api"
],
"infect:test:cli": [
"@test:cli:ci",
"@infect:ci:cli"
],
"swagger:validate": "php-openapi validate docs/swagger/swagger.json",
"swagger:inline": "php-openapi inline docs/swagger/swagger.json docs/swagger/swagger-inlined.json",
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
},
"scripts-descriptions": {
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"swagger:validate\", \"test:ci\" and \"infect:ci\"</>",
"ci:parallel": "<fg=blue;options=bold>Same as \"ci\", but parallelizing tasks as much as possible</>",
"cs": "<fg=blue;options=bold>Checks coding styles</>",
"cs:fix": "<fg=blue;options=bold>Fixes coding styles, when possible</>",
"stan": "<fg=blue;options=bold>Inspects code with phpstan</>",
"test": "<fg=blue;options=bold>Runs all test suites</>",
"test:ci": "<fg=blue;options=bold>Runs all test suites, generating all needed reports and logs for CI envs</>",
"test:unit": "<fg=blue;options=bold>Runs unit test suites</>",
"test:unit:ci": "<fg=blue;options=bold>Runs unit test suites, generating all needed reports and logs for CI envs</>",
"test:unit:pretty": "<fg=blue;options=bold>Runs unit test suites and generates an HTML code coverage report</>",
@@ -173,7 +176,11 @@
"test:db:postgres": "<fg=blue;options=bold>Runs database test suites on a PostgreSQL database</>",
"test:db:ms": "<fg=blue;options=bold>Runs database test suites on a Microsoft SQL Server database</>",
"test:api": "<fg=blue;options=bold>Runs API test suites</>",
"test:api:ci": "<fg=blue;options=bold>Runs API test suites, and generates code coverage reports</>",
"test:api:ci": "<fg=blue;options=bold>Runs API test suites, and generates code coverage for CI</>",
"test:api:pretty": "<fg=blue;options=bold>Runs API test suites, and generates code coverage in HTML format</>",
"test:cli": "<fg=blue;options=bold>Runs CLI test suites</>",
"test:cli:ci": "<fg=blue;options=bold>Runs CLI test suites, and generates code coverage for CI</>",
"test:cli:pretty": "<fg=blue;options=bold>Runs CLI test suites, and generates code coverage in HTML format</>",
"infect:ci": "<fg=blue;options=bold>Checks unit and db tests quality applying mutation testing with existing reports and logs</>",
"infect:ci:unit": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing with existing reports and logs</>",
"infect:ci:db": "<fg=blue;options=bold>Checks db tests quality applying mutation testing with existing reports and logs</>",

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
use Shlinkio\Shlink\Core\Config\EnvVars;
return (static function (): array {
$redisServers = EnvVars::REDIS_SERVERS->loadFromEnv();
$redis = ['pub_sub_enabled' => $redisServers !== null && EnvVars::REDIS_PUB_SUB_ENABLED->loadFromEnv(false)];
$cacheRedisBlock = $redisServers === null ? [] : [
'redis' => [
'servers' => $redisServers,
'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE->loadFromEnv(),
],
];
return [
'cache' => [
'namespace' => 'Shlink',
...$cacheRedisBlock,
],
'redis' => $redis,
];
})();

View File

@@ -8,8 +8,8 @@ return [
'debug' => false,
// Disabling config cache for cli, ensures it's never used for openswoole and also that console commands don't
// generate a cache file that's then used by non-openswoole web executions
// Disabling config cache for cli, ensures it's never used for openswoole/RoadRunner, and also that console
// commands don't generate a cache file that's then used by php-fpm web executions
ConfigAggregator::ENABLE_CACHE => PHP_SAPI !== 'cli',
];

View File

@@ -3,12 +3,22 @@
declare(strict_types=1);
use GuzzleHttp\Client;
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
use Mezzio\Container;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Spiral\RoadRunner\Http\PSR7Worker;
use Spiral\RoadRunner\WorkerInterface;
return [
'dependencies' => [
'factories' => [
PSR7Worker::class => ConfigAbstractFactory::class,
],
'delegators' => [
Mezzio\Application::class => [
Container\ApplicationConfigInjectionDelegator::class,
@@ -26,4 +36,13 @@ return [
],
],
ConfigAbstractFactory::class => [
PSR7Worker::class => [
WorkerInterface::class,
ServerRequestFactoryInterface::class,
StreamFactoryInterface::class,
UploadedFileFactoryInterface::class,
],
],
];

View File

@@ -42,6 +42,9 @@ return (static function (): array {
'port' => EnvVars::DB_PORT->loadFromEnv($resolveDefaultPort()),
'unix_socket' => $isMysqlCompatible ? EnvVars::DB_UNIX_SOCKET->loadFromEnv() : null,
'charset' => $resolveCharset(),
'driverOptions' => $driver !== 'mssql' ? [] : [
'TrustServerCertificate' => 'true',
],
],
};

View File

@@ -6,12 +6,14 @@ use Laminas\Stratigility\Middleware\ErrorHandler;
use Mezzio\ProblemDetails\ProblemDetailsMiddleware;
use Shlinkio\Shlink\Common\Logger;
use function Shlinkio\Shlink\Core\toProblemDetailsType;
return [
'problem-details' => [
'default_types_map' => [
404 => 'NOT_FOUND',
500 => 'INTERNAL_SERVER_ERROR',
404 => toProblemDetailsType('not-found'),
500 => toProblemDetailsType('internal-server-error'),
],
],

View File

@@ -44,6 +44,8 @@ return [
Option\UrlShortener\AutoResolveTitlesConfigOption::class,
Option\UrlShortener\AppendExtraPathConfigOption::class,
Option\UrlShortener\EnableMultiSegmentSlugsConfigOption::class,
Option\UrlShortener\EnableTrailingSlashConfigOption::class,
Option\UrlShortener\ShortUrlModeConfigOption::class,
Option\Tracking\IpAnonymizationConfigOption::class,
Option\Tracking\OrphanVisitsTrackingConfigOption::class,
Option\Tracking\DisableTrackParamConfigOption::class,
@@ -72,9 +74,18 @@ return [
InstallationCommand::DB_MIGRATE->value => [
'command' => 'bin/cli ' . Command\Db\MigrateDatabaseCommand::NAME,
],
InstallationCommand::ORM_PROXIES->value => [
'command' => 'bin/doctrine orm:generate-proxies',
],
InstallationCommand::ORM_CLEAR_CACHE->value => [
'command' => 'bin/doctrine orm:clear-cache:metadata',
],
InstallationCommand::GEOLITE_DOWNLOAD_DB->value => [
'command' => 'bin/cli ' . Command\Visit\DownloadGeoLiteDbCommand::NAME,
],
InstallationCommand::API_KEY_GENERATE->value => [
'command' => 'bin/cli ' . Command\Api\GenerateKeyCommand::NAME,
],
],
],

View File

@@ -7,7 +7,7 @@ return [
'mercure' => [
'public_hub_url' => 'http://localhost:8001',
'internal_hub_url' => 'http://shlink_mercure_proxy',
'jwt_secret' => 'mercure_jwt_key',
'jwt_secret' => 'mercure_jwt_key_long_enough_to_avoid_error',
],
];

View File

@@ -45,6 +45,7 @@ return [
'rest' => [
'path' => '/rest',
'middleware' => [
Rest\Middleware\ErrorHandler\BackwardsCompatibleProblemDetailsHandler::class,
Router\Middleware\ImplicitOptionsMiddleware::class,
Rest\Middleware\BodyParserMiddleware::class,
Rest\Middleware\AuthenticationMiddleware::class,

View File

@@ -16,7 +16,7 @@ return [
],
'redirects' => [
'redirect_status_code' => (int) EnvVars::REDIRECT_STATUS_CODE->loadFromEnv(DEFAULT_REDIRECT_STATUS_CODE),
'redirect_status_code' => (int) EnvVars::REDIRECT_STATUS_CODE->loadFromEnv(DEFAULT_REDIRECT_STATUS_CODE->value),
'redirect_cache_lifetime' => (int) EnvVars::REDIRECT_CACHE_LIFETIME->loadFromEnv(
DEFAULT_REDIRECT_CACHE_LIFETIME,
),

View File

@@ -1,27 +0,0 @@
<?php
declare(strict_types=1);
use Shlinkio\Shlink\Core\Config\EnvVars;
return (static function (): array {
$redisServers = EnvVars::REDIS_SERVERS->loadFromEnv();
$pubSub = [
'redis' => [
'pub_sub_enabled' => $redisServers !== null && EnvVars::REDIS_PUB_SUB_ENABLED->loadFromEnv(false),
],
];
return match ($redisServers) {
null => $pubSub,
default => [
'cache' => [
'redis' => [
'servers' => $redisServers,
'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE->loadFromEnv(),
],
],
...$pubSub,
],
};
})();

View File

@@ -7,6 +7,8 @@ return [
'cache' => [
'redis' => [
'servers' => 'tcp://shlink_redis:6379',
// 'servers' => 'tcp://barbar@shlink_redis_acl:6379',
// 'servers' => 'tcp://foo:bar@shlink_redis_acl:6379',
],
],

View File

@@ -7,16 +7,23 @@ namespace Shlinkio\Shlink;
use Fig\Http\Message\RequestMethodInterface;
use RKA\Middleware\IpAddress;
use Shlinkio\Shlink\Core\Action as CoreAction;
use Shlinkio\Shlink\Core\Config\EnvVars;
use Shlinkio\Shlink\Core\ShortUrl\Middleware\TrimTrailingSlashMiddleware;
use Shlinkio\Shlink\Rest\Action;
use Shlinkio\Shlink\Rest\ConfigProvider;
use Shlinkio\Shlink\Rest\Middleware;
use Shlinkio\Shlink\Rest\Middleware\Mercure\NotConfiguredMercureErrorHandler;
use function sprintf;
return (static function (): array {
$contentNegotiationMiddleware = Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware::class;
$dropDomainMiddleware = Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware::class;
$overrideDomainMiddleware = Middleware\ShortUrl\OverrideDomainMiddleware::class;
// TODO This should be based on config, not the env var
$shortUrlRouteSuffix = EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv(false) ? '[/]' : '';
return [
// The order of the routes defined here matters. Changing it might cause path conflicts
@@ -90,9 +97,10 @@ return (static function (): array {
],
[
'name' => CoreAction\RedirectAction::class,
'path' => '/{shortCode}',
'path' => sprintf('/{shortCode}%s', $shortUrlRouteSuffix),
'middleware' => [
IpAddress::class,
TrimTrailingSlashMiddleware::class,
CoreAction\RedirectAction::class,
],
'allowed_methods' => [RequestMethodInterface::METHOD_GET],

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
use Shlinkio\Shlink\Core\Config\EnvVars;
use function Shlinkio\Shlink\Config\getOpenswooleConfigFromEnv;
use const Shlinkio\Shlink\MIN_TASK_WORKERS;
return (static function (): array {
@@ -21,6 +23,7 @@ return (static function (): array {
'process-name' => 'shlink',
'options' => [
...getOpenswooleConfigFromEnv(),
'worker_num' => (int) EnvVars::WEB_WORKER_NUM->loadFromEnv(16),
'task_worker_num' => max($taskWorkers, MIN_TASK_WORKERS),
],

View File

@@ -4,33 +4,40 @@ declare(strict_types=1);
use Shlinkio\Shlink\Core\Config\EnvVars;
return [
return (static function (): array {
/** @var string|null $disableTrackingFrom */
$disableTrackingFrom = EnvVars::DISABLE_TRACKING_FROM->loadFromEnv();
'tracking' => [
// Tells if IP addresses should be anonymized before persisting, to fulfil data protection regulations
// This applies only if IP address tracking is enabled
'anonymize_remote_addr' => (bool) EnvVars::ANONYMIZE_REMOTE_ADDR->loadFromEnv(true),
return [
// Tells if visits to not-found URLs should be tracked. The disable_tracking option takes precedence
'track_orphan_visits' => (bool) EnvVars::TRACK_ORPHAN_VISITS->loadFromEnv(true),
'tracking' => [
// Tells if IP addresses should be anonymized before persisting, to fulfil data protection regulations
// This applies only if IP address tracking is enabled
'anonymize_remote_addr' => (bool) EnvVars::ANONYMIZE_REMOTE_ADDR->loadFromEnv(true),
// A query param that, if provided, will disable tracking of one particular visit. Always takes precedence
'disable_track_param' => EnvVars::DISABLE_TRACK_PARAM->loadFromEnv(),
// Tells if visits to not-found URLs should be tracked. The disable_tracking option takes precedence
'track_orphan_visits' => (bool) EnvVars::TRACK_ORPHAN_VISITS->loadFromEnv(true),
// If true, visits will not be tracked at all
'disable_tracking' => (bool) EnvVars::DISABLE_TRACKING->loadFromEnv(false),
// A query param that, if provided, will disable tracking of one particular visit. Always takes precedence
'disable_track_param' => EnvVars::DISABLE_TRACK_PARAM->loadFromEnv(),
// If true, visits will be tracked, but neither the IP address, nor the location will be resolved
'disable_ip_tracking' => (bool) EnvVars::DISABLE_IP_TRACKING->loadFromEnv(false),
// If true, visits will not be tracked at all
'disable_tracking' => (bool) EnvVars::DISABLE_TRACKING->loadFromEnv(false),
// If true, the referrer will not be tracked
'disable_referrer_tracking' => (bool) EnvVars::DISABLE_REFERRER_TRACKING->loadFromEnv(false),
// If true, visits will be tracked, but neither the IP address, nor the location will be resolved
'disable_ip_tracking' => (bool) EnvVars::DISABLE_IP_TRACKING->loadFromEnv(false),
// If true, the user agent will not be tracked
'disable_ua_tracking' => (bool) EnvVars::DISABLE_UA_TRACKING->loadFromEnv(false),
// If true, the referrer will not be tracked
'disable_referrer_tracking' => (bool) EnvVars::DISABLE_REFERRER_TRACKING->loadFromEnv(false),
// A list of IP addresses, patterns or CIDR blocks from which tracking is disabled by default
'disable_tracking_from' => EnvVars::DISABLE_TRACKING_FROM->loadFromEnv(),
],
// If true, the user agent will not be tracked
'disable_ua_tracking' => (bool) EnvVars::DISABLE_UA_TRACKING->loadFromEnv(false),
];
// A list of IP addresses, patterns or CIDR blocks from which tracking is disabled by default
'disable_tracking_from' => $disableTrackingFrom === null
? []
: array_map(trim(...), explode(',', $disableTrackingFrom)),
],
];
})();

View File

@@ -3,6 +3,7 @@
declare(strict_types=1);
use Shlinkio\Shlink\Core\Config\EnvVars;
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode;
use const Shlinkio\Shlink\DEFAULT_SHORT_CODES_LENGTH;
use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH;
@@ -12,6 +13,8 @@ return (static function (): array {
(int) EnvVars::DEFAULT_SHORT_CODES_LENGTH->loadFromEnv(DEFAULT_SHORT_CODES_LENGTH),
MIN_SHORT_CODES_LENGTH,
);
$modeFromEnv = EnvVars::SHORT_URL_MODE->loadFromEnv(ShortUrlMode::STRICT->value);
$mode = ShortUrlMode::tryFrom($modeFromEnv) ?? ShortUrlMode::STRICT;
return [
@@ -24,6 +27,8 @@ return (static function (): array {
'auto_resolve_titles' => (bool) EnvVars::AUTO_RESOLVE_TITLES->loadFromEnv(false),
'append_extra_path' => (bool) EnvVars::REDIRECT_APPEND_EXTRA_PATH->loadFromEnv(false),
'multi_segment_slugs_enabled' => (bool) EnvVars::MULTI_SEGMENT_SLUGS_ENABLED->loadFromEnv(false),
'trailing_slash_enabled' => (bool) EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv(false),
'mode' => $mode,
],
];

View File

@@ -2,17 +2,23 @@
declare(strict_types=1);
$isSwoole = extension_loaded('openswoole');
use function Shlinkio\Shlink\Config\runningInOpenswoole;
use function Shlinkio\Shlink\Config\runningInRoadRunner;
return [
'url_shortener' => [
'domain' => [
'schema' => 'http',
'hostname' => sprintf('localhost:%s', $isSwoole ? '8080' : '8000'),
'hostname' => sprintf('localhost:%s', match (true) {
runningInRoadRunner() => '8800',
runningInOpenswoole() => '8080',
default => '8000',
}),
],
'auto_resolve_titles' => true,
// 'multi_segment_slugs_enabled' => true,
// 'trailing_slash_enabled' => true,
],
];

View File

@@ -13,20 +13,23 @@ use Shlinkio\Shlink\Config\ConfigAggregator\EnvVarLoaderProvider;
use function class_exists;
use function Shlinkio\Shlink\Config\env;
use function Shlinkio\Shlink\Config\openswooleIsInstalled;
use function Shlinkio\Shlink\Config\runningInRoadRunner;
use function Shlinkio\Shlink\Core\enumValues;
use const PHP_SAPI;
$isCli = PHP_SAPI === 'cli';
$isTestEnv = env('APP_ENV') === 'test';
$enableSwoole = PHP_SAPI === 'cli' && openswooleIsInstalled() && ! runningInRoadRunner();
return (new ConfigAggregator\ConfigAggregator([
! $isTestEnv
? new EnvVarLoaderProvider('config/params/generated_config.php', Core\Config\EnvVars::values())
? new EnvVarLoaderProvider('config/params/generated_config.php', enumValues(Core\Config\EnvVars::class))
: new ConfigAggregator\ArrayProvider([]),
Mezzio\ConfigProvider::class,
Mezzio\Router\ConfigProvider::class,
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
$isCli && class_exists(Swoole\ConfigProvider::class)
$enableSwoole && class_exists(Swoole\ConfigProvider::class)
? Swoole\ConfigProvider::class
: new ConfigAggregator\ArrayProvider([]),
ProblemDetails\ConfigProvider::class,
@@ -46,6 +49,7 @@ return (new ConfigAggregator\ConfigAggregator([
// Routes have to be loaded last
new ConfigAggregator\PhpFileProvider('config/autoload/routes.config.php'),
], 'data/cache/app_config.php', [
Core\Config\BasePathPrefixer::class,
Core\Config\MultiSegmentSlugProcessor::class,
Core\Config\PostProcessor\BasePathPrefixer::class,
Core\Config\PostProcessor\MultiSegmentSlugProcessor::class,
Core\Config\PostProcessor\ShortUrlMethodsProcessor::class,
]))->getMergedConfig();

View File

@@ -4,12 +4,12 @@ declare(strict_types=1);
namespace Shlinkio\Shlink;
use Fig\Http\Message\StatusCodeInterface;
use Shlinkio\Shlink\Core\Util\RedirectStatus;
const DEFAULT_DELETE_SHORT_URL_THRESHOLD = 15;
const DEFAULT_SHORT_CODES_LENGTH = 5;
const MIN_SHORT_CODES_LENGTH = 4;
const DEFAULT_REDIRECT_STATUS_CODE = StatusCodeInterface::STATUS_FOUND;
const DEFAULT_REDIRECT_STATUS_CODE = RedirectStatus::STATUS_302; // Deprecated. Default to 307 for Shlink v4
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
const TITLE_TAG_VALUE = '/<title[^>]*>(.*?)<\/title>/i'; // Matches the value inside a html title tag

View File

@@ -0,0 +1,49 @@
version: '2.7'
rpc:
listen: tcp://127.0.0.1:6001
server:
command: 'php ../../bin/roadrunner-worker.php'
http:
address: '0.0.0.0:8080'
middleware: ['static']
static:
dir: '../../public'
forbid: ['.php', '.htaccess']
pool:
num_workers: 1
jobs:
pool:
num_workers: 1
timeout: 300
consume: ['shlink']
pipelines:
shlink:
driver: memory
config:
priority: 10
prefetch: 10
logs:
mode: development
channels:
http:
level: debug
server:
level: debug
metrics:
level: debug
reload:
interval: 1s
patterns: ['.php']
services:
http:
dirs: ['../../bin', '../../config', '../../data/migrations', '../../module', '../../vendor']
recursive: true
jobs:
dirs: ['../../bin', '../../config', '../../data/migrations', '../../module', '../../vendor']
recursive: true

36
config/roadrunner/.rr.yml Normal file
View File

@@ -0,0 +1,36 @@
version: '2.7'
rpc:
listen: tcp://127.0.0.1:6001
server:
command: 'php -dopcache.enable_cli=1 -dopcache.validate_timestamps=0 ../../bin/roadrunner-worker.php'
http:
address: '0.0.0.0:${PORT}'
middleware: ['static']
static:
dir: '../../public'
forbid: ['.php', '.htaccess']
pool:
num_workers: ${WEB_WORKER_NUM}
jobs:
timeout: 300 # 5 minutes
pool:
num_workers: ${TASK_WORKER_NUM}
consume: ['shlink']
pipelines:
shlink:
driver: memory
config:
priority: 10
prefetch: 10
logs:
mode: production
channels:
http:
level: info # Log all http requests, set to info to disable
server:
level: debug # Everything written to worker stderr is logged

View File

@@ -10,8 +10,8 @@ use Psr\Container\ContainerInterface;
use function register_shutdown_function;
use function sprintf;
use const ShlinkioTest\Shlink\SWOOLE_TESTING_HOST;
use const ShlinkioTest\Shlink\SWOOLE_TESTING_PORT;
use const ShlinkioTest\Shlink\API_TESTS_HOST;
use const ShlinkioTest\Shlink\API_TESTS_PORT;
/** @var ContainerInterface $container */
$container = require __DIR__ . '/../container.php';
@@ -24,10 +24,15 @@ $httpClient = $container->get('shlink_test_api_client');
register_shutdown_function(function () use ($httpClient): void {
$httpClient->request(
'GET',
sprintf('http://%s:%s/api-tests/stop-coverage', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT),
sprintf('http://%s:%s/api-tests/stop-coverage', API_TESTS_HOST, API_TESTS_PORT),
);
});
$testHelper->createTestDb(['bin/cli', 'db:create'], ['bin/cli', 'db:migrate']);
$testHelper->createTestDb(
['bin/cli', 'db:create'],
['bin/cli', 'db:migrate'],
['bin/doctrine', 'orm:schema-tool:drop'],
['bin/doctrine', 'dbal:run-sql'],
);
ApiTest\ApiTestCase::setApiClient($httpClient);
ApiTest\ApiTestCase::setSeedFixturesCallback(fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? []));

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\TestUtils;
use Doctrine\ORM\EntityManager;
use Psr\Container\ContainerInterface;
use function file_exists;
use function unlink;
/** @var ContainerInterface $container */
$container = require __DIR__ . '/../container.php';
$testHelper = $container->get(Helper\TestHelper::class);
$config = $container->get('config');
$em = $container->get(EntityManager::class);
// Delete old coverage in PHP, to avoid merging older executions with current one
$covFile = __DIR__ . '/../../build/coverage-cli.cov';
if (file_exists($covFile)) {
unlink($covFile);
}
$testHelper->createTestDb(
['bin/cli', 'db:create'],
['bin/cli', 'db:migrate'],
['bin/doctrine', 'orm:schema-tool:drop'],
['bin/doctrine', 'dbal:run-sql'],
);
CliTest\CliTestCase::setSeedFixturesCallback(
static fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? []),
);

View File

@@ -8,5 +8,10 @@ use Psr\Container\ContainerInterface;
/** @var ContainerInterface $container */
$container = require __DIR__ . '/../container.php';
$container->get(Helper\TestHelper::class)->createTestDb(['bin/cli', 'db:create'], ['bin/cli', 'db:migrate']);
$container->get(Helper\TestHelper::class)->createTestDb(
['bin/cli', 'db:create'],
['bin/cli', 'db:migrate'],
['bin/doctrine', 'orm:schema-tool:drop'],
['bin/doctrine', 'dbal:run-sql'],
);
DbTest\DatabaseTestCase::setEntityManager($container->get('em'));

View File

@@ -4,5 +4,12 @@ declare(strict_types=1);
namespace ShlinkioTest\Shlink;
const SWOOLE_TESTING_HOST = '127.0.0.1';
const SWOOLE_TESTING_PORT = 9999;
const API_TESTS_HOST = '127.0.0.1';
const API_TESTS_PORT = 9999;
const ANDROID_USER_AGENT = 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) '
. 'Chrome/109.0.5414.86 Mobile Safari/537.36';
const IOS_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 '
. '(KHTML, like Gecko) FxiOS/109.0 Mobile/15E148 Safari/605.1.15';
const DESKTOP_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like '
. 'Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61';

View File

@@ -8,8 +8,10 @@ use GuzzleHttp\Client;
use Laminas\ConfigAggregator\ConfigAggregator;
use Laminas\Diactoros\Response\EmptyResponse;
use Laminas\ServiceManager\Factory\InvokableFactory;
use League\Event\EventDispatcher;
use Monolog\Level;
use PHPUnit\Runner\Version;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
@@ -20,24 +22,60 @@ use SebastianBergmann\CodeCoverage\Report\Html\Facade as Html;
use SebastianBergmann\CodeCoverage\Report\PHP;
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as Xml;
use Shlinkio\Shlink\Common\Logger\LoggerType;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use function file_exists;
use function Functional\contains;
use function Laminas\Stratigility\middleware;
use function Shlinkio\Shlink\Config\env;
use function sprintf;
use function sys_get_temp_dir;
use const ShlinkioTest\Shlink\SWOOLE_TESTING_HOST;
use const ShlinkioTest\Shlink\SWOOLE_TESTING_PORT;
use const ShlinkioTest\Shlink\API_TESTS_HOST;
use const ShlinkioTest\Shlink\API_TESTS_PORT;
$isApiTest = env('TEST_ENV') === 'api';
$generateCoverage = env('GENERATE_COVERAGE') === 'yes';
if ($isApiTest && $generateCoverage) {
$isCliTest = env('TEST_ENV') === 'cli';
$isE2eTest = $isApiTest || $isCliTest;
$coverageType = env('GENERATE_COVERAGE');
$generateCoverage = contains(['yes', 'pretty'], $coverageType);
$coverage = null;
if ($isE2eTest && $generateCoverage) {
$filter = new Filter();
$filter->includeDirectory(__DIR__ . '/../../module/Core/src');
$filter->includeDirectory(__DIR__ . '/../../module/Rest/src');
$filter->includeDirectory(__DIR__ . '/../../module/' . ($isApiTest ? 'Rest' : 'CLI') . '/src');
$coverage = new CodeCoverage((new Selector())->forLineCoverage($filter), $filter);
}
/**
* @param 'api'|'cli' $type
*/
$exportCoverage = static function (string $type = 'api') use (&$coverage, $coverageType): void {
if ($coverage === null) {
return;
}
$basePath = __DIR__ . '/../../build/coverage-' . $type;
$covPath = $basePath . '.cov';
// Every CLI test runs on its own process and dumps the coverage afterwards.
// Try to load it and merge it, so that we end up with the whole coverage at the end.
if ($type === 'cli' && file_exists($covPath)) {
$coverage->merge(require $covPath);
}
if ($coverageType === 'pretty') {
(new Html())->process($coverage, $basePath . '/coverage-html');
} else {
(new PHP())->process($coverage, $covPath);
(new Xml(Version::getVersionString()))->process($coverage, $basePath . '/coverage-xml');
}
};
$buildDbConnection = static function (): array {
$driver = env('DB_DRIVER', 'sqlite');
$isCi = env('CI', false);
@@ -46,7 +84,7 @@ $buildDbConnection = static function (): array {
return match ($driver) {
'sqlite' => [
'driver' => 'pdo_sqlite',
'path' => sys_get_temp_dir() . '/shlink-tests.db',
'memory' => true,
],
'postgres' => [
'driver' => 'pdo_pgsql',
@@ -63,6 +101,9 @@ $buildDbConnection = static function (): array {
'user' => 'sa',
'password' => 'Passw0rd!',
'dbname' => 'shlink_test',
'driverOptions' => [
'TrustServerCertificate' => 'true',
],
],
default => [ // mysql and maria
'driver' => 'pdo_mysql',
@@ -90,16 +131,15 @@ return [
'url_shortener' => [
'domain' => [
'schema' => 'http',
'hostname' => 'doma.in',
'hostname' => 's.test',
],
'validate_url' => true,
],
'mezzio-swoole' => [
'enable_coroutine' => false,
'swoole-http-server' => [
'host' => SWOOLE_TESTING_HOST,
'port' => SWOOLE_TESTING_PORT,
'host' => API_TESTS_HOST,
'port' => API_TESTS_PORT,
'process-name' => 'shlink_test',
'options' => [
'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid',
@@ -113,17 +153,10 @@ return [
[
'name' => 'dump_coverage',
'path' => '/api-tests/stop-coverage',
'middleware' => middleware(static function () use (&$coverage) {
'middleware' => middleware(static function () use ($exportCoverage) {
// TODO I have tried moving this block to a listener so that it's invoked automatically,
// but then the coverage is generated empty ¯\_(ツ)_/¯
if ($coverage) { // @phpstan-ignore-line
$basePath = __DIR__ . '/../../build/coverage-api';
(new PHP())->process($coverage, $basePath . '.cov');
(new Xml(Version::getVersionString()))->process($coverage, $basePath . '/coverage-xml');
(new Html())->process($coverage, $basePath . '/coverage-html');
}
$exportCoverage();
return new EmptyResponse();
}),
'allowed_methods' => ['GET'],
@@ -157,13 +190,69 @@ return [
'dependencies' => [
'services' => [
'shlink_test_api_client' => new Client([
'base_uri' => sprintf('http://%s:%s/', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT),
'base_uri' => sprintf('http://%s:%s/', API_TESTS_HOST, API_TESTS_PORT),
'http_errors' => false,
]),
],
'factories' => [
TestUtils\Helper\TestHelper::class => InvokableFactory::class,
],
'delegators' => $isCliTest ? [
Application::class => [
static function (
ContainerInterface $c,
string $serviceName,
callable $callback,
) use (
&$coverage,
$exportCoverage,
) {
/** @var Application $app */
$app = $callback();
$wrappedEventDispatcher = new EventDispatcher();
// When the command starts, start collecting coverage
$wrappedEventDispatcher->subscribeTo(
ConsoleCommandEvent::class,
static function () use (&$coverage): void {
$id = env('COVERAGE_ID');
if ($id === null) {
return;
}
$coverage?->start($id);
},
);
// When the command ends, stop collecting coverage
$wrappedEventDispatcher->subscribeTo(
ConsoleTerminateEvent::class,
static function () use (&$coverage, $exportCoverage): void {
$id = env('COVERAGE_ID');
if ($id === null) {
return;
}
$coverage?->stop();
$exportCoverage('cli');
},
);
$app->setDispatcher(new class ($wrappedEventDispatcher) implements EventDispatcherInterface {
public function __construct(private EventDispatcher $wrappedDispatcher)
{
}
public function dispatch(object $event, ?string $eventName = null): object
{
$this->wrappedDispatcher->dispatch($event);
return $event;
}
});
return $app;
},
],
] : [],
],
'entity_manager' => [
@@ -172,6 +261,7 @@ return [
'data_fixtures' => [
'paths' => [
// TODO These are used for CLI tests too, so maybe should be somewhere else
__DIR__ . '/../../module/Rest/test-api/Fixtures',
],
],

View File

@@ -1,5 +1,5 @@
<VirtualHost *:80>
ServerName doma.in
ServerName s.test
DocumentRoot "/path/to/shlink/public"
<Directory "/path/to/shlink/public">

View File

@@ -1,5 +1,5 @@
server {
server_name doma.in;
server_name s.test;
listen 80;
root /path/to/shlink/public;
index index.php;

View File

@@ -1,9 +1,10 @@
FROM php:8.1.9-fpm-alpine3.16
FROM php:8.2-fpm-alpine3.17
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
ENV APCU_VERSION 5.1.21
ENV PDO_SQLSRV_VERSION 5.10.1
ENV MS_ODBC_SQL_VERSION 17.5.2.2
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
RUN apk update
@@ -30,7 +31,9 @@ RUN docker-php-ext-install gd
RUN apk add --no-cache postgresql-dev
RUN docker-php-ext-install pdo_pgsql
RUN docker-php-ext-install sockets
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \
docker-php-ext-install sockets && \
apk del .phpize-deps
RUN docker-php-ext-install bcmath
# Install APCu extension
@@ -44,13 +47,13 @@ RUN mkdir -p /usr/src/php/ext/apcu \
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
# Install pcov and sqlsrv driver
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
apk add --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
RUN wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
docker-php-ext-enable pdo_sqlsrv pcov && \
apk del .phpize-deps && \
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk
rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk
# Install composer
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer

View File

@@ -0,0 +1,2 @@
user foo allcommands allkeys on >bar
requirepass barbar

View File

@@ -0,0 +1,76 @@
FROM php:8.2-alpine3.17
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
ENV APCU_VERSION 5.1.21
ENV PDO_SQLSRV_VERSION 5.10.1
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
RUN apk update
# Install common php extensions
RUN docker-php-ext-install pdo_mysql
RUN docker-php-ext-install calendar
RUN apk add --no-cache oniguruma-dev
RUN docker-php-ext-install mbstring
RUN apk add --no-cache sqlite-libs
RUN apk add --no-cache sqlite-dev
RUN docker-php-ext-install pdo_sqlite
RUN apk add --no-cache icu-dev
RUN docker-php-ext-install intl
RUN apk add --no-cache libzip-dev zlib-dev
RUN docker-php-ext-install zip
RUN apk add --no-cache libpng-dev
RUN docker-php-ext-install gd
RUN apk add --no-cache postgresql-dev
RUN docker-php-ext-install pdo_pgsql
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \
docker-php-ext-install sockets && \
apk del .phpize-deps
RUN docker-php-ext-install bcmath
# Install APCu extension
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
RUN mkdir -p /usr/src/php/ext/apcu \
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1 \
&& docker-php-ext-configure apcu \
&& docker-php-ext-install apcu \
&& rm /tmp/apcu.tar.gz \
&& rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
# Install pcov and sqlsrv driver
RUN wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
docker-php-ext-enable pdo_sqlsrv pcov && \
apk del .phpize-deps && \
rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk
# Install composer
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
# Make home directory writable by anyone
RUN chmod 777 /home
VOLUME /home/shlink
WORKDIR /home/shlink
# Expose roadrunner port
EXPOSE 8080
CMD \
# Install dependencies if the vendor dir does not exist
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
# Download roadrunner binary
if [[ ! -f "./bin/rr" ]]; then ./vendor/bin/rr get --no-interaction --location bin/ && chmod +x bin/rr ; fi && \
# This forces the app to be started every second until the exit code is 0
until ./bin/rr serve -c config/roadrunner/.rr.dev.yml; do sleep 1 ; done

View File

@@ -1,11 +1,12 @@
FROM php:8.1.9-alpine3.16
FROM php:8.2-alpine3.17
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
ENV APCU_VERSION 5.1.21
ENV INOTIFY_VERSION 3.0.0
ENV OPENSWOOLE_VERSION 4.11.1
ENV OPENSWOOLE_VERSION 4.12.1
ENV PDO_SQLSRV_VERSION 5.10.1
ENV MS_ODBC_SQL_VERSION 17.5.2.2
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
RUN apk update
@@ -32,7 +33,9 @@ RUN docker-php-ext-install gd
RUN apk add --no-cache postgresql-dev
RUN docker-php-ext-install pdo_pgsql
RUN docker-php-ext-install sockets
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \
docker-php-ext-install sockets && \
apk del .phpize-deps
RUN docker-php-ext-install bcmath
# Install APCu extension
@@ -54,13 +57,13 @@ RUN mkdir -p /usr/src/php/ext/inotify \
&& rm /tmp/inotify.tar.gz
# Install openswoole, pcov and mssql driver
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
apk add --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
RUN wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
pecl install openswoole-${OPENSWOOLE_VERSION} pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
docker-php-ext-enable openswoole pdo_sqlsrv pcov && \
apk del .phpize-deps && \
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk
rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk
# Install composer
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer

View File

@@ -8,7 +8,7 @@ use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
use Shlinkio\Shlink\Core\Visit\Model\VisitType;
final class Version20210207100807 extends AbstractMigration

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
final class Version20230103105343 extends AbstractMigration
{
private const TABLE_NAME = 'device_long_urls';
public function up(Schema $schema): void
{
$this->skipIf($schema->hasTable(self::TABLE_NAME));
$table = $schema->createTable(self::TABLE_NAME);
$table->addColumn('id', Types::BIGINT, [
'unsigned' => true,
'autoincrement' => true,
'notnull' => true,
]);
$table->setPrimaryKey(['id']);
$table->addColumn('device_type', Types::STRING, ['length' => 255]);
$table->addColumn('long_url', Types::STRING, ['length' => 2048]);
$table->addColumn('short_url_id', Types::BIGINT, [
'unsigned' => true,
'notnull' => true,
]);
$table->addForeignKeyConstraint('short_urls', ['short_url_id'], ['id'], [
'onDelete' => 'CASCADE',
'onUpdate' => 'RESTRICT',
]);
$table->addUniqueIndex(['device_type', 'short_url_id'], 'UQ_device_type_per_short_url');
}
public function down(Schema $schema): void
{
$this->skipIf(! $schema->hasTable(self::TABLE_NAME));
$schema->dropTable(self::TABLE_NAME);
}
public function isTransactional(): bool
{
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -13,6 +13,12 @@ services:
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro
shlink_roadrunner:
user: 1000:1000
volumes:
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro
shlink_db_mysql:
user: 1000:1000
volumes:

View File

@@ -29,6 +29,7 @@ services:
- shlink_db_maria
- shlink_db_ms
- shlink_redis
- shlink_redis_acl
- shlink_mercure
- shlink_mercure_proxy
- shlink_rabbitmq
@@ -65,6 +66,32 @@ services:
- shlink_db_maria
- shlink_db_ms
- shlink_redis
- shlink_redis_acl
- shlink_mercure
- shlink_mercure_proxy
- shlink_rabbitmq
environment:
LC_ALL: C
extra_hosts:
- 'host.docker.internal:host-gateway'
shlink_roadrunner:
container_name: shlink_roadrunner
build:
context: .
dockerfile: ./data/infra/roadrunner.Dockerfile
ports:
- "8800:8080"
volumes:
- ./:/home/shlink
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
links:
- shlink_db_mysql
- shlink_db_postgres
- shlink_db_maria
- shlink_db_ms
- shlink_redis
- shlink_redis_acl
- shlink_mercure
- shlink_mercure_proxy
- shlink_rabbitmq
@@ -75,7 +102,7 @@ services:
shlink_db_mysql:
container_name: shlink_db_mysql
image: mysql:5.7
image: mysql:8.0
ports:
- "3307:3306"
volumes:
@@ -122,10 +149,19 @@ services:
shlink_redis:
container_name: shlink_redis
image: redis:6.0-alpine
image: redis:6.2-alpine
ports:
- "6380:6379"
shlink_redis_acl:
container_name: shlink_redis_acl
image: redis:6.2-alpine
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
ports:
- "6382:6379"
volumes:
- ./data/infra/redis/redis-acl.conf:/usr/local/etc/redis/redis.conf
shlink_mercure_proxy:
container_name: shlink_mercure_proxy
image: nginx:1.19.6-alpine
@@ -139,13 +175,13 @@ services:
shlink_mercure:
container_name: shlink_mercure
image: dunglas/mercure:v0.13
image: dunglas/mercure:v0.14
ports:
- "3080:80"
environment:
SERVER_NAME: ":80"
MERCURE_PUBLISHER_JWT_KEY: mercure_jwt_key
MERCURE_SUBSCRIBER_JWT_KEY: mercure_jwt_key
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_EXTRA_DIRECTIVES: "cors_origins https://app.shlink.io http://localhost:3000 http://127.0.0.1:3000"
shlink_rabbitmq:

View File

@@ -11,7 +11,7 @@ It exposes a shlink instance served with [openswoole](https://openswoole.com/),
The most basic way to run Shlink's docker image is by providing these mandatory env vars.
* `DEFAULT_DOMAIN`: The default short domain used for this shlink instance. For example **doma.in**.
* `DEFAULT_DOMAIN`: The default short domain used for this shlink instance. For example **s.test**.
* `IS_HTTPS_ENABLED`: Either **true** or **false**. Tells if Shlink is being served with HTTPs or not.
* `GEOLITE_LICENSE_KEY`: Your GeoLite2 license key. [Learn more](https://shlink.io/documentation/geolite-license-key/) about this.
@@ -21,7 +21,7 @@ To run shlink on top of a local docker service, and using an internal SQLite dat
docker run \
--name shlink \
-p 8080:8080 \
-e DEFAULT_DOMAIN=doma.in \
-e DEFAULT_DOMAIN=s.test \
-e IS_HTTPS_ENABLED=true \
-e GEOLITE_LICENSE_KEY=kjh23ljkbndskj345 \
shlinkio/shlink:stable

View File

@@ -1,25 +0,0 @@
#!/bin/bash
set -ex
PLATFORMS="linux/arm/v7,linux/arm64/v8,linux/amd64"
DOCKER_IMAGE="shlinkio/shlink"
# If ref is not develop, then this is a tag. Build that docker tag and also "stable"
if [[ "$GITHUB_REF" != *"develop"* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
TAGS="-t ${DOCKER_IMAGE}:${VERSION}"
# Push stable tag only if this is not an alpha or beta tag
[[ $GITHUB_REF != *"alpha"* && $GITHUB_REF != *"beta"* ]] && TAGS="${TAGS} -t ${DOCKER_IMAGE}:stable"
docker buildx build --push \
--build-arg SHLINK_VERSION=${VERSION} \
--platform ${PLATFORMS} \
${TAGS} .
# If build branch is develop, build latest
elif [[ "$GITHUB_REF" == *"develop"* ]]; then
docker buildx build --push \
--platform ${PLATFORMS} \
-t ${DOCKER_IMAGE}:latest .
fi

View File

@@ -6,11 +6,14 @@ namespace Shlinkio\Shlink;
use Shlinkio\Shlink\Common\Logger\LoggerType;
use function Shlinkio\Shlink\Config\runningInRoadRunner;
return [
'logger' => [
'Shlink' => [
'type' => LoggerType::STREAM->value,
'destination' => runningInRoadRunner() ? 'php://stderr' : 'php://stdout',
],
],

View File

@@ -13,24 +13,36 @@ echo "Updating database..."
php bin/cli db:migrate -n ${flags}
echo "Generating proxies..."
php vendor/doctrine/orm/bin/doctrine.php orm:generate-proxies -n ${flags}
php bin/doctrine orm:generate-proxies -n ${flags}
echo "Clearing entities cache..."
php vendor/doctrine/orm/bin/doctrine.php orm:clear-cache:metadata -n ${flags}
php bin/doctrine orm:clear-cache:metadata -n ${flags}
# Try to download GeoLite2 db file only if the license key env var was defined
if [ ! -z "${GEOLITE_LICENSE_KEY}" ]; then
# Try to download GeoLite2 db file only if the license key env var was defined and skipping was not explicitly set
if [ ! -z "${GEOLITE_LICENSE_KEY}" ] && [ "${SKIP_INITIAL_GEOLITE_DOWNLOAD}" != "true" ]; then
echo "Downloading GeoLite2 db file..."
php bin/cli visit:download-db -n ${flags}
fi
# Periodically run visit:locate every hour, if ENABLE_PERIODIC_VISIT_LOCATE=true was provided
if [ $ENABLE_PERIODIC_VISIT_LOCATE ]; then
if [ "${ENABLE_PERIODIC_VISIT_LOCATE}" = "true" ]; then
echo "Configuring periodic visit location..."
echo "0 * * * * php /etc/shlink/bin/cli visit:locate -q" > /etc/crontabs/root
/usr/sbin/crond &
fi
# When restarting the container, openswoole might think it is already in execution
# This forces the app to be started every second until the exit code is 0
until php vendor/bin/laminas mezzio:swoole:start; do sleep 1 ; done
# RoadRunner config needs these to have been set, so falling back to default values if not set yet
if [ "$SHLINK_RUNTIME" == 'rr' ]; then
export PORT="${PORT:-"8080"}"
# Default to 0 so that RoadRunner decides the number of workers based on the amount of logical CPUs
export WEB_WORKER_NUM="${WEB_WORKER_NUM:-"0"}"
export TASK_WORKER_NUM="${TASK_WORKER_NUM:-"0"}"
fi
if [ "$SHLINK_RUNTIME" == 'openswoole' ]; then
# When restarting the container, openswoole might think it is already in execution
# This forces the app to be started every second until the exit code is 0
until php vendor/bin/laminas mezzio:swoole:start; do sleep 1 ; done
elif [ "$SHLINK_RUNTIME" == 'rr' ]; then
./bin/rr serve -c config/roadrunner/.rr.yml
fi

View File

@@ -0,0 +1,77 @@
# Support any HTTP method in short URLs
* Status: Accepted
* Date: 2023-01-06
## Context and problem statement
There has been a report that Shlink behaves as if a short URL was not found when the request HTTP method is not `GET`.
They want it to accept other methods so that they can do things like POSTing stuff that then gets "redirected" to the original URL.
This presents two main problems:
* Changing this could be considered a breaking change, in case someone is relying on this behavior (Shlink to only redirect on `GET`).
* Shlink currently supports two redirect statuses ([301](https://httpwg.org/specs/rfc9110.html#status.301) and [302](https://httpwg.org/specs/rfc9110.html#status.302)), which can be configured by the server admin.
For historical reasons, a client might switch from the original method to `GET` when any of these is returned, not resulting in the desired behavior anyway.
Instead, statuses [308](https://httpwg.org/specs/rfc9110.html#status.308) and [307](https://httpwg.org/specs/rfc9110.html#status.307) should be used.
## Considered options
There's actually two problems to solve here. Some combinations are implicitly required:
* **To support other HTTP methods in short URLs**
* Start supporting all HTTP methods.
* Introduce a feature flag to allow users decide if they want to support all methods or just `GET`.
* **To support other redirects statuses (308 and 307)**
* Switch to status 308 and 307 and stop using 301 and 302.
* Allow users to configure which of the 4 status codes they want to use, insteadof just supporting 301 and 302.
* Allow users to configure between two combinations: 301+308 and 302+307, using 301 or 302 for `GET` requests, and 308 or 307 for the rest.
> **Note**
> I asked on social networks, and these were the results (not too many answers though):
> * https://fosstodon.org/@shlinkio/109626773392324128
> * https://twitter.com/shlinkio/status/1610347091741507585
## Decision outcome
Because of backwards compatibility, it feels like the bets option is allowing to configure between 301, 302, 308 and 307.
This has the benefit that we can keep existing behavior intact. Existing instances will continue working only on `GET`, with statuses 301 or 302.
Anyone who wants to opt-in, can switch to 308 or 307, and the short URLs will transparently work on other HTTP methods in that case.
The only drawback is that this difference in the behavior when 308 or 307 are configured needs to be documented, and explained in shlink-installer.
## Pros and Cons of the Options
### Start supporting all HTTP methods
* Good: Because the change in code is pretty simple.
* Bad: Because it would be potentially a breaking change for anyone trusting current behavior for anything.
### Support HTTP methods via feature flag
* Good: because it would be safer for existing instances and opt-in for anyone interested in this change of behavior.
* Bad: Because it requires more changes in code.
* Bad: Because it requires a new config entry in the shlink-installer.
### Switch to statuses 308 and 307
* Good: Because we keep supporting just two status codes.
* Bad: Because it requires applying mapping/transformation to convert old configurations.
* Bad: Because it requires changes in shlink-installer.
### Allow users to configure between 301, 302, 308 and 307
* Good: Because it's fully backwards compatible with existing configs.
* Good: Because it would implicitly allow enabling all HTTP methods if 308 or 307 are selected, and keep only `GET` for 301 and 302, without the need for a separated feature flag.
* Bad: Because it requires dynamically supporting only `GET` or all methods, depending on the selected status.
### Allow users to configure between 301+308 or 302+307
* Good: Because it would allow a more explicit redirects config, where values are not 301 and 302, but something like "permanent" and "temporary".
* Bad: Because it implicitly changes the behavior of existing instances, making them respond to redirects with a method other than `GET`, and with a status code other than the one they explicitly configured.
* Bad: because existing `REDIRECT_STATUS_CODE` env var might not make sense anymore, requiring a new one and logic to map from one to another.

View File

@@ -2,6 +2,7 @@
Here listed you will find the different architectural decisions taken in the project, including all the reasoning behind it, options considered, and final outcome.
* [2023-01-06 Support any HTTP method in short URLs](2023-01-06-support-any-http-method-in-short-urls.md)
* [2022-08-05 Support multi-segment custom slugs](2022-08-05-support-multi-segment-custom-slugs.md)
* [2022-01-15 Update env vars behavior to have precedence over installer options](2022-01-15-update-env-vars-behavior-to-have-precedence-over-installer-options.md)
* [2021-08-05 Migrate to a new caching library](2021-08-05-migrate-to-a-new-caching-library.md)

View File

@@ -111,12 +111,19 @@
"type": "string",
"description": "The original long URL."
},
"deviceLongUrls": {
"$ref": "#/components/schemas/DeviceLongUrls"
},
"dateCreated": {
"type": "string",
"format": "date-time",
"description": "The date in which the short URL was created in ISO format."
},
"visitsSummary": {
"$ref": "#/components/schemas/VisitsSummary"
},
"visitsCount": {
"deprecated": true,
"type": "integer",
"description": "The number of visits that this short URL has received."
},
@@ -146,10 +153,19 @@
},
"example": {
"shortCode": "12C18",
"shortUrl": "https://doma.in/12C18",
"shortUrl": "https://s.test/12C18",
"longUrl": "https://store.steampowered.com",
"deviceLongUrls": {
"android": "https://store.steampowered.com/android",
"ios": "https://store.steampowered.com/ios",
"desktop": null
},
"dateCreated": "2016-08-21T20:34:16+02:00",
"visitsCount": 328,
"visitsSummary": {
"total": 328,
"nonBots": 285,
"bots": 43
},
"tags": [
"games",
"tech"
@@ -189,6 +205,42 @@
}
}
},
"VisitsSummary": {
"type": "object",
"required": ["total", "nonBots", "bots"],
"properties": {
"total": {
"description": "The total amount of visits",
"type": "number"
},
"nonBots": {
"description": "The amount of visits which were not identified as bots",
"type": "number"
},
"bots": {
"description": "The amount of visits that were identified as potential bots",
"type": "number"
}
}
},
"DeviceLongUrls": {
"type": "object",
"required": ["android", "ios", "desktop"],
"properties": {
"android": {
"description": "The long URL to redirect to when the short URL is visited from a device running Android",
"type": "string"
},
"ios": {
"description": "The long URL to redirect to when the short URL is visited from a device running iOS",
"type": "string"
},
"desktop": {
"description": "The long URL to redirect to when the short URL is visited from a desktop browser",
"type": "string"
}
}
},
"Visit": {
"type": "object",
"properties": {
@@ -266,7 +318,7 @@
"timezone": "America/Los_Angeles"
},
"potentialBot": false,
"visitedUrl": "https://doma.in",
"visitedUrl": "https://s.test",
"type": "base_url"
}
},

View File

@@ -0,0 +1,20 @@
{
"type": "object",
"properties": {
"android": {
"description": "The long URL to redirect to when the short URL is visited from a device running Android",
"type": "string",
"nullable": false
},
"ios": {
"description": "The long URL to redirect to when the short URL is visited from a device running iOS",
"type": "string",
"nullable": false
},
"desktop": {
"description": "The long URL to redirect to when the short URL is visited from a desktop browser",
"type": "string",
"nullable": false
}
}
}

View File

@@ -0,0 +1,17 @@
{
"type": "object",
"allOf": [{
"$ref": "./DeviceLongUrls.json"
}],
"properties": {
"android": {
"nullable": true
},
"ios": {
"nullable": true
},
"desktop": {
"nullable": true
}
}
}

View File

@@ -0,0 +1,7 @@
{
"type": "object",
"required": ["android", "ios", "desktop"],
"allOf": [{
"$ref": "./DeviceLongUrlsEdit.json"
}]
}

View File

@@ -4,8 +4,10 @@
"shortCode",
"shortUrl",
"longUrl",
"deviceLongUrls",
"dateCreated",
"visitsCount",
"visitsSummary",
"tags",
"meta",
"domain",
@@ -26,14 +28,21 @@
"type": "string",
"description": "The original long URL."
},
"deviceLongUrls": {
"$ref": "./DeviceLongUrlsResp.json"
},
"dateCreated": {
"type": "string",
"format": "date-time",
"description": "The date in which the short URL was created in ISO format."
},
"visitsCount": {
"deprecated": true,
"type": "integer",
"description": "The number of visits that this short URL has received."
"description": "**[DEPRECATED]** Use `visitsSummary.total` instead."
},
"visitsSummary": {
"$ref": "./VisitsSummary.json"
},
"tags": {
"type": "array",

View File

@@ -5,6 +5,9 @@
"description": "The long URL this short URL will redirect to",
"type": "string"
},
"deviceLongUrls": {
"$ref": "./DeviceLongUrlsEdit.json"
},
"validSince": {
"description": "The date (in ISO-8601 format) from which this short code will be valid",
"type": "string",
@@ -21,7 +24,8 @@
"nullable": true
},
"validateUrl": {
"description": "Tells if the long URL (if provided) should or should not be validated as a reachable URL. If not provided, it will fall back to app-level config",
"deprecated": true,
"description": "**[DEPRECATED]** Tells if the long URL should or should not be validated as a reachable URL. Defaults to `false`",
"type": "boolean"
},
"tags": {

View File

@@ -1,5 +1,6 @@
{
"type": "object",
"required": ["tag", "shortUrlsCount", "visitsSummary", "visitsCount"],
"properties": {
"tag": {
"type": "string",
@@ -9,9 +10,13 @@
"type": "number",
"description": "The amount of short URLs using this tag"
},
"userAgent": {
"visitsSummary": {
"$ref": "./VisitsSummary.json"
},
"visitsCount": {
"deprecated": true,
"type": "number",
"description": "The combined amount of visits received by short URLs with this tag"
"description": "**[DEPRECATED]** Use visitsSummary.total instead"
}
}
}

View File

@@ -1,14 +1,22 @@
{
"type": "object",
"required": ["visitsCount", "orphanVisitsCount"],
"required": ["nonOrphanVisits", "orphanVisits", "visitsCount", "orphanVisitsCount"],
"properties": {
"nonOrphanVisits": {
"$ref": "./VisitsSummary.json"
},
"orphanVisits": {
"$ref": "./VisitsSummary.json"
},
"visitsCount": {
"deprecated": true,
"type": "number",
"description": "The total amount of visits received on any short URL."
"description": "**[DEPRECATED]** Use nonOrphanVisits.total instead"
},
"orphanVisitsCount": {
"deprecated": true,
"type": "number",
"description": "The total amount of visits that could not be matched to a short URL (visits to the base URL, an invalid short URL or any other kind of 404)."
"description": "**[DEPRECATED]** Use orphanVisits.total instead"
}
}
}

View File

@@ -0,0 +1,18 @@
{
"type": "object",
"required": ["total", "nonBots", "bots"],
"properties": {
"total": {
"description": "The total amount of visits.",
"type": "integer"
},
"nonBots": {
"description": "The amount of visits which were not identified as bots.",
"type": "integer"
},
"bots": {
"description": "The amount of visits that were identified as potential bots.",
"type": "integer"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"value": {
"title": "Invalid data",
"type": "https://shlink.io/api/error/invalid-data",
"detail": "Provided data is not valid",
"status": 400,
"invalidElements": ["maxVisits", "validSince"]
}
}

View File

@@ -0,0 +1,9 @@
{
"value": {
"detail": "No URL found with short code \"abc123\"",
"title": "Short URL not found",
"type": "https://shlink.io/api/error/short-url-not-found",
"status": 404,
"shortCode": "abc123"
}
}

View File

@@ -0,0 +1,9 @@
{
"value": {
"detail": "Tag with name \"foo\" could not be found",
"title": "Tag not found",
"type": "https://shlink.io/api/error/tag-not-found",
"status": 404,
"tag": "foo"
}
}

View File

@@ -6,6 +6,7 @@
"schema": {
"type": "string",
"enum": [
"3",
"2",
"1"
]

View File

@@ -73,10 +73,12 @@
"shortCode-DESC",
"dateCreated-ASC",
"dateCreated-DESC",
"title-ASC",
"title-DESC",
"visits-ASC",
"visits-DESC",
"title-ASC",
"title-DESC"
"nonBotVisits-ASC",
"nonBotVisits-DESC"
]
}
},
@@ -97,6 +99,32 @@
"schema": {
"type": "string"
}
},
{
"name": "excludeMaxVisitsReached",
"in": "query",
"description": "If true, short URLs which already reached their maximum amount of visits will be excluded.",
"required": false,
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
}
},
{
"name": "excludePastValidUntil",
"in": "query",
"description": "If true, short URLs which validUntil date is on the past will be excluded.",
"required": false,
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
}
}
],
"security": [
@@ -133,10 +161,19 @@
"data": [
{
"shortCode": "12C18",
"shortUrl": "https://doma.in/12C18",
"shortUrl": "https://s.test/12C18",
"longUrl": "https://store.steampowered.com",
"deviceLongUrls": {
"android": null,
"ios": null,
"desktop": null
},
"dateCreated": "2016-08-21T20:34:16+02:00",
"visitsCount": 328,
"visitsSummary": {
"total": 328,
"nonBots": 328,
"bots": 0
},
"tags": [
"games",
"tech"
@@ -152,10 +189,19 @@
},
{
"shortCode": "12Kb3",
"shortUrl": "https://doma.in/12Kb3",
"shortUrl": "https://s.test/12Kb3",
"longUrl": "https://shlink.io",
"deviceLongUrls": {
"android": null,
"ios": "https://shlink.io/ios",
"desktop": null
},
"dateCreated": "2016-05-01T20:34:16+02:00",
"visitsCount": 1029,
"visitsSummary": {
"total": 1029,
"nonBots": 900,
"bots": 129
},
"tags": [
"shlink"
],
@@ -172,8 +218,17 @@
"shortCode": "123bA",
"shortUrl": "https://example.com/123bA",
"longUrl": "https://www.google.com",
"deviceLongUrls": {
"android": null,
"ios": null,
"desktop": null
},
"dateCreated": "2015-10-01T20:34:16+02:00",
"visitsCount": 25,
"visitsSummary": {
"total": 25,
"nonBots": 0,
"bots": 25
},
"tags": [],
"meta": {
"validSince": "2017-01-21T00:00:00+02:00",
@@ -241,6 +296,9 @@
"type": "object",
"required": ["longUrl"],
"properties": {
"deviceLongUrls": {
"$ref": "../definitions/DeviceLongUrls.json"
},
"customSlug": {
"description": "A unique custom slug to be used instead of the generated short code",
"type": "string"
@@ -256,10 +314,6 @@
"shortCodeLength": {
"description": "The length for generated short code. It has to be at least 4 and defaults to 5. It will be ignored when customSlug is provided",
"type": "number"
},
"validateUrl": {
"description": "Tells if the long URL should or should not be validated as a reachable URL. If not provided, it will fall back to app-level config",
"type": "boolean"
}
}
}
@@ -278,10 +332,19 @@
},
"example": {
"shortCode": "12C18",
"shortUrl": "https://doma.in/12C18",
"shortUrl": "https://s.test/12C18",
"longUrl": "https://store.steampowered.com",
"deviceLongUrls": {
"android": null,
"ios": null,
"desktop": null
},
"dateCreated": "2016-08-21T20:34:16+02:00",
"visitsCount": 0,
"visitsSummary": {
"total": 0,
"nonBots": 0,
"bots": 0
},
"tags": [
"games",
"tech"
@@ -327,11 +390,11 @@
},
"url": {
"type": "string",
"description": "A URL that could not be verified, if the error type is INVALID_URL"
"description": "A URL that could not be verified, if the error type is https://shlink.io/api/error/invalid-url"
},
"customSlug": {
"type": "string",
"description": "Provided custom slug when the error type is INVALID_SLUG"
"description": "Provided custom slug when the error type is https://shlink.io/api/error/non-unique-slug"
},
"domain": {
"type": "string",
@@ -342,10 +405,31 @@
]
},
"examples": {
"Invalid arguments": {
"$ref": "../examples/short-url-invalid-args.json"
"Invalid arguments with API v3 and newer": {
"$ref": "../examples/short-url-invalid-args-v3.json"
},
"Invalid long URL": {
"Invalid long URL with API v3 and newer": {
"value": {
"title": "Invalid URL",
"type": "https://shlink.io/api/error/invalid-url",
"detail": "Provided URL foo is invalid. Try with a different one.",
"status": 400,
"url": "https://invalid-url.com"
}
},
"Non-unique slug with API v3 and newer": {
"value": {
"title": "Invalid custom slug",
"type": "https://shlink.io/api/error/non-unique-slug",
"detail": "Provided slug \"my-slug\" is already in use.",
"status": 400,
"customSlug": "my-slug"
}
},
"Invalid arguments previous to API v3": {
"$ref": "../examples/short-url-invalid-args-v2.json"
},
"Invalid long URL previous to API v3": {
"value": {
"title": "Invalid URL",
"type": "INVALID_URL",
@@ -354,7 +438,7 @@
"url": "https://invalid-url.com"
}
},
"Non-unique slug": {
"Non-unique slug previous to API v3": {
"value": {
"title": "Invalid custom slug",
"type": "INVALID_SLUG",

View File

@@ -1,11 +1,12 @@
{
"get": {
"operationId": "shortenUrl",
"deprecated": true,
"tags": [
"Short URLs"
],
"summary": "Create a short URL",
"description": "Creates a short URL in a single API call. Useful for third party integrations.",
"description": "**[Deprecated]** Use [Create short URL](#/Short%20URLs/createShortUrl) instead",
"parameters": [
{
"$ref": "../parameters/version.json"
@@ -52,10 +53,19 @@
},
"example": {
"longUrl": "https://github.com/shlinkio/shlink",
"shortUrl": "https://doma.in/abc123",
"deviceLongUrls": {
"android": null,
"ios": null,
"desktop": null
},
"shortUrl": "https://s.test/abc123",
"shortCode": "abc123",
"dateCreated": "2016-08-21T20:34:16+02:00",
"visitsCount": 0,
"visitsSummary": {
"total": 0,
"nonBots": 0,
"bots": 0
},
"tags": [
"games",
"tech"
@@ -74,7 +84,7 @@
"schema": {
"type": "string"
},
"example": "https://doma.in/abc123"
"example": "https://s.test/abc123"
}
}
},
@@ -85,19 +95,39 @@
"schema": {
"$ref": "../definitions/Error.json"
},
"example": {
"title": "Invalid URL",
"type": "INVALID_URL",
"detail": "Provided URL foo is invalid. Try with a different one.",
"status": 400,
"url": "https://invalid-url.com"
"examples": {
"API v3 and newer": {
"value": {
"title": "Invalid URL",
"type": "https://shlink.io/api/error/invalid-url",
"detail": "Provided URL foo is invalid. Try with a different one.",
"status": 400,
"url": "https://invalid-url.com"
}
},
"Previous to API v3": {
"value": {
"title": "Invalid URL",
"type": "INVALID_URL",
"detail": "Provided URL foo is invalid. Try with a different one.",
"status": 400,
"url": "https://invalid-url.com"
}
}
}
},
"text/plain": {
"schema": {
"type": "string"
},
"example": "INVALID_URL"
"examples": {
"API v3 and newer": {
"value": "https://shlink.io/api/error/invalid-url"
},
"Previous to API v3": {
"value": "INVALID_URL"
}
}
}
}
},

View File

@@ -38,10 +38,19 @@
},
"example": {
"shortCode": "12Kb3",
"shortUrl": "https://doma.in/12Kb3",
"shortUrl": "https://s.test/12Kb3",
"longUrl": "https://shlink.io",
"deviceLongUrls": {
"android": null,
"ios": null,
"desktop": null
},
"dateCreated": "2016-05-01T20:34:16+02:00",
"visitsCount": 1029,
"visitsSummary": {
"total": 1029,
"nonBots": 820,
"bots": 209
},
"tags": [
"shlink"
],
@@ -83,8 +92,11 @@
]
},
"examples": {
"Not found": {
"$ref": "../examples/short-url-not-found.json"
"API v3 and newer": {
"$ref": "../examples/short-url-not-found-v3.json"
},
"Previous to API v3": {
"$ref": "../examples/short-url-not-found-v2.json"
}
}
}
@@ -153,10 +165,19 @@
},
"example": {
"shortCode": "12Kb3",
"shortUrl": "https://doma.in/12Kb3",
"shortUrl": "https://s.test/12Kb3",
"longUrl": "https://shlink.io",
"deviceLongUrls": {
"android": "https://shlink.io/android",
"ios": null,
"desktop": null
},
"dateCreated": "2016-05-01T20:34:16+02:00",
"visitsCount": 1029,
"visitsSummary": {
"total": 1029,
"nonBots": 900,
"bots": 129
},
"tags": [
"shlink"
],
@@ -203,8 +224,11 @@
]
},
"examples": {
"Invalid arguments": {
"$ref": "../examples/short-url-invalid-args.json"
"API v3 and newer": {
"$ref": "../examples/short-url-invalid-args-v3.json"
},
"Previous to API v3": {
"$ref": "../examples/short-url-invalid-args-v2.json"
}
}
}
@@ -236,8 +260,11 @@
]
},
"examples": {
"Not found": {
"$ref": "../examples/short-url-not-found.json"
"API v3 and newer": {
"$ref": "../examples/short-url-not-found-v3.json"
},
"Previous to API v3": {
"$ref": "../examples/short-url-not-found-v2.json"
}
}
}
@@ -318,13 +345,27 @@
}
]
},
"example": {
"title": "Cannot delete short URL",
"type": "INVALID_SHORT_URL_DELETION",
"detail": "Impossible to delete short URL with short code \"abc123\", since it has more than \"15\" visits.",
"status": 422,
"shortCode": "abc123",
"threshold": 15
"examples": {
"API v3 and newer": {
"value": {
"title": "Cannot delete short URL",
"type": "https://shlink.io/api/error/invalid-short-url-deletion",
"detail": "Impossible to delete short URL with short code \"abc123\", since it has more than \"15\" visits.",
"status": 422,
"shortCode": "abc123",
"threshold": 15
}
},
"Previous to API v3": {
"value": {
"title": "Cannot delete short URL",
"type": "INVALID_SHORT_URL_DELETION",
"detail": "Impossible to delete short URL with short code \"abc123\", since it has more than \"15\" visits.",
"status": 422,
"shortCode": "abc123",
"threshold": 15
}
}
}
}
}
@@ -355,8 +396,11 @@
]
},
"examples": {
"Not found": {
"$ref": "../examples/short-url-not-found.json"
"API v3 and newer": {
"$ref": "../examples/short-url-not-found-v3.json"
},
"Previous to API v3": {
"$ref": "../examples/short-url-not-found-v2.json"
}
}
}

View File

@@ -151,8 +151,11 @@
"$ref": "../definitions/Error.json"
},
"examples": {
"Short URL not found": {
"$ref": "../examples/short-url-not-found.json"
"Short URL not found with API v3 and newer": {
"$ref": "../examples/short-url-not-found-v3.json"
},
"Short URL not found previous to API v3": {
"$ref": "../examples/short-url-not-found-v2.json"
}
}
}

View File

@@ -188,12 +188,25 @@
"schema": {
"$ref": "../definitions/Error.json"
},
"example": {
"title": "Invalid data",
"type": "INVALID_ARGUMENT",
"detail": "Provided data is not valid",
"status": 400,
"invalidElements": ["oldName", "newName"]
"examples": {
"API v3 and newer": {
"value": {
"title": "Invalid data",
"type": "https://shlink.io/api/error/invalid-data",
"detail": "Provided data is not valid",
"status": 400,
"invalidElements": ["oldName", "newName"]
}
},
"Previous to API v3": {
"value": {
"title": "Invalid data",
"type": "INVALID_ARGUMENT",
"detail": "Provided data is not valid",
"status": 400,
"invalidElements": ["oldName", "newName"]
}
}
}
}
}
@@ -205,11 +218,23 @@
"schema": {
"$ref": "../definitions/Error.json"
},
"example": {
"detail": "You are not allowed to rename tags",
"title": "Forbidden tag operation",
"type": "FORBIDDEN_OPERATION",
"status": 403
"examples": {
"API v3 and newer": {
"value": {
"detail": "You are not allowed to rename tags",
"title": "Forbidden tag operation",
"type": "https://shlink.io/api/error/forbidden-tag-operation",
"status": 403
}
},
"Previous to API v3": {
"value": {
"detail": "You are not allowed to rename tags",
"title": "Forbidden tag operation",
"type": "FORBIDDEN_OPERATION",
"status": 403
}
}
}
}
}
@@ -222,8 +247,11 @@
"$ref": "../definitions/Error.json"
},
"examples": {
"Tag not found": {
"$ref": "../examples/tag-not-found.json"
"API v3 and newer": {
"$ref": "../examples/tag-not-found-v3.json"
},
"Previous to API v3": {
"$ref": "../examples/tag-not-found-v2.json"
}
}
}
@@ -236,13 +264,27 @@
"schema": {
"$ref": "../definitions/Error.json"
},
"example": {
"detail": "You cannot rename tag foo, because it already exists",
"title": "Tag conflict",
"type": "TAG_CONFLICT",
"status": 409,
"oldName": "bar",
"newName": "foo"
"examples": {
"API v3 and newer": {
"value": {
"detail": "You cannot rename tag foo, because it already exists",
"title": "Tag conflict",
"type": "https://shlink.io/api/error/tag-conflict",
"status": 409,
"oldName": "bar",
"newName": "foo"
}
},
"Previous to API v3": {
"value": {
"detail": "You cannot rename tag foo, because it already exists",
"title": "Tag conflict",
"type": "TAG_CONFLICT",
"status": 409,
"oldName": "bar",
"newName": "foo"
}
}
}
}
}
@@ -300,11 +342,23 @@
"schema": {
"$ref": "../definitions/Error.json"
},
"example": {
"detail": "You are not allowed to delete tags",
"title": "Forbidden tag operation",
"type": "FORBIDDEN_OPERATION",
"status": 403
"examples": {
"API v3 and newer": {
"value": {
"detail": "You are not allowed to delete tags",
"title": "Forbidden tag operation",
"type": "https://shlink.io/api/error/forbidden-tag-operation",
"status": 403
}
},
"Previous to API v3": {
"value": {
"detail": "You are not allowed to delete tags",
"title": "Forbidden tag operation",
"type": "FORBIDDEN_OPERATION",
"status": 403
}
}
}
}
}

View File

@@ -94,12 +94,25 @@
}
]
},
"example": {
"title": "Invalid data",
"type": "INVALID_ARGUMENT",
"detail": "Provided data is not valid",
"status": 400,
"invalidElements": ["domain", "invalidShortUrlRedirect"]
"examples": {
"API v3 and newer": {
"value": {
"title": "Invalid data",
"type": "https://shlink.io/api/error/invalid-data",
"detail": "Provided data is not valid",
"status": 400,
"invalidElements": ["domain", "invalidShortUrlRedirect"]
}
},
"Previous to API v3": {
"value": {
"title": "Invalid data",
"type": "INVALID_ARGUMENT",
"detail": "Provided data is not valid",
"status": 400,
"invalidElements": ["domain", "invalidShortUrlRedirect"]
}
}
}
}
}

View File

@@ -147,12 +147,25 @@
"schema": {
"$ref": "../definitions/Error.json"
},
"example": {
"detail": "Domain with authority \"example.com\" could not be found",
"title": "Domain not found",
"type": "DOMAIN_NOT_FOUND",
"status": 404,
"authority": "example.com"
"examples": {
"API v3 and newer": {
"value": {
"detail": "Domain with authority \"example.com\" could not be found",
"title": "Domain not found",
"type": "https://shlink.io/api/error/domain-not-found",
"status": 404,
"authority": "example.com"
}
},
"Previous to API v3": {
"value": {
"detail": "Domain with authority \"example.com\" could not be found",
"title": "Domain not found",
"type": "DOMAIN_NOT_FOUND",
"status": 404,
"authority": "example.com"
}
}
}
}
}

View File

@@ -39,11 +39,23 @@
"schema": {
"$ref": "../definitions/Error.json"
},
"example": {
"title": "Mercure integration not configured",
"type": "MERCURE_NOT_CONFIGURED",
"detail": "This Shlink instance is not integrated with a mercure hub.",
"status": 501
"examples": {
"API v3 and newer": {
"value": {
"title": "Mercure integration not configured",
"type": "https://shlink.io/api/error/mercure-not-configured",
"detail": "This Shlink instance is not integrated with a mercure hub.",
"status": 501
}
},
"Previous to API v3": {
"value": {
"title": "Mercure integration not configured",
"type": "MERCURE_NOT_CONFIGURED",
"detail": "This Shlink instance is not integrated with a mercure hub.",
"status": 501
}
}
}
}
}

View File

@@ -45,7 +45,7 @@
{
"name": "orderBy",
"in": "query",
"description": "To determine how to order the results.<br /><br />**Important!** Ordering by `shortUrlsCount` or `visitsCount` has a [known performance issue](https://github.com/shlinkio/shlink/issues/1346) which makes loading a subset of the list take as much as loading the whole list.<br />If you plan to order by any of these fields, it's worth loading the whole list with no pagination.",
"description": "To determine how to order the results.<br /><br />**Important!** Ordering by `shortUrlsCount`, `visits` or `nonBotVisits` has a [known performance issue](https://github.com/shlinkio/shlink/issues/1346) which makes loading a subset of the list take as much as loading the whole list.<br />If you plan to order by any of these fields, it's worth loading the whole list with no pagination.",
"required": false,
"schema": {
"type": "string",
@@ -54,8 +54,10 @@
"tag-DESC",
"shortUrlsCount-ASC",
"shortUrlsCount-DESC",
"visitsCount-ASC",
"visitsCount-DESC"
"visits-ASC",
"visits-DESC",
"nonBotVisits-ASC",
"nonBotVisits-DESC"
]
}
}
@@ -73,7 +75,6 @@
"required": ["data"],
"properties": {
"data": {
"description": "The tag stats will be returned only if the withStats param was provided with value 'true'",
"type": "array",
"items": {
"$ref": "../definitions/TagInfo.json"
@@ -92,12 +93,20 @@
{
"tag": "games",
"shortUrlsCount": 10,
"visitsCount": 521
"visitsSummary": {
"total": 521,
"nonBots": 521,
"bots": 0
}
},
{
"tag": "shlink",
"shortUrlsCount": 7,
"visitsCount": 1087
"visitsSummary": {
"total": 1087,
"nonBots": 1000,
"bots": 87
}
}
],
"pagination": {

View File

@@ -148,8 +148,12 @@
"$ref": "../definitions/Error.json"
},
"examples": {
"Tag not found": {
"$ref": "../examples/tag-not-found.json"
"API v3 and newer": {
"$ref": "../examples/tag-not-found-v3.json"
},
"Previous to API v3": {
"$ref": "../examples/tag-not-found-v2.json"
}
}
}

View File

@@ -31,8 +31,16 @@
},
"example": {
"visits": {
"visitsCount": 1569874,
"orphanVisitsCount": 71345
"nonOrphanVisits": {
"total": 64994,
"nonBots": 64986,
"bots": 8
},
"orphanVisits": {
"total": 37,
"nonBots": 34,
"bots": 3
}
}
}
}

View File

@@ -95,7 +95,7 @@
"userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0",
"visitLocation": null,
"potentialBot": false,
"visitedUrl": "https://doma.in",
"visitedUrl": "https://s.test",
"type": "base_url"
},
{
@@ -112,7 +112,7 @@
"timezone": "America/Los_Angeles"
},
"potentialBot": false,
"visitedUrl": "https://doma.in/foo",
"visitedUrl": "https://s.test/foo",
"type": "invalid_short_url"
},
{
@@ -121,7 +121,7 @@
"userAgent": "some_web_crawler/1.4",
"visitLocation": null,
"potentialBot": true,
"visitedUrl": "https://doma.in/foo/bar/baz",
"visitedUrl": "https://s.test/foo/bar/baz",
"type": "regular_404"
}
],

View File

@@ -3,7 +3,7 @@
"info": {
"title": "Shlink",
"description": "Shlink, the self-hosted URL shortener",
"version": "2.0"
"version": "3.0"
},
"externalDocs": {

View File

@@ -1,24 +0,0 @@
{
"source": {
"directories": [
"module/*/src"
]
},
"timeout": 5,
"logs": {
"text": "build/infection-api/infection-log.txt",
"html": "build/infection-api/infection-log.html",
"summary": "build/infection-api/summary-log.txt",
"debug": "build/infection-api/debug-log.txt"
},
"tmpDir": "build/infection-api/temp",
"phpUnit": {
"configDir": "."
},
"testFrameworkOptions": "--configuration=phpunit-api.xml",
"mutators": {
"@default": true,
"IdenticalEqual": false,
"NotIdenticalNotEqual": false
}
}

24
infection-api.json5 Normal file
View File

@@ -0,0 +1,24 @@
{
source: {
directories: [
'module/*/src'
]
},
timeout: 5,
logs: {
text: 'build/infection-api/infection-log.txt',
html: 'build/infection-api/infection-log.html',
summary: 'build/infection-api/summary-log.txt',
debug: 'build/infection-api/debug-log.txt'
},
tmpDir: 'build/infection-api/temp',
phpUnit: {
configDir: '.'
},
testFrameworkOptions: '--configuration=phpunit-api.xml',
mutators: {
'@default': true,
IdenticalEqual: false,
NotIdenticalNotEqual: false
}
}

24
infection-cli.json5 Normal file
View File

@@ -0,0 +1,24 @@
{
source: {
directories: [
'module/*/src'
]
},
timeout: 5,
logs: {
text: 'build/infection-cli/infection-log.txt',
html: 'build/infection-cli/infection-log.html',
summary: 'build/infection-cli/summary-log.txt',
debug: 'build/infection-cli/debug-log.txt'
},
tmpDir: 'build/infection-cli/temp',
phpUnit: {
configDir: '.'
},
testFrameworkOptions: '--configuration=phpunit-cli.xml',
mutators: {
'@default': true,
IdenticalEqual: false,
NotIdenticalNotEqual: false
}
}

View File

@@ -1,24 +0,0 @@
{
"source": {
"directories": [
"module/*/src"
]
},
"timeout": 5,
"logs": {
"text": "build/infection-db/infection-log.txt",
"html": "build/infection-db/infection-log.html",
"summary": "build/infection-db/summary-log.txt",
"debug": "build/infection-db/debug-log.txt"
},
"tmpDir": "build/infection-db/temp",
"phpUnit": {
"configDir": "."
},
"testFrameworkOptions": "--configuration=phpunit-db.xml",
"mutators": {
"@default": true,
"IdenticalEqual": false,
"NotIdenticalNotEqual": false
}
}

24
infection-db.json5 Normal file
View File

@@ -0,0 +1,24 @@
{
source: {
directories: [
'module/*/src'
]
},
timeout: 5,
logs: {
text: 'build/infection-db/infection-log.txt',
html: 'build/infection-db/infection-log.html',
summary: 'build/infection-db/summary-log.txt',
debug: 'build/infection-db/debug-log.txt'
},
tmpDir: 'build/infection-db/temp',
phpUnit: {
configDir: '.'
},
testFrameworkOptions: '--configuration=phpunit-db.xml',
mutators: {
'@default': true,
IdenticalEqual: false,
NotIdenticalNotEqual: false
}
}

View File

@@ -1,26 +0,0 @@
{
"source": {
"directories": [
"module/*/src"
]
},
"timeout": 5,
"logs": {
"text": "build/infection-unit/infection-log.txt",
"html": "build/infection-unit/infection-log.html",
"summary": "build/infection-unit/summary-log.txt",
"debug": "build/infection-unit/debug-log.txt",
"stryker": {
"report": "develop"
}
},
"tmpDir": "build/infection-unit/temp",
"phpUnit": {
"configDir": "."
},
"mutators": {
"@default": true,
"IdenticalEqual": false,
"NotIdenticalNotEqual": false
}
}

26
infection.json5 Normal file
View File

@@ -0,0 +1,26 @@
{
source: {
directories: [
'module/*/src'
]
},
timeout: 5,
logs: {
text: 'build/infection-unit/infection-log.txt',
html: 'build/infection-unit/infection-log.html',
summary: 'build/infection-unit/summary-log.txt',
debug: 'build/infection-unit/debug-log.txt',
stryker: {
report: 'develop'
}
},
tmpDir: 'build/infection-unit/temp',
phpUnit: {
configDir: '.'
},
mutators: {
'@default': true,
IdenticalEqual: false,
NotIdenticalNotEqual: false
}
}

View File

@@ -12,14 +12,12 @@ use Shlinkio\Shlink\Common\Doctrine\NoDbNameConnectionFactory;
use Shlinkio\Shlink\Core\Domain\DomainService;
use Shlinkio\Shlink\Core\Options\TrackingOptions;
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
use Shlinkio\Shlink\Core\Service;
use Shlinkio\Shlink\Core\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Core\Tag\TagService;
use Shlinkio\Shlink\Core\Visit;
use Shlinkio\Shlink\Installer\Factory\ProcessHelperFactory;
use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdater;
use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface;
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
use Symfony\Component\Console as SymfonyCli;
use Symfony\Component\Lock\LockFactory;
@@ -35,7 +33,7 @@ return [
SymfonyCli\Helper\ProcessHelper::class => ProcessHelperFactory::class,
PhpExecutableFinder::class => InvokableFactory::class,
Util\GeolocationDbUpdater::class => ConfigAbstractFactory::class,
GeoLite\GeolocationDbUpdater::class => ConfigAbstractFactory::class,
Util\ProcessRunner::class => ConfigAbstractFactory::class,
ApiKey\RoleResolver::class => ConfigAbstractFactory::class,
@@ -70,7 +68,7 @@ return [
],
ConfigAbstractFactory::class => [
Util\GeolocationDbUpdater::class => [
GeoLite\GeolocationDbUpdater::class => [
DbUpdater::class,
Reader::class,
LOCAL_LOCK_FACTORY,
@@ -80,22 +78,22 @@ return [
ApiKey\RoleResolver::class => [DomainService::class, 'config.url_shortener.domain.hostname'],
Command\ShortUrl\CreateShortUrlCommand::class => [
Service\UrlShortener::class,
ShortUrl\UrlShortener::class,
ShortUrlStringifier::class,
UrlShortenerOptions::class,
],
Command\ShortUrl\ResolveUrlCommand::class => [Service\ShortUrl\ShortUrlResolver::class],
Command\ShortUrl\ResolveUrlCommand::class => [ShortUrl\ShortUrlResolver::class],
Command\ShortUrl\ListShortUrlsCommand::class => [
Service\ShortUrlService::class,
ShortUrlDataTransformer::class,
ShortUrl\ShortUrlListService::class,
ShortUrl\Transformer\ShortUrlDataTransformer::class,
],
Command\ShortUrl\GetShortUrlVisitsCommand::class => [Visit\VisitsStatsHelper::class],
Command\ShortUrl\DeleteShortUrlCommand::class => [Service\ShortUrl\DeleteShortUrlService::class],
Command\ShortUrl\DeleteShortUrlCommand::class => [ShortUrl\DeleteShortUrlService::class],
Command\Visit\DownloadGeoLiteDbCommand::class => [Util\GeolocationDbUpdater::class],
Command\Visit\DownloadGeoLiteDbCommand::class => [GeoLite\GeolocationDbUpdater::class],
Command\Visit\LocateVisitsCommand::class => [
Visit\VisitLocator::class,
IpLocationResolverInterface::class,
Visit\Geolocation\VisitLocator::class,
Visit\Geolocation\VisitToLocationHelper::class,
LockFactory::class,
],
Command\Visit\GetOrphanVisitsCommand::class => [Visit\VisitsStatsHelper::class],

Some files were not shown because too many files have changed in this diff Show More