Compare commits

...

270 Commits

Author SHA1 Message Date
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
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
28b9cd02ef Merge pull request #1501 from shlinkio/develop
Release 3.2.1
2022-08-08 19:51:00 +02:00
Alejandro Celaya
af9ea13933 Merge pull request #1500 from acelaya-forks/feature/fix-env-var-loading
Feature/fix env var loading
2022-08-08 19:46:53 +02:00
Alejandro Celaya
bd2cd18916 Tagged stable releases for all shlink teps 2022-08-08 19:33:59 +02:00
Alejandro Celaya
23138dc0b4 Updated changelog 2022-08-08 19:24:51 +02:00
Alejandro Celaya
a2f9742cfc Fix loading of config options as env vars 2022-08-08 19:23:16 +02:00
Alejandro Celaya
6378e614b0 Merge pull request #1498 from acelaya-forks/feature/update-shlink-deps
Feature/update shlink deps
2022-08-07 10:04:23 +02:00
Alejandro Celaya
b116a57aa7 Updated changelog 2022-08-07 09:37:49 +02:00
Alejandro Celaya
a03f32f521 Updated to latest shlink dependencies 2022-08-07 09:36:51 +02:00
Alejandro Celaya
b9180be685 Merge pull request #1496 from acelaya-forks/feature/centralize-multi-segment
Feature/centralize multi segment
2022-08-06 09:54:09 +02:00
Alejandro Celaya
334aee64ad Updated changelog 2022-08-06 09:37:15 +02:00
Alejandro Celaya
16bd368a58 Centralized how routes are configured to support multi-segment slugs 2022-08-06 09:30:13 +02:00
Alejandro Celaya
3266a0f85c Merge pull request #1494 from shlinkio/develop
Release 3.2.0
2022-08-05 19:04:45 +02:00
Alejandro Celaya
4629f1b03f Merge pull request #1493 from acelaya-forks/feature/update-deps
Updated to latest PHP version and native dependencies
2022-08-05 16:44:29 +02:00
Alejandro Celaya
fbd0c6cbea Merge pull request #1491 from acelaya-forks/feature/multi-segment-slugs
Feature/multi segment slugs
2022-08-05 16:31:38 +02:00
Alejandro Celaya
8260051c30 Updated to latest PHP version and native dependencies 2022-08-05 16:31:15 +02:00
Alejandro Celaya
c061c9c3ff Added v3.2.0 to changelog 2022-08-05 16:19:40 +02:00
Alejandro Celaya
8961191b2e Documented ADR for multi-segment slugs 2022-08-05 16:18:53 +02:00
Alejandro Celaya
fc0d99be41 Ensure filtering of custom-slug is different depending on the multi-sement lugsfeature flag 2022-08-05 08:38:05 +02:00
Alejandro Celaya
6834e72c4a Updated changelog 2022-08-04 17:15:35 +02:00
Alejandro Celaya
efe655f880 Enhanced ExtraPathRedirectMiddleware so that it supports multi-segment slugs 2022-08-04 17:03:08 +02:00
Alejandro Celaya
3d5ddce621 Ensured multi-segment feature flag affects how append_extra_path is checked 2022-08-04 16:10:54 +02:00
Alejandro Celaya
a3de3e15cb Updated installer with support for multi-segment slugs flag 2022-08-04 13:00:09 +02:00
Alejandro Celaya
619999d4f8 Added feature flag to enable/disable multi-segment support 2022-08-04 11:49:33 +02:00
Alejandro Celaya
7acf27dd38 Replaced usage of deprecated methods in DateRange class 2022-08-04 11:27:33 +02:00
Alejandro Celaya
ba517eeeb5 Moved routes config together, and ensure they are loaded last 2022-08-04 11:14:26 +02:00
Alejandro Celaya
fdd3e24967 Added support for multi-segment slugs 2022-08-03 19:32:59 +02:00
Alejandro Celaya
a570ce202a Updated to latest common 2022-08-03 12:59:09 +02:00
Alejandro Celaya
0a220bbc7a Allowed slashes on custom slugs during short URL creation 2022-08-01 17:32:54 +02:00
Alejandro Celaya
e0e511f56d Some improvements and comments in preparation of multi-segment slugs 2022-08-01 17:32:54 +02:00
Alejandro Celaya
d375dece0e Updated required deps 2022-08-01 17:32:54 +02:00
Alejandro Celaya
f801f265ed Added comments on places to change 2022-08-01 17:32:54 +02:00
Alejandro Celaya
1b4fc89b07 Merge pull request #1490 from acelaya-forks/feature/ci-composer-cache
Added cache for composer dependencies during CI
2022-08-01 17:32:07 +02:00
Alejandro Celaya
3ac2b77bf0 Removed composer cache due to a bug in github runner making it fail 2022-08-01 17:23:51 +02:00
Alejandro Celaya
b2ca4ad66b Migrated all workflows to ubuntu-22.04 2022-08-01 17:13:34 +02:00
Alejandro Celaya
25a7c7bc7f Added cache for composer dependencies during CI 2022-08-01 16:56:25 +02:00
Alejandro Celaya
6b009a4de4 Merge pull request #1489 from acelaya-forks/feature/command-error
Feature/command error
2022-08-01 12:25:05 +02:00
Alejandro Celaya
0b80a86e88 Updated changelog 2022-08-01 12:07:50 +02:00
Alejandro Celaya
b03f24d59a Ensured no arguments are passed form locate visits command to download geolite command, is it does not expect any 2022-08-01 12:06:38 +02:00
Alejandro Celaya
78ea13d366 Merge pull request #1488 from acelaya-forks/feature/redis-pub-sub
Feature/redis pub sub
2022-07-28 11:04:19 +02:00
Alejandro Celaya
8c2bdfba1c Refactored match to ifs with eary returns 2022-07-28 10:51:48 +02:00
Alejandro Celaya
3289968a93 Updated changelog 2022-07-28 10:46:24 +02:00
Alejandro Celaya
73ae754aa7 Created NotifyVisitToRedisTest 2022-07-28 10:36:52 +02:00
Alejandro Celaya
20a6e7e210 Created NotifyNewShortUrlToRedisTest 2022-07-28 10:33:26 +02:00
Alejandro Celaya
4cf433a994 Defined enum with supported remote systems 2022-07-28 10:25:55 +02:00
Alejandro Celaya
e36c4d397c Moved duplicated code in visit listeners to an abstract class 2022-07-27 18:18:36 +02:00
Alejandro Celaya
26037327f9 Moved duplicated code in short URL listeners to an abstract class 2022-07-27 18:06:47 +02:00
Alejandro Celaya
da6aa1d697 Integrated PublishUpdatesGenerator in NotifyVisitToRabbitMq listener 2022-07-27 17:41:48 +02:00
Alejandro Celaya
dada6aa3d1 Integrated PublishUpdatesGenerator in NotifyVisitToRedis listener 2022-07-27 16:55:19 +02:00
Alejandro Celaya
fa5ebb1677 Integrated PublishUpdatesGenerator in NotifyNewShortUrlToRedis listener 2022-07-27 16:47:21 +02:00
Alejandro Celaya
f071df325d Fixed NotifyNewShortUrlToRabbitMqTest 2022-07-27 10:26:18 +02:00
Alejandro Celaya
3c042c4011 Integrated PublishUpdatesGenerator in NotifyNewShortUrlToRabbitMq listener 2022-07-27 10:18:28 +02:00
Alejandro Celaya
7e8109caa3 Renamed MercureUpdatesGenerator to PublishingUpdatesGenerator to make it general purpose 2022-07-27 09:38:47 +02:00
Alejandro Celaya
d3add6d8e4 Added TODO 2022-07-26 12:18:58 +02:00
Alejandro Celaya
1b089749c0 Migrated mercure event listeners to use new publishing helper from shlink-common 2022-07-26 12:17:37 +02:00
Alejandro Celaya
791d6b7e57 Updated to latest common, with unified publishing API 2022-07-26 12:07:27 +02:00
Alejandro Celaya
233bb603cf Updated local redis config 2022-07-26 10:25:16 +02:00
Alejandro Celaya
db8a816524 Implemented redis pub/sub listeners 2022-07-26 10:17:50 +02:00
Alejandro Celaya
eff50ca202 Created new event listeners to send events to redis pub/sub 2022-07-25 18:23:13 +02:00
Alejandro Celaya
ceabb5ab2c Merge pull request #1486 from acelaya-forks/feature/backwards-compatible-rabbit-mq
Feature/backwards compatible rabbit mq
2022-07-25 12:55:28 +02:00
Alejandro Celaya
122c2fd5e6 Updated changelog 2022-07-25 12:34:40 +02:00
Alejandro Celaya
cd27a72982 Reduced duplicated code in NotifyNewShortUrlToRabbitMqTest 2022-07-25 12:31:32 +02:00
Alejandro Celaya
19b0f0d7dc Extended NotifyVisitToRabbitMqTest covering legacy and non-legacy use-cases 2022-07-25 12:30:28 +02:00
Alejandro Celaya
6ce2049935 Added support for legacy and new publishing of visits in RabbitMQ 2022-07-25 12:08:22 +02:00
Alejandro Celaya
53b937be63 Updated coding standard 2022-07-25 09:49:14 +02:00
Alejandro Celaya
71c8f99dab Merge pull request #1484 from acelaya-forks/feature/short-url-created-event
Feature/short url created event
2022-07-25 09:48:44 +02:00
Alejandro Celaya
9eb3fca726 Updated changelog 2022-07-25 09:32:09 +02:00
Alejandro Celaya
019bd4dec8 Created NotifyNewShortUrlToMercureTest 2022-07-25 09:30:25 +02:00
Alejandro Celaya
be1ce06c00 Updated asyn API spec 2022-07-25 09:04:15 +02:00
Alejandro Celaya
074bfe3db2 Updated MercureUpdatesGeneratorTest 2022-07-25 09:02:05 +02:00
Alejandro Celaya
34e72b42dc Implemented listener to publish new short URL events in Mercure 2022-07-24 19:00:48 +02:00
Alejandro Celaya
97d24d76d8 Fixed new short URL event payload to RabbitMQ, and started to add logic for Mercure 2022-07-24 12:37:57 +02:00
Alejandro Celaya
4d1af867a4 Extracted real-time update topic names to an enum 2022-07-24 12:06:00 +02:00
Alejandro Celaya
fc6b4c12b2 Configured publishing of new short URL events in RabbitMQ 2022-07-24 11:07:20 +02:00
Alejandro Celaya
405c6de591 Created NotifyNewShortUrlToRabbitMq test 2022-07-24 10:53:42 +02:00
Alejandro Celaya
47bfa5fcc0 Simplified NotifyNewShortUrlToRabbitMq 2022-07-24 10:18:19 +02:00
Alejandro Celaya
67d91d5fc5 Migrated rabbit integration to RabbitMqPublishingHelper from shlink-common 2022-07-24 10:12:26 +02:00
Alejandro Celaya
f832c56adb Moved Mercure and RabbitMq event listeners to their own subnamespaces 2022-07-21 20:07:28 +02:00
Alejandro Celaya
1aa9ae680e Merge pull request #1479 from acelaya-forks/feature/unknown-visits
Added missing implements JsonSerializable on VisitLocation that got l…
2022-07-18 20:43:02 +02:00
Alejandro Celaya
c4b30db82d Added missing implements JsonSerializable on VisitLocation that got lost when VisitLocationInterface was removed 2022-07-18 20:23:27 +02:00
Alejandro Celaya
abd9f3c6be Removed style checks disabling due to bug on php code sniffer 2022-07-04 17:12:38 +02:00
Alejandro Celaya
3de3594282 Merge pull request #1465 from jsoref/spelling
Spelling
2022-06-08 07:31:25 +02:00
Alejandro Celaya
ed5816d464 Fixed merge conflicts 2022-06-04 11:43:02 +02:00
Alejandro Celaya
f5a48ff98d Merge pull request #1460 from acelaya-forks/feature/monolog3
Feature/monolog3
2022-06-04 09:11:31 +02:00
Alejandro Celaya
8493ee5b83 Updated changelog 2022-06-04 09:00:00 +02:00
Alejandro Celaya
52a6d55e5d Updated to monolog 3 2022-06-04 08:59:17 +02:00
Josh Soref
7142295aa5 spelling: urls
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2022-05-30 23:36:42 -04:00
Josh Soref
8b65be26a6 spelling: the
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2022-05-30 23:36:42 -04:00
Josh Soref
60f5deb494 spelling: received
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2022-05-30 23:36:42 -04:00
Josh Soref
0fc09e6dd3 spelling: monolog
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2022-05-30 23:36:42 -04:00
Josh Soref
0c4ccf4e3e spelling: middleware
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2022-05-30 23:36:42 -04:00
Josh Soref
a0e79bf446 spelling: microsoft
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2022-05-30 23:36:42 -04:00
Josh Soref
aa356ad7c7 spelling: github
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2022-05-30 23:36:42 -04:00
Josh Soref
9e0e384d46 spelling: campaign
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2022-05-30 22:39:08 -04:00
Alejandro Celaya
a20b99e643 Merge pull request #1451 from acelaya-forks/feature/missing-visits-commands
Feature/missing visits commands
2022-05-24 18:44:56 +02:00
Alejandro Celaya
fe4237b2b1 Updated changelog 2022-05-24 18:00:17 +02:00
Alejandro Celaya
4146835f6f Created GetOrhanVisitsCommand test 2022-05-24 17:59:06 +02:00
Alejandro Celaya
5201ea4516 Created tests for short-url-visits commands 2022-05-24 17:54:44 +02:00
Alejandro Celaya
fba7b36245 Improved GetShortUrlVisitsCommand test 2022-05-24 17:44:12 +02:00
Alejandro Celaya
353ac0fc0c Added logic to resolve extra columns on visits commands 2022-05-23 21:19:59 +02:00
Alejandro Celaya
00002b1e24 Renamed some visits commands 2022-05-23 20:47:37 +02:00
Alejandro Celaya
aa32830671 Added note to readme 2022-05-23 07:55:33 +02:00
Alejandro Celaya
12b8100d89 Created visits commands for orphan, non-orphan and domain 2022-05-22 19:34:08 +02:00
Alejandro Celaya
72e56d271d Created tags visits command, with abstract class wrapping common logic for visits lists commands 2022-05-22 19:22:29 +02:00
Alejandro Celaya
358b600713 Fixed merge conflicts 2022-05-09 08:21:19 +02:00
Alejandro Celaya
e8c7bee924 Added SemVer constraints for some deps which were left with specific commits by mistake 2022-04-23 19:30:33 +02:00
Alejandro Celaya
24b06c24dc Merge pull request #1434 from acelaya-forks/feature/drop-php-8.0
Feature/drop php 8.0
2022-04-23 19:26:02 +02:00
Alejandro Celaya
6fdd764a35 Updated to phpcov 8.2.1 2022-04-23 19:11:23 +02:00
Alejandro Celaya
2400d1f265 Removed unused method 2022-04-23 19:07:36 +02:00
Alejandro Celaya
cdef430b0b Set ShortUrlIdentifier constructor to private 2022-04-23 19:01:02 +02:00
Alejandro Celaya
6074e4ae2c Updated changelog 2022-04-23 18:57:39 +02:00
Alejandro Celaya
6ada704bc3 Moved TagsMode to its own enum 2022-04-23 18:56:27 +02:00
Alejandro Celaya
e8f7daac6f Converted Role constants to enum 2022-04-23 18:41:16 +02:00
Alejandro Celaya
404455928e Converted visit types into enum 2022-04-23 18:19:16 +02:00
Alejandro Celaya
bca3e62ced Updated to readonly public props on as many models as possible 2022-04-23 14:00:47 +02:00
Alejandro Celaya
e79391907a Added some PHP 8.1 features 2022-04-23 13:08:21 +02:00
Alejandro Celaya
54a23cc7fa Converted EnvVars to enum 2022-04-23 12:44:17 +02:00
Alejandro Celaya
e8ebe77923 Dropped PHP 8.0 support 2022-04-23 12:08:01 +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
477 changed files with 7089 additions and 3937 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

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

@@ -0,0 +1,50 @@
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
- run: echo "::set-output name=composerArgs::${{ inputs.php-version == '8.2' && '--ignore-platform-req=php' || '' }}"
id: composer_args
shell: bash
- name: Install dependencies
if: ${{ inputs.install-deps == 'yes' }}
run: composer install --no-interaction --prefer-dist ${{ steps.composer_args.outputs.composerArgs }}
shell: bash

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

@@ -0,0 +1,45 @@
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']
continue-on-error: ${{ matrix.php-version == '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.11.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

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

@@ -0,0 +1,46 @@
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']
continue-on-error: ${{ matrix.php-version == '8.2' }}
steps:
- uses: actions/checkout@v3
- uses: './.github/actions/ci-setup'
with:
php-version: ${{ matrix.php-version }}
php-extensions: openswoole-4.11.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 }}

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

@@ -0,0 +1,38 @@
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']
continue-on-error: ${{ matrix.php-version == '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.11.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

@@ -10,150 +10,136 @@ on:
jobs:
static-analysis:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
matrix:
php-version: ['8.0']
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
- run: composer install --no-interaction --prefer-dist
php-extensions: openswoole-4.11.1
extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ matrix.command }}
- run: composer ${{ matrix.command }}
tests:
runs-on: ubuntu-20.04
unit-tests:
uses: './.github/workflows/ci-tests.yml'
with:
test-group: unit
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.0', '8.1']
test-group: ['unit', 'api']
php-version: ['8.1', '8.2']
continue-on-error: ${{ matrix.php-version == '8.2' }}
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
- 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
coverage: pcov
ini-values: pcov.directory=module
- run: composer install --no-interaction --prefer-dist
- run: composer test:${{ matrix.test-group }}:ci
- uses: actions/upload-artifact@v2
if: ${{ matrix.php-version == '8.0' }}
with:
name: coverage-${{ matrix.test-group }}
path: |
build/coverage-${{ matrix.test-group }}
build/coverage-${{ matrix.test-group }}.cov
- run: echo "::set-output name=composerArgs::${{ matrix.php-version == '8.2' && '--ignore-platform-req=php' || '' }}"
id: composer_args
shell: bash
- run: composer install --no-interaction --prefer-dist ${{ steps.composer_args.outputs.composerArgs }}
- run: ./vendor/bin/rr get --no-interaction --location bin/ && chmod +x bin/rr
- run: composer test:api:rr
db-tests:
runs-on: ubuntu-20.04
strategy:
matrix:
php-version: ['8.0', '8.1']
platform: ['sqlite:ci', 'mysql', 'maria', 'postgres', 'ms']
env:
LC_ALL: C
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
with:
php-version: ${{ matrix.php-version }}
tools: composer
extensions: openswoole-4.11.1, pdo_sqlsrv-5.10.0
coverage: pcov
ini-values: pcov.directory=module
- 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.0' && matrix.platform == 'sqlite:ci' }}
with:
name: coverage-db
path: |
build/coverage-db
build/coverage-db.cov
sqlite-db-tests:
uses: './.github/workflows/ci-db-tests.yml'
with:
platform: 'sqlite:ci'
mutation-tests:
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-20.04
strategy:
matrix:
php-version: ['8.0', '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
- 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
runs-on: ubuntu-20.04
- unit-tests
- openswoole-api-tests
- cli-tests
- sqlite-db-tests
runs-on: ubuntu-22.04
strategy:
matrix:
php-version: ['8.0']
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: wget https://phar.phpunit.de/phpcov-8.2.0.phar
- run: php phpcov-8.2.0.phar merge build --clover build/clover.xml
- 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
uses: codecov/codecov-action@v1
with:
@@ -161,9 +147,12 @@ jobs:
delete-artifacts:
needs:
- mutation-tests
- unit-mutation-tests
- db-mutation-tests
- api-mutation-tests
- cli-mutation-tests
- upload-coverage
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: geekyeggo/delete-artifact@v1
with:
@@ -171,12 +160,13 @@ jobs:
coverage-unit
coverage-db
coverage-api
coverage-cli
build-docker-image:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 100
- uses: marceloprado/has-changed-path@v1

View File

@@ -1,4 +1,4 @@
name: Build docker image
name: Build and publish docker image
on:
push:
@@ -8,21 +8,20 @@ on:
- 'v*'
jobs:
build:
runs-on: ubuntu-20.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
build-openswool:
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

@@ -7,36 +7,34 @@ on:
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
matrix:
php-version: ['8.0', '8.1']
php-version: ['8.1']
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.11.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
publish:
needs: ['build']
runs-on: ubuntu-20.04
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
@@ -50,11 +48,11 @@ jobs:
delete-artifacts:
needs: ['publish']
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
matrix:
php-version: [ '8.0', '8.1' ]
swoole: [ 'yes', 'no' ]
php-version: ['8.1']
swoole: ['yes', 'no']
steps:
- uses: geekyeggo/delete-artifact@v1
with:

View File

@@ -7,25 +7,21 @@ on:
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
matrix:
php-version: ['8.0']
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.11.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,146 @@ 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.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*
### Changed
* [#1495](https://github.com/shlinkio/shlink/issues/1495) Centralized how routes are configured to support multi-segment slugs.
* [#1497](https://github.com/shlinkio/shlink/issues/1497) Updated to latest shlink dependencies with support for PHP 8.1 only.
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* [#1499](https://github.com/shlinkio/shlink/issues/1499) Fixed loading of config options as env vars, which was making all default configurations to be loaded unless env vars were explicitly provided.
## [3.2.0] - 2022-08-05
### Added
* [#854](https://github.com/shlinkio/shlink/issues/854) Added support for multi-segment custom slugs.
The feature is disabled by default, but you can optionally opt in. If you do, you will be able to create short URLs with multiple segments in the custom slug, like `https://example.com/foo/bar/baz`.
* [#1280](https://github.com/shlinkio/shlink/issues/1280) Added missing visit-related commands.
Now you can run `tag:visits`, `domain:visits`, `visit:orphan` or `visit:non-orphan` to get the corresponding list of visits from the command line.
* [#962](https://github.com/shlinkio/shlink/issues/962) Added new real-time update for new short URLs.
You can now subscribe to the `https://shlink.io/new-short-url` topic on any of the supported async updates technologies in order to get notified when a short URL is created.
* [#1367](https://github.com/shlinkio/shlink/issues/1367) Added support to publish real-time updates in redis pub/sub.
The publishing will happen in the same redis instance/cluster configured for caching.
### Changed
* [#1452](https://github.com/shlinkio/shlink/issues/1452) Updated to monolog 3
* [#1485](https://github.com/shlinkio/shlink/issues/1485) Changed payload published in RabbitMQ for all visits events, in order to conform with the Async API spec.
Since this is a breaking change, also provided a new `RABBITMQ_LEGACY_VISITS_PUBLISHING=true` env var that can be provided in order to keep the old payload.
This env var is considered deprecated and will be removed in Shlink 4, when the legacy format will no longer be supported.
### Deprecated
* *Nothing*
### Removed
* [#1280](https://github.com/shlinkio/shlink/issues/1280) Dropped support for PHP 8.0
### Fixed
* [#1471](https://github.com/shlinkio/shlink/issues/1471) Fixed error when running `visit:locate` command with any extra parameter (like `--retry`).
## [3.1.2] - 2022-06-04
### Added
* *Nothing*
@@ -605,7 +745,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
* *Nothing*
### Fixed
* [#979](https://github.com/shlinkio/shlink/issues/979) Added missing `itemsPerPage` query param to swagger docs for short RULs list.
* [#979](https://github.com/shlinkio/shlink/issues/979) Added missing `itemsPerPage` query param to swagger docs for short URLs list.
* [#980](https://github.com/shlinkio/shlink/issues/980) Fixed value used for `Access-Control-Allow-Origin`, that could not work as expected when including an IP address.
* [#947](https://github.com/shlinkio/shlink/issues/947) Fixed incorrect value returned in `Access-Control-Allow-Methods` header, which always contained all methods.
@@ -1253,7 +1393,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-compaign` 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://doma.in/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:
@@ -1503,7 +1643,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
### Fixed
* [#309](https://github.com/shlinkio/shlink/issues/309) Added missing favicon to prevent 404 errors logged when an error page is loaded in a browser.
* [#310](https://github.com/shlinkio/shlink/issues/310) Fixed execution context not being properly detected, making `CloseDbConnectionMiddlware` to be always piped. Now the check is not even made, which simplifies everything.
* [#310](https://github.com/shlinkio/shlink/issues/310) Fixed execution context not being properly detected, making `CloseDbConnectionMiddleware` to be always piped. Now the check is not even made, which simplifies everything.
## [1.15.0] - 2018-12-02
@@ -1568,7 +1708,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
### Changed
* [#241](https://github.com/shlinkio/shlink/issues/241) Fixed columns in `visit_locations` table, to be snake_case instead of camelCase.
* [#228](https://github.com/shlinkio/shlink/issues/228) Updated how exceptions are serialized into logs, by using monlog's `PsrLogMessageProcessor`.
* [#228](https://github.com/shlinkio/shlink/issues/228) Updated how exceptions are serialized into logs, by using monolog's `PsrLogMessageProcessor`.
* [#225](https://github.com/shlinkio/shlink/issues/225) Performance and maintainability slightly improved by enforcing via code sniffer that all global namespace classes, functions and constants are explicitly imported.
* [#196](https://github.com/shlinkio/shlink/issues/196) Reduced anemic model in entities, defining more expressive public APIs instead.
* [#249](https://github.com/shlinkio/shlink/issues/249) Added [functional-php](https://github.com/lstrojny/functional-php) to ease collections handling.

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,9 +1,11 @@
FROM php:8.1.5-alpine3.15 as base
FROM php:8.1.9-alpine3.16 as base
ARG SHLINK_VERSION=latest
ENV SHLINK_VERSION ${SHLINK_VERSION}
ARG SHLINK_RUNTIME=openswoole
ENV SHLINK_RUNTIME ${SHLINK_RUNTIME}
ENV OPENSWOOLE_VERSION 4.11.1
ENV PDO_SQLSRV_VERSION 5.10.0
ENV PDO_SQLSRV_VERSION 5.10.1
ENV MS_ODBC_SQL_VERSION 17.5.2.2
ENV LC_ALL "C"
@@ -22,8 +24,10 @@ 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 && \
@@ -38,7 +42,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 +58,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

@@ -15,7 +15,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 +35,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.0 or 8.1
* PHP 8.1
* 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.
@@ -66,7 +66,9 @@ In order to run Shlink, you will need a built version of the project. There are
After that, you will have a dist file inside the `build` directory, that you need to decompress in the location of your choice.
> This is the process used when releasing new shlink versions. After tagging the new version with git, the Github release is automatically created by a [GitHub workflow](https://github.com/shlinkio/shlink/actions?query=workflow%3A%22Publish+release%22), attaching the generated dist file to it.
> **Note**
>
> This is the process used when releasing new Shlink versions. After tagging the new version with git, the GitHub release is automatically created by a [GitHub workflow](https://github.com/shlinkio/shlink/actions?query=workflow%3A%22Publish+release%22), attaching the generated dist file to it.
### Configure

View File

@@ -76,7 +76,7 @@ These routes have been removed, but have a direct replacement:
* `/qr/{shortCode}[/{size}]` -> `/{shortCode}/qr-code[/{size}]`
* `PUT /rest/v{version}/short-urls/{shortCode}` -> `PATCH /rest/v{version}/short-urls/{shortCode}`
When using the old ones, a 404 status will me returned now.
When using the old ones, a 404 status will be returned now.
### Removed command and route aliases

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

@@ -12,70 +12,69 @@
}
],
"require": {
"php": "^8.0",
"php": "^8.1",
"ext-curl": "*",
"ext-gd": "*",
"ext-json": "*",
"ext-pdo": "*",
"akrabat/ip-address-middleware": "^2.1",
"cakephp/chronos": "^2.3",
"doctrine/migrations": "^3.3",
"doctrine/orm": "^2.11",
"doctrine/migrations": "^3.5",
"doctrine/orm": "^2.13.3",
"endroid/qr-code": "^4.4",
"geoip2/geoip2": "^2.12",
"guzzlehttp/guzzle": "^7.4",
"happyr/doctrine-specification": "^2.0",
"jaybizzle/crawler-detect": "^1.2.110",
"laminas/laminas-config": "^3.7",
"laminas/laminas-config-aggregator": "^1.7",
"laminas/laminas-diactoros": "^2.8",
"laminas/laminas-inputfilter": "^2.13",
"laminas/laminas-servicemanager": "^3.11.2",
"laminas/laminas-stdlib": "^3.6",
"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.4",
"league/uri": "^6.7",
"lstrojny/functional-php": "^1.17",
"mezzio/mezzio": "^3.7",
"mezzio/mezzio-fastroute": "^3.3",
"mezzio/mezzio-problem-details": "^1.5",
"mezzio/mezzio-swoole": "^4.0",
"mlocati/ip-lib": "^1.17",
"monolog/monolog": "^2.3",
"nikolaposa/monolog-factory": "^3.1",
"ocramius/proxy-manager": "^2.11",
"pagerfanta/core": "^3.5",
"php-amqplib/php-amqplib": "^3.1",
"mezzio/mezzio": "^3.11",
"mezzio/mezzio-fastroute": "^3.5",
"mezzio/mezzio-problem-details": "^1.6",
"mezzio/mezzio-swoole": "^4.3",
"mlocati/ip-lib": "^1.18",
"ocramius/proxy-manager": "^2.14",
"pagerfanta/core": "^3.6",
"php-middleware/request-id": "^4.1",
"predis/predis": "^1.1",
"pugx/shortid-php": "^1.0",
"ramsey/uuid": "^4.2",
"shlinkio/shlink-common": "^4.4",
"shlinkio/shlink-config": "^1.6",
"shlinkio/shlink-event-dispatcher": "^2.3",
"shlinkio/shlink-importer": "^3.0",
"shlinkio/shlink-installer": "^7.1",
"shlinkio/shlink-ip-geolocation": "^2.2",
"symfony/console": "^6.0",
"symfony/filesystem": "^6.0",
"symfony/lock": "^6.0",
"symfony/mercure": "^0.6",
"symfony/process": "^6.0",
"symfony/string": "^6.0"
"ramsey/uuid": "^4.3",
"shlinkio/shlink-common": "^5.1",
"shlinkio/shlink-config": "^2.1",
"shlinkio/shlink-event-dispatcher": "^2.6",
"shlinkio/shlink-importer": "^4.0",
"shlinkio/shlink-installer": "^8.2",
"shlinkio/shlink-ip-geolocation": "^3.1",
"spiral/roadrunner": "^2.11",
"spiral/roadrunner-jobs": "^2.3",
"symfony/console": "^6.1",
"symfony/filesystem": "^6.1",
"symfony/lock": "^6.1",
"symfony/process": "^6.1",
"symfony/string": "^6.1"
},
"require-dev": {
"cebe/php-openapi": "^1.7",
"devster/ubench": "^2.1",
"dms/phpunit-arraysubset-asserts": "^0.3.0",
"infection/infection": "^0.26.5",
"dms/phpunit-arraysubset-asserts": "^0.4.0",
"infection/infection": "^0.26.15",
"openswoole/ide-helper": "~4.11.1",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/phpstan": "^1.2",
"phpstan/phpstan-doctrine": "^1.0",
"phpstan/phpstan-symfony": "^1.0",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-doctrine": "^1.3",
"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.2.0",
"shlinkio/shlink-test-utils": "^3.0.1",
"symfony/var-dumper": "^6.0",
"shlinkio/php-coding-standard": "~2.3.0",
"shlinkio/shlink-test-utils": "^3.3",
"symfony/var-dumper": "^6.1",
"veewee/composer-run-parallel": "^1.1"
},
"autoload": {
@@ -92,8 +91,10 @@
"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"
},
@@ -103,31 +104,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",
"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",
@@ -137,12 +125,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",
"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=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",
"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=80 --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"
@@ -155,18 +149,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</>",
@@ -176,9 +172,13 @@
"test:db:mysql": "<fg=blue;options=bold>Runs database test suites on a MySQL database</>",
"test:db:maria": "<fg=blue;options=bold>Runs database test suites on a MariaDB database</>",
"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 Miscrosoft SQL Server 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

@@ -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

@@ -7,7 +7,7 @@ namespace Shlinkio\Shlink;
use Shlinkio\Shlink\Core\Config\EnvVars;
return (static function (): array {
$threshold = EnvVars::DELETE_SHORT_URL_THRESHOLD()->loadFromEnv();
$threshold = EnvVars::DELETE_SHORT_URL_THRESHOLD->loadFromEnv();
return [

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

@@ -8,7 +8,7 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
use function Functional\contains;
return (static function (): array {
$driver = EnvVars::DB_DRIVER()->loadFromEnv();
$driver = EnvVars::DB_DRIVER->loadFromEnv();
$isMysqlCompatible = contains(['maria', 'mysql'], $driver);
$resolveDriver = static fn () => match ($driver) {
@@ -35,12 +35,12 @@ return (static function (): array {
],
default => [
'driver' => $resolveDriver(),
'dbname' => EnvVars::DB_NAME()->loadFromEnv('shlink'),
'user' => EnvVars::DB_USER()->loadFromEnv(),
'password' => EnvVars::DB_PASSWORD()->loadFromEnv(),
'host' => EnvVars::DB_HOST()->loadFromEnv(EnvVars::DB_UNIX_SOCKET()->loadFromEnv()),
'port' => EnvVars::DB_PORT()->loadFromEnv($resolveDefaultPort()),
'unix_socket' => $isMysqlCompatible ? EnvVars::DB_UNIX_SOCKET()->loadFromEnv() : null,
'dbname' => EnvVars::DB_NAME->loadFromEnv('shlink'),
'user' => EnvVars::DB_USER->loadFromEnv(),
'password' => EnvVars::DB_PASSWORD->loadFromEnv(),
'host' => EnvVars::DB_HOST->loadFromEnv(EnvVars::DB_UNIX_SOCKET->loadFromEnv()),
'port' => EnvVars::DB_PORT->loadFromEnv($resolveDefaultPort()),
'unix_socket' => $isMysqlCompatible ? EnvVars::DB_UNIX_SOCKET->loadFromEnv() : null,
'charset' => $resolveCharset(),
],
};

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

@@ -9,7 +9,7 @@ return [
'geolite2' => [
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
'temp_dir' => __DIR__ . '/../../data',
'license_key' => EnvVars::GEOLITE_LICENSE_KEY()->loadFromEnv(),
'license_key' => EnvVars::GEOLITE_LICENSE_KEY->loadFromEnv(),
],
];

View File

@@ -32,6 +32,7 @@ return [
Option\Worker\WebWorkerNumConfigOption::class,
Option\Redis\RedisServersConfigOption::class,
Option\Redis\RedisSentinelServiceConfigOption::class,
Option\Redis\RedisPubSubConfigOption::class,
Option\UrlShortener\ShortCodeLengthOption::class,
Option\Mercure\EnableMercureConfigOption::class,
Option\Mercure\MercurePublicUrlConfigOption::class,
@@ -42,6 +43,8 @@ return [
Option\UrlShortener\RedirectCacheLifeTimeConfigOption::class,
Option\UrlShortener\AutoResolveTitlesConfigOption::class,
Option\UrlShortener\AppendExtraPathConfigOption::class,
Option\UrlShortener\EnableMultiSegmentSlugsConfigOption::class,
Option\UrlShortener\EnableTrailingSlashConfigOption::class,
Option\Tracking\IpAnonymizationConfigOption::class,
Option\Tracking\OrphanVisitsTrackingConfigOption::class,
Option\Tracking\DisableTrackParamConfigOption::class,
@@ -64,15 +67,24 @@ return [
],
'installation_commands' => [
InstallationCommand::DB_CREATE_SCHEMA => [
InstallationCommand::DB_CREATE_SCHEMA->value => [
'command' => 'bin/cli ' . Command\Db\CreateDatabaseCommand::NAME,
],
InstallationCommand::DB_MIGRATE => [
InstallationCommand::DB_MIGRATE->value => [
'command' => 'bin/cli ' . Command\Db\MigrateDatabaseCommand::NAME,
],
InstallationCommand::GEOLITE_DOWNLOAD_DB => [
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

@@ -3,7 +3,7 @@
declare(strict_types=1);
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
use Predis\ClientInterface as PredisClient;
use Shlinkio\Shlink\Common\Cache\RedisFactory;
use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
use Shlinkio\Shlink\Core\Config\EnvVars;
use Symfony\Component\Lock;
@@ -24,7 +24,7 @@ return [
LOCAL_LOCK_FACTORY => ConfigAbstractFactory::class,
],
'aliases' => [
'lock_store' => EnvVars::REDIS_SERVERS()->existsInEnv() ? 'redis_lock_store' : 'local_lock_store',
'lock_store' => EnvVars::REDIS_SERVERS->existsInEnv() ? 'redis_lock_store' : 'local_lock_store',
'redis_lock_store' => Lock\Store\RedisStore::class,
'local_lock_store' => Lock\Store\FlockStore::class,
@@ -38,7 +38,7 @@ return [
ConfigAbstractFactory::class => [
Lock\Store\FlockStore::class => ['config.locks.locks_dir'],
Lock\Store\RedisStore::class => [PredisClient::class],
Lock\Store\RedisStore::class => [RedisFactory::SERVICE_NAME],
Lock\LockFactory::class => ['lock_store'],
LOCAL_LOCK_FACTORY => ['local_lock_store'],
],

View File

@@ -4,72 +4,36 @@ declare(strict_types=1);
namespace Shlinkio\Shlink;
use Monolog\Formatter;
use Monolog\Handler;
use Monolog\Level;
use Monolog\Logger;
use Monolog\Processor;
use MonologFactory\DiContainerLoggerFactory;
use PhpMiddleware\RequestId;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Common\Logger\LoggerFactory;
use Shlinkio\Shlink\Common\Logger\LoggerType;
use const PHP_EOL;
$processors = [
'exception_with_new_line' => [
'name' => Common\Logger\Processor\ExceptionWithNewLineProcessor::class,
],
'psr3' => [
'name' => Processor\PsrLogMessageProcessor::class,
],
'request_id' => RequestId\MonologProcessor::class,
];
$formatter = [
'name' => Formatter\LineFormatter::class,
'params' => [
'format' => '[%datetime%] [%extra.request_id%] %channel%.%level_name% - %message%' . PHP_EOL,
'allow_inline_line_breaks' => true,
],
$common = [
'level' => Level::Info->value,
'processors' => [RequestId\MonologProcessor::class],
'line_format' => '[%datetime%] [%extra.request_id%] %channel%.%level_name% - %message%',
];
return [
'logger' => [
'Shlink' => [
'name' => 'Shlink',
'handlers' => [
'shlink_handler' => [
'name' => Handler\RotatingFileHandler::class,
'params' => [
'level' => Logger::INFO,
'filename' => 'data/log/shlink_log.log',
'max_files' => 30,
'file_permission' => 0666,
],
'formatter' => $formatter,
],
],
'processors' => $processors,
'type' => LoggerType::FILE->value,
...$common,
],
'Access' => [
'name' => 'Access',
'handlers' => [
'access_handler' => [
'name' => Handler\StreamHandler::class,
'params' => [
'level' => Logger::INFO,
'stream' => 'php://stdout',
],
'formatter' => $formatter,
],
],
'processors' => $processors,
'type' => LoggerType::STREAM->value,
...$common,
],
],
'dependencies' => [
'factories' => [
'Logger_Shlink' => [DiContainerLoggerFactory::class, 'Shlink'],
'Logger_Access' => [DiContainerLoggerFactory::class, 'Access'],
'Logger_Shlink' => [LoggerFactory::class, 'Shlink'],
'Logger_Access' => [LoggerFactory::class, 'Access'],
],
'aliases' => [
'logger' => 'Logger_Shlink',

View File

@@ -2,33 +2,18 @@
declare(strict_types=1);
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Monolog\Level;
use Shlinkio\Shlink\Common\Logger\LoggerType;
$isSwoole = extension_loaded('openswoole');
// For swoole, send logs to standard output
$handler = $isSwoole
? [
'name' => StreamHandler::class,
'params' => [
'level' => Logger::DEBUG,
'stream' => 'php://stdout',
],
]
: [
'params' => [
'level' => Logger::DEBUG,
],
];
return [
'logger' => [
'Shlink' => [
'handlers' => [
'shlink_handler' => $handler,
],
// For swoole, send logs as stream
'type' => $isSwoole ? LoggerType::STREAM->value : LoggerType::FILE->value,
'level' => Level::Debug->value,
],
],

View File

@@ -9,14 +9,14 @@ use Symfony\Component\Mercure\Hub;
use Symfony\Component\Mercure\HubInterface;
return (static function (): array {
$publicUrl = EnvVars::MERCURE_PUBLIC_HUB_URL()->loadFromEnv();
$publicUrl = EnvVars::MERCURE_PUBLIC_HUB_URL->loadFromEnv();
return [
'mercure' => [
'public_hub_url' => $publicUrl,
'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL()->loadFromEnv($publicUrl),
'jwt_secret' => EnvVars::MERCURE_JWT_SECRET()->loadFromEnv(),
'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL->loadFromEnv($publicUrl),
'jwt_secret' => EnvVars::MERCURE_JWT_SECRET->loadFromEnv(),
'jwt_issuer' => 'Shlink',
],

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

@@ -13,13 +13,13 @@ use const Shlinkio\Shlink\DEFAULT_QR_CODE_SIZE;
return [
'qr_codes' => [
'size' => (int) EnvVars::DEFAULT_QR_CODE_SIZE()->loadFromEnv(DEFAULT_QR_CODE_SIZE),
'margin' => (int) EnvVars::DEFAULT_QR_CODE_MARGIN()->loadFromEnv(DEFAULT_QR_CODE_MARGIN),
'format' => EnvVars::DEFAULT_QR_CODE_FORMAT()->loadFromEnv(DEFAULT_QR_CODE_FORMAT),
'error_correction' => EnvVars::DEFAULT_QR_CODE_ERROR_CORRECTION()->loadFromEnv(
'size' => (int) EnvVars::DEFAULT_QR_CODE_SIZE->loadFromEnv(DEFAULT_QR_CODE_SIZE),
'margin' => (int) EnvVars::DEFAULT_QR_CODE_MARGIN->loadFromEnv(DEFAULT_QR_CODE_MARGIN),
'format' => EnvVars::DEFAULT_QR_CODE_FORMAT->loadFromEnv(DEFAULT_QR_CODE_FORMAT),
'error_correction' => EnvVars::DEFAULT_QR_CODE_ERROR_CORRECTION->loadFromEnv(
DEFAULT_QR_CODE_ERROR_CORRECTION,
),
'round_block_size' => (bool) EnvVars::DEFAULT_QR_CODE_ROUND_BLOCK_SIZE()->loadFromEnv(
'round_block_size' => (bool) EnvVars::DEFAULT_QR_CODE_ROUND_BLOCK_SIZE->loadFromEnv(
DEFAULT_QR_CODE_ROUND_BLOCK_SIZE,
),
],

View File

@@ -2,46 +2,20 @@
declare(strict_types=1);
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use Shlinkio\Shlink\Core\Config\EnvVars;
return [
'rabbitmq' => [
'enabled' => (bool) EnvVars::RABBITMQ_ENABLED()->loadFromEnv(false),
'host' => EnvVars::RABBITMQ_HOST()->loadFromEnv(),
'port' => (int) EnvVars::RABBITMQ_PORT()->loadFromEnv('5672'),
'user' => EnvVars::RABBITMQ_USER()->loadFromEnv(),
'password' => EnvVars::RABBITMQ_PASSWORD()->loadFromEnv(),
'vhost' => EnvVars::RABBITMQ_VHOST()->loadFromEnv('/'),
],
'enabled' => (bool) EnvVars::RABBITMQ_ENABLED->loadFromEnv(false),
'host' => EnvVars::RABBITMQ_HOST->loadFromEnv(),
'port' => (int) EnvVars::RABBITMQ_PORT->loadFromEnv('5672'),
'user' => EnvVars::RABBITMQ_USER->loadFromEnv(),
'password' => EnvVars::RABBITMQ_PASSWORD->loadFromEnv(),
'vhost' => EnvVars::RABBITMQ_VHOST->loadFromEnv('/'),
'dependencies' => [
'factories' => [
AMQPStreamConnection::class => ConfigAbstractFactory::class,
],
'delegators' => [
AMQPStreamConnection::class => [
LazyServiceFactory::class,
],
],
'lazy_services' => [
'class_map' => [
AMQPStreamConnection::class => AMQPStreamConnection::class,
],
],
],
ConfigAbstractFactory::class => [
AMQPStreamConnection::class => [
'config.rabbitmq.host',
'config.rabbitmq.port',
'config.rabbitmq.user',
'config.rabbitmq.password',
'config.rabbitmq.vhost',
],
// Deprecated
'legacy_visits_publishing' => (bool) EnvVars::RABBITMQ_LEGACY_VISITS_PUBLISHING->loadFromEnv(false),
],
];

View File

@@ -10,14 +10,14 @@ use const Shlinkio\Shlink\DEFAULT_REDIRECT_STATUS_CODE;
return [
'not_found_redirects' => [
'invalid_short_url' => EnvVars::DEFAULT_INVALID_SHORT_URL_REDIRECT()->loadFromEnv(),
'regular_404' => EnvVars::DEFAULT_REGULAR_404_REDIRECT()->loadFromEnv(),
'base_url' => EnvVars::DEFAULT_BASE_URL_REDIRECT()->loadFromEnv(),
'invalid_short_url' => EnvVars::DEFAULT_INVALID_SHORT_URL_REDIRECT->loadFromEnv(),
'regular_404' => EnvVars::DEFAULT_REGULAR_404_REDIRECT->loadFromEnv(),
'base_url' => EnvVars::DEFAULT_BASE_URL_REDIRECT->loadFromEnv(),
],
'redirects' => [
'redirect_status_code' => (int) EnvVars::REDIRECT_STATUS_CODE()->loadFromEnv(DEFAULT_REDIRECT_STATUS_CODE),
'redirect_cache_lifetime' => (int) EnvVars::REDIRECT_CACHE_LIFETIME()->loadFromEnv(
'redirect_status_code' => (int) EnvVars::REDIRECT_STATUS_CODE->loadFromEnv(DEFAULT_REDIRECT_STATUS_CODE),
'redirect_cache_lifetime' => (int) EnvVars::REDIRECT_CACHE_LIFETIME->loadFromEnv(
DEFAULT_REDIRECT_CACHE_LIFETIME,
),
],

View File

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

View File

@@ -7,12 +7,13 @@ return [
'cache' => [
'redis' => [
'servers' => 'tcp://shlink_redis:6379',
// 'servers' => [
// 'tcp://shlink_redis:6379',
// ],
],
],
'redis' => [
'pub_sub_enabled' => true,
],
'dependencies' => [
'aliases' => [
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
use Laminas\ServiceManager\Factory\InvokableFactory;
use PhpMiddleware\RequestId;
use Shlinkio\Shlink\Common\Logger\Processor\BackwardsCompatibleMonologProcessorDelegator;
return [
@@ -20,6 +21,11 @@ return [
RequestId\RequestIdMiddleware::class => ConfigAbstractFactory::class,
RequestId\MonologProcessor::class => ConfigAbstractFactory::class,
],
'delegators' => [
RequestId\MonologProcessor::class => [
BackwardsCompatibleMonologProcessorDelegator::class,
],
],
],
ConfigAbstractFactory::class => [

View File

@@ -8,7 +8,7 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
return [
'router' => [
'base_path' => EnvVars::BASE_PATH()->loadFromEnv(''),
'base_path' => EnvVars::BASE_PATH->loadFromEnv(''),
'fastroute' => [
FastRouteRouter::CONFIG_CACHE_ENABLED => true,

View File

@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
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
'routes' => [
// Rest
...ConfigProvider::applyRoutesPrefix([
Action\HealthAction::getRouteDef(),
// Visits
Action\Visit\ShortUrlVisitsAction::getRouteDef([$dropDomainMiddleware]),
Action\Visit\TagVisitsAction::getRouteDef(),
Action\Visit\DomainVisitsAction::getRouteDef(),
Action\Visit\GlobalVisitsAction::getRouteDef(),
Action\Visit\OrphanVisitsAction::getRouteDef(),
Action\Visit\NonOrphanVisitsAction::getRouteDef(),
// Short URLs
Action\ShortUrl\CreateShortUrlAction::getRouteDef([
$contentNegotiationMiddleware,
$dropDomainMiddleware,
$overrideDomainMiddleware,
Middleware\ShortUrl\DefaultShortCodesLengthMiddleware::class,
]),
Action\ShortUrl\SingleStepCreateShortUrlAction::getRouteDef([
$contentNegotiationMiddleware,
$overrideDomainMiddleware,
]),
Action\ShortUrl\EditShortUrlAction::getRouteDef([$dropDomainMiddleware]),
Action\ShortUrl\DeleteShortUrlAction::getRouteDef([$dropDomainMiddleware]),
Action\ShortUrl\ResolveShortUrlAction::getRouteDef([$dropDomainMiddleware]),
Action\ShortUrl\ListShortUrlsAction::getRouteDef(),
// Tags
Action\Tag\ListTagsAction::getRouteDef(),
Action\Tag\TagsStatsAction::getRouteDef(),
Action\Tag\DeleteTagsAction::getRouteDef(),
Action\Tag\UpdateTagAction::getRouteDef(),
// Domains
Action\Domain\ListDomainsAction::getRouteDef(),
Action\Domain\DomainRedirectsAction::getRouteDef(),
Action\MercureInfoAction::getRouteDef([NotConfiguredMercureErrorHandler::class]),
]),
// Non-rest
[
'name' => CoreAction\RobotsAction::class,
'path' => '/robots.txt',
'middleware' => [
CoreAction\RobotsAction::class,
],
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
],
[
'name' => CoreAction\PixelAction::class,
'path' => '/{shortCode}/track',
'middleware' => [
IpAddress::class,
CoreAction\PixelAction::class,
],
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
],
[
'name' => CoreAction\QrCodeAction::class,
'path' => '/{shortCode}/qr-code',
'middleware' => [
CoreAction\QrCodeAction::class,
],
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
],
[
'name' => CoreAction\RedirectAction::class,
'path' => sprintf('/{shortCode}%s', $shortUrlRouteSuffix),
'middleware' => [
IpAddress::class,
TrimTrailingSlashMiddleware::class,
CoreAction\RedirectAction::class,
],
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
],
],
];
})();

View File

@@ -7,7 +7,7 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
use const Shlinkio\Shlink\MIN_TASK_WORKERS;
return (static function (): array {
$taskWorkers = (int) EnvVars::TASK_WORKER_NUM()->loadFromEnv(16);
$taskWorkers = (int) EnvVars::TASK_WORKER_NUM->loadFromEnv(16);
return [
@@ -17,11 +17,11 @@ return (static function (): array {
'swoole-http-server' => [
'host' => '0.0.0.0',
'port' => (int) EnvVars::PORT()->loadFromEnv(8080),
'port' => (int) EnvVars::PORT->loadFromEnv(8080),
'process-name' => 'shlink',
'options' => [
'worker_num' => (int) EnvVars::WEB_WORKER_NUM()->loadFromEnv(16),
'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

@@ -9,7 +9,7 @@ use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH;
return (static function (): array {
$shortCodesLength = max(
(int) EnvVars::DEFAULT_SHORT_CODES_LENGTH()->loadFromEnv(DEFAULT_SHORT_CODES_LENGTH),
(int) EnvVars::DEFAULT_SHORT_CODES_LENGTH->loadFromEnv(DEFAULT_SHORT_CODES_LENGTH),
MIN_SHORT_CODES_LENGTH,
);
@@ -17,12 +17,14 @@ return (static function (): array {
'url_shortener' => [
'domain' => [ // TODO Refactor this structure to url_shortener.schema and url_shortener.default_domain
'schema' => ((bool) EnvVars::IS_HTTPS_ENABLED()->loadFromEnv(true)) ? 'https' : 'http',
'hostname' => EnvVars::DEFAULT_DOMAIN()->loadFromEnv(''),
'schema' => ((bool) EnvVars::IS_HTTPS_ENABLED->loadFromEnv(true)) ? 'https' : 'http',
'hostname' => EnvVars::DEFAULT_DOMAIN->loadFromEnv(''),
],
'default_short_codes_length' => $shortCodesLength,
'auto_resolve_titles' => (bool) EnvVars::AUTO_RESOLVE_TITLES()->loadFromEnv(false),
'append_extra_path' => (bool) EnvVars::REDIRECT_APPEND_EXTRA_PATH()->loadFromEnv(false),
'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),
],
];

View File

@@ -2,16 +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

@@ -6,14 +6,14 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
// Deprecated. Webhooks are no longer supported. To be removed in Shlink 4.0.0
return (static function (): array {
$webhooks = EnvVars::VISITS_WEBHOOKS()->loadFromEnv();
$webhooks = EnvVars::VISITS_WEBHOOKS->loadFromEnv();
return [
'visits_webhooks' => [
'webhooks' => $webhooks === null ? [] : explode(',', $webhooks),
'notify_orphan_visits_to_webhooks' =>
(bool) EnvVars::NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS()->loadFromEnv(false),
(bool) EnvVars::NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS->loadFromEnv(false),
],
];

View File

@@ -13,20 +13,22 @@ 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 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::cases())
? new EnvVarLoaderProvider('config/params/generated_config.php', Core\Config\EnvVars::values())
: 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,
@@ -43,6 +45,9 @@ return (new ConfigAggregator\ConfigAggregator([
$isTestEnv
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
: new ConfigAggregator\ArrayProvider([]),
// 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,
]))->getMergedConfig();

View File

@@ -13,7 +13,7 @@ chdir(dirname(__DIR__));
require 'vendor/autoload.php';
// This is one of the first files loaded. Configure the timezone here
date_default_timezone_set(EnvVars::TIMEZONE()->loadFromEnv(date_default_timezone_get()));
date_default_timezone_set(EnvVars::TIMEZONE->loadFromEnv(date_default_timezone_get()));
// This class alias tricks the ConfigAbstractFactory to return Lock\Factory instances even with a different service name
// It needs to be placed here as individual config files will not be loaded once config is cached

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,5 @@ 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;

View File

@@ -8,9 +8,10 @@ use GuzzleHttp\Client;
use Laminas\ConfigAggregator\ConfigAggregator;
use Laminas\Diactoros\Response\EmptyResponse;
use Laminas\ServiceManager\Factory\InvokableFactory;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
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 +21,61 @@ use SebastianBergmann\CodeCoverage\Filter;
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);
@@ -76,16 +114,10 @@ $buildDbConnection = static function (): array {
};
};
$buildTestLoggerConfig = fn (string $handlerName, string $filename) => [
'handlers' => [
$handlerName => [
'name' => StreamHandler::class,
'params' => [
'level' => Logger::DEBUG,
'stream' => sprintf('data/log/api-tests/%s', $filename),
],
],
],
$buildTestLoggerConfig = static fn (string $filename) => [
'level' => Level::Debug->value,
'type' => LoggerType::STREAM->value,
'destination' => sprintf('data/log/api-tests/%s', $filename),
];
return [
@@ -98,14 +130,13 @@ return [
'schema' => 'http',
'hostname' => 'doma.in',
],
'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',
@@ -119,17 +150,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'],
@@ -163,13 +187,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' => [
@@ -178,13 +258,14 @@ return [
'data_fixtures' => [
'paths' => [
// TODO These are used for CLI tests too, so maybe should be somewhere else
__DIR__ . '/../../module/Rest/test-api/Fixtures',
],
],
'logger' => [
'Shlink' => $buildTestLoggerConfig('shlink_handler', 'shlink.log'),
'Access' => $buildTestLoggerConfig('access_handler', 'access.log'),
'Shlink' => $buildTestLoggerConfig('shlink.log'),
'Access' => $buildTestLoggerConfig('access.log'),
],
];

View File

@@ -11,7 +11,7 @@ server {
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
include fastcgi.conf;
}

View File

@@ -1,8 +1,8 @@
FROM php:8.1.5-fpm-alpine3.15
FROM php:8.1.9-fpm-alpine3.16
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
ENV APCU_VERSION 5.1.21
ENV PDO_SQLSRV_VERSION 5.10.0
ENV PDO_SQLSRV_VERSION 5.10.1
ENV MS_ODBC_SQL_VERSION 17.5.2.2
RUN apk update

View File

@@ -0,0 +1,73 @@
FROM php:8.1.9-alpine3.16
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
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 docker-php-ext-install sockets
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/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 && \
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
# 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,10 +1,10 @@
FROM php:8.1.5-alpine3.15
FROM php:8.1.9-alpine3.16
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 PDO_SQLSRV_VERSION 5.10.0
ENV PDO_SQLSRV_VERSION 5.10.1
ENV MS_ODBC_SQL_VERSION 17.5.2.2
RUN apk update

View File

@@ -8,8 +8,8 @@ use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
use Shlinkio\Shlink\Core\Entity\Visit;
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
{
@@ -27,7 +27,7 @@ final class Version20210207100807 extends AbstractMigration
]);
$visits->addColumn('type', Types::STRING, [
'length' => 255,
'default' => Visit::TYPE_VALID_SHORT_URL,
'default' => VisitType::VALID_SHORT_URL->value,
]);
}

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

@@ -73,6 +73,30 @@ services:
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_mercure
- shlink_mercure_proxy
- shlink_rabbitmq
environment:
LC_ALL: C
extra_hosts:
- 'host.docker.internal:host-gateway'
shlink_db_mysql:
container_name: shlink_db_mysql
image: mysql:5.7
@@ -144,8 +168,8 @@ services:
- "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

@@ -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

@@ -4,22 +4,16 @@ declare(strict_types=1);
namespace Shlinkio\Shlink;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Shlinkio\Shlink\Common\Logger\LoggerType;
use function Shlinkio\Shlink\Config\runningInRoadRunner;
return [
'logger' => [
'Shlink' => [
'handlers' => [
'shlink_handler' => [
'name' => StreamHandler::class,
'params' => [
'level' => Logger::INFO,
'stream' => 'php://stdout',
],
],
],
'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

@@ -16,7 +16,7 @@ The intention is to implement a system that allows adding to API keys as many of
Supporting more restrictions in the future is also desirable.
## Considered option
## Considered options
* Using an ACL/RBAC library, and checking roles in a middleware.
* Using a service that, provided an API key, tells if certain resource is reachable while it also allows building queries dynamically.

View File

@@ -11,7 +11,7 @@ However, it does not track visits to any of those, just to valid short URLs.
The intention is to change that, and allow users to track the cases mentioned above.
## Considered option
## Considered options
* Create a new table to track visits o this kind.
* Reuse the existing `visits` table, by making `short_url_id` nullable and adding a couple of other fields.

View File

@@ -13,7 +13,7 @@ However, after the creation of the caching PSRs ([PSR-6 - Cache](https://www.php
Also, Shlink needs support for Redis clusters and Redis sentinels, which is not supported by `doctrine/cache` Redis adapters.
## Considered option
## Considered options
After some research, the only packages that seem to support the capabilities required by Shlink and also seem healthy, are these:

View File

@@ -11,7 +11,7 @@ It is potentially possible to combine both, but if you do so, you will find out
A [Twitter survey](https://twitter.com/shlinkio/status/1480614855006732289) has also showed up all participants also found the behavior should be the opposite.
## Considered option
## Considered options
* Move the logic to read env vars to another config file which always overrides installer options.
* Move the logic to read env vars to a config post-processor which overrides config dynamically, only if the appropriate env var had been defined.

View File

@@ -0,0 +1,42 @@
# Support multi-segment custom slugs
* Status: Accepted
* Date: 2022-08-05
## Context and problem statement
There's a new requirement to support multi-segment custom slugs (as in `https://exam.ple/foo/bar/baz`).
The internal router does not support this at the moment, as it only matches the shortCode in one of the segments.
## Considered options
* Tweak the internal router, so that it is capable of matching multiple segments for the slug, in every route that requires it.
* Define a new set of routes with a short prefix that allows configuring multi-segment in those, without touching the existing routes.
* Let the router fail, and use a middleware to fall back to the proper route (similar to what was done for the extra path forwarding feature).
## Decision outcome
Even though I was initially inclined to use a fallback middleware, that has turned out to be harder than anticipated, because there are several possible routes where the slug is used, and we would still need some kind of router to determine which one matches.
Because of that, the selected approach has been to tweak the existing router, so that it can match multiple segments, and moving the configuration of routes to a common place so that they can be defined in the proper order that prevents conflicts.
## Pros and Cons of the Options
### Tweaking the router
* Bad: It requires routes to be defined in a specific order, and remember it in the future if more routes are added.
* Good: It initially requires fewer changes.
* Good: Once routes are defined in the proper order, all the internal logic works out of the box.
### Defining new routes
* Bad: The end-user experience gets affected.
* Bad: Probably a lot of side effects would happen when it comes to assembling short URLs.
* Bad: Routing needs to be configured twice, resolving the same logic.
* Bad: It turns out to still conflict with some routes, even with the prefix, which defeats what looked like its main benefit.
### Let routing fail and fall back in middleware
* Good: Does not require changing routes configuration, which means less side effects.
* Bad: Since many routes can potentially end up in the middleware, there's still the need to have some kind of routing logic.

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.
* [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)
* [2021-02-07 Track visits to 'base_url', 'invalid_short_url' and 'regular_404'](2021-02-07-track-visits-to-base-url-invalid-short-url-and-regular-404.md)

View File

@@ -1,8 +1,8 @@
{
"asyncapi": "2.0.0",
"asyncapi": "2.4.0",
"info": {
"title": "Shlink",
"version": "2.0.0",
"version": "3.0.0",
"description": "Shlink, the self-hosted URL shortener",
"license": {
"name": "MIT",
@@ -75,6 +75,23 @@
}
}
}
},
"https://shlink.io/new-short-url": {
"subscribe": {
"summary": "Receive information about any new short URL.",
"operationId": "newshortUrl",
"message": {
"payload": {
"type": "object",
"additionalProperties": false,
"properties": {
"shortUrl": {
"$ref": "#/components/schemas/ShortUrl"
}
}
}
}
}
}
},
"components": {
@@ -101,7 +118,7 @@
},
"visitsCount": {
"type": "integer",
"description": "The number of visits that this short URL has recieved."
"description": "The number of visits that this short URL has received."
},
"tags": {
"type": "array",

View File

@@ -33,7 +33,7 @@
},
"visitsCount": {
"type": "integer",
"description": "The number of visits that this short URL has recieved."
"description": "The number of visits that this short URL has received."
},
"tags": {
"type": "array",

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

@@ -327,11 +327,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 +342,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 +375,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

@@ -85,19 +85,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

@@ -83,8 +83,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"
}
}
}
@@ -203,8 +206,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 +242,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"
}
}
}
@@ -312,19 +321,33 @@
},
"threshold": {
"type": "number",
"description": "The amount of visits currently configured as threshold to allow deleting short UYRLs or not"
"description": "The amount of visits currently configured as threshold to allow deleting short URLs or not"
}
}
}
]
},
"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 +378,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

@@ -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

@@ -53,7 +53,7 @@
{
"name": "errorCorrection",
"in": "query",
"description": "The error correction level to apply to the the QR code: **[L]**ow, **[M]**edium, **[Q]**uartile or **[H]**igh. See [docs](https://www.qrcode.com/en/about/error_correction.html).",
"description": "The error correction level to apply to the QR code: **[L]**ow, **[M]**edium, **[Q]**uartile or **[H]**igh. See [docs](https://www.qrcode.com/en/about/error_correction.html).",
"required": false,
"schema": {
"type": "string",

View File

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

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Run docker containers if they are not up yet
if ! [[ $(docker ps | grep shlink_swoole) ]]; then
if ! [[ $(docker ps | grep shlink) ]]; then
docker-compose up -d
fi

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

@@ -11,11 +11,13 @@ return [
Command\ShortUrl\CreateShortUrlCommand::NAME => Command\ShortUrl\CreateShortUrlCommand::class,
Command\ShortUrl\ResolveUrlCommand::NAME => Command\ShortUrl\ResolveUrlCommand::class,
Command\ShortUrl\ListShortUrlsCommand::NAME => Command\ShortUrl\ListShortUrlsCommand::class,
Command\ShortUrl\GetVisitsCommand::NAME => Command\ShortUrl\GetVisitsCommand::class,
Command\ShortUrl\GetShortUrlVisitsCommand::NAME => Command\ShortUrl\GetShortUrlVisitsCommand::class,
Command\ShortUrl\DeleteShortUrlCommand::NAME => Command\ShortUrl\DeleteShortUrlCommand::class,
Command\Visit\LocateVisitsCommand::NAME => Command\Visit\LocateVisitsCommand::class,
Command\Visit\DownloadGeoLiteDbCommand::NAME => Command\Visit\DownloadGeoLiteDbCommand::class,
Command\Visit\GetOrphanVisitsCommand::NAME => Command\Visit\GetOrphanVisitsCommand::class,
Command\Visit\GetNonOrphanVisitsCommand::NAME => Command\Visit\GetNonOrphanVisitsCommand::class,
Command\Api\GenerateKeyCommand::NAME => Command\Api\GenerateKeyCommand::class,
Command\Api\DisableKeyCommand::NAME => Command\Api\DisableKeyCommand::class,
@@ -24,9 +26,11 @@ return [
Command\Tag\ListTagsCommand::NAME => Command\Tag\ListTagsCommand::class,
Command\Tag\RenameTagCommand::NAME => Command\Tag\RenameTagCommand::class,
Command\Tag\DeleteTagsCommand::NAME => Command\Tag\DeleteTagsCommand::class,
Command\Tag\GetTagVisitsCommand::NAME => Command\Tag\GetTagVisitsCommand::class,
Command\Domain\ListDomainsCommand::NAME => Command\Domain\ListDomainsCommand::class,
Command\Domain\DomainRedirectsCommand::NAME => Command\Domain\DomainRedirectsCommand::class,
Command\Domain\GetDomainVisitsCommand::NAME => Command\Domain\GetDomainVisitsCommand::class,
Command\Db\CreateDatabaseCommand::NAME => Command\Db\CreateDatabaseCommand::class,
Command\Db\MigrateDatabaseCommand::NAME => Command\Db\MigrateDatabaseCommand::class,

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