Compare commits

...

218 Commits

Author SHA1 Message Date
Alejandro Celaya
68e0aa1ea9 Merge pull request #1433 from shlinkio/develop
Release 3.1.0
2022-04-23 11:39:27 +02:00
Alejandro Celaya
ceaf64c9b3 Fixed typo in changelog 2022-04-23 11:35:55 +02:00
Alejandro Celaya
e015b1bec5 Improved changelog 2022-04-23 11:34:40 +02:00
Alejandro Celaya
ca9726c997 Merge pull request #1432 from acelaya-forks/feature/domain-visits
Feature/domain visits
2022-04-23 11:33:04 +02:00
Alejandro Celaya
85c79abd30 Updated changelog 2022-04-23 11:19:14 +02:00
Alejandro Celaya
54c1c7ad84 Created DomainVisits API test 2022-04-23 11:17:32 +02:00
Alejandro Celaya
af15e31b42 Created DomainVisitsActionTest 2022-04-23 11:07:10 +02:00
Alejandro Celaya
99b4f9f4dd Improved VisitsStatsHelperTest covering visitsForDomain method 2022-04-23 11:02:51 +02:00
Alejandro Celaya
9a0e5ea626 Created method to check if domain exists based on authority and API key 2022-04-23 10:58:33 +02:00
Alejandro Celaya
984205e02c Extended VisitRepositoryTest with domain visits functions 2022-04-23 10:45:42 +02:00
Alejandro Celaya
e11bf6ac67 Created endpoint to get visits for one specific domain 2022-04-23 10:32:07 +02:00
Alejandro Celaya
e029d91544 Documented new domain visits endpoint 2022-04-23 09:27:52 +02:00
Alejandro Celaya
011856cbfa Removed redundant var 2022-04-23 09:15:01 +02:00
Alejandro Celaya
9ce8164013 Merge pull request #1431 from acelaya-forks/feature/update-deps
Updated docker images and dependencies
2022-04-23 09:13:47 +02:00
Alejandro Celaya
dca6b7bbf5 Updated docker images and dependencies 2022-04-23 08:56:25 +02:00
Alejandro Celaya
2511ec3395 Merge pull request #1424 from acelaya-forks/feature/skip-invalid-imports
Added errorhandling for individual imported URLs, so that one failing…
2022-04-23 08:35:21 +02:00
Alejandro Celaya
9f6ffc7186 Added errorhandling for individual imported URLs, so that one failing doe snot make the whole process fail 2022-04-18 14:45:37 +02:00
Alejandro Celaya
622f0217fa Merge pull request #1421 from acelaya-forks/feature/cast-incoming-dates
Feature/cast incoming dates
2022-04-15 20:20:26 +02:00
Alejandro Celaya
09eba49bab Updated changelog 2022-04-15 20:06:51 +02:00
Alejandro Celaya
c20c3801a8 Ensured all input dates are changed to the default timezone before being used on any inner layer 2022-04-15 19:57:27 +02:00
Alejandro Celaya
f8208b7288 Merge pull request #1420 from acelaya-forks/feature/timezone
Feature/timezone
2022-04-15 09:27:17 +02:00
Alejandro Celaya
3db8a65ddb Fixed test 2022-04-14 16:00:15 +02:00
Alejandro Celaya
0495b6f298 Updated changelog 2022-04-14 14:24:01 +02:00
Alejandro Celaya
52c55f385d Added support to set the timezone via config/env vars 2022-04-14 14:22:48 +02:00
Alejandro Celaya
fe28d6fba0 Merge pull request #1417 from acelaya-forks/feature/kutt-import
Updated importer with support for Kutt.it
2022-04-14 12:51:56 +02:00
Alejandro Celaya
0294e49d4a Added dist local config for app options 2022-04-14 11:35:12 +02:00
Alejandro Celaya
cbaf51d3ef Updated importer with support for Kutt.it 2022-04-14 11:31:50 +02:00
Alejandro Celaya
efb604a381 Merge pull request #1415 from acelaya-forks/feature/yourls-domain
Feature/yourls domain
2022-04-13 12:54:12 +02:00
Alejandro Celaya
da87f05126 Updated changelog 2022-04-13 12:41:09 +02:00
Alejandro Celaya
21534b78cb Updated to latest shlink-importer, with support to import on a specific domain for YOURLS 2022-04-13 12:40:21 +02:00
Alejandro Celaya
ab65593f7d Merge pull request #1414 from acelaya-forks/feature/postgres-db-error
Feature/postgres db error
2022-04-12 19:27:35 +02:00
Alejandro Celaya
3a82691503 Small improvements on CreateDatabaseCommand 2022-04-10 19:48:32 +02:00
Alejandro Celaya
430e2ff0b5 Ensured db and api tests can be run without the need of creating the database beforehand 2022-04-09 17:46:13 +02:00
Alejandro Celaya
7d572e7988 Merge pull request #1404 from acelaya-forks/feature/fix-double-paths
Feature/fix double paths
2022-03-14 19:56:58 +01:00
Alejandro Celaya
1449e24b66 Improved some tests 2022-03-14 19:41:33 +01:00
Alejandro Celaya
6a671760da Updated changelog 2022-03-14 19:28:55 +01:00
Alejandro Celaya
613bdd82b0 Ensured base path is not prefixed more than it should 2022-03-14 19:26:02 +01:00
Alejandro Celaya
01bae358f9 Merge pull request #1399 from shlinkio/feature/improve-db-create
Feature/improve db create
2022-03-05 11:03:39 +01:00
Alejandro Celaya
3a8e560dc5 Increased required mutation score for unit tests to 85% 2022-03-05 10:51:48 +01:00
Alejandro Celaya
a0c538d9ee Updated changelog 2022-03-05 10:48:02 +01:00
Alejandro Celaya
07c30f86e9 Excluded migrations table when checking if the database schema exists 2022-03-05 10:41:13 +01:00
Alejandro Celaya
c22e38f9a0 Removed deprecated method call 2022-03-05 10:32:05 +01:00
Alejandro Celaya
7502e8a1e4 Merge pull request #1386 from acelaya-forks/feature/mercure-error
Feature/mercure error
2022-02-20 15:58:49 +01:00
Alejandro Celaya
5a25211371 Created NotConfiguredMercureErrorHandlerTest 2022-02-20 10:50:21 +01:00
Alejandro Celaya
6983f9b2bf Added middleware that mitigates big error traces being logged for those not using mercure 2022-02-20 10:36:54 +01:00
Alejandro Celaya
5affe64b61 Removed references in CONTRIBUTING.md file to no longer existing assets 2022-02-19 19:55:36 +01:00
Alejandro Celaya
c52f3c396b Fixed merge conflicts 2022-02-19 19:47:34 +01:00
Alejandro Celaya
e1ebbaa52f Merge pull request #1384 from acelaya-forks/feature/default-domain-role
Feature/default domain role
2022-02-19 19:42:12 +01:00
Alejandro Celaya
7abe6af5ec Updated changelog 2022-02-19 19:24:43 +01:00
Alejandro Celaya
c98ea6055b Ensured API keys cannot be generated with domain-only roles linked to default domain 2022-02-19 19:23:36 +01:00
Alejandro Celaya
3e3d255edf Merge pull request #1383 from acelaya-forks/feature/fixes
Updated docker images to PHP 8.1.3
2022-02-19 19:11:30 +01:00
Alejandro Celaya
816d4851e7 Updated docker images to PHP 8.1.3 2022-02-19 18:57:36 +01:00
Alejandro Celaya
79af315b9f Fixed merge conflicts 2022-02-10 21:48:21 +01:00
Alejandro Celaya
4110c702c0 Merge pull request #1376 from acelaya-forks/feature/release-3.0.2
Feature/release 3.0.2
2022-02-10 21:44:30 +01:00
Roy-Orbison
57eb29c3c8 Optimise RewriteRules/Conds
From upstream changes on Mezzio Skeleton.

Closes #1369.
2022-02-10 21:31:45 +01:00
Alejandro Celaya
5267c4eee6 Updated changelog 2022-02-10 21:31:30 +01:00
Alejandro Celaya
1453ebe8ca Updated to shlink-installer 7.0.1 2022-02-10 21:29:28 +01:00
Alejandro Celaya
3b5cea5768 Updated changelog 2022-02-07 18:49:22 +01:00
Roy-Orbison
a89f67348d Optimise RewriteRules/Conds
From upstream changes on Mezzio Skeleton.

Closes #1369.
2022-02-07 18:45:27 +01:00
Alejandro Celaya
af1ae0399c Fixed merge conflicts 2022-02-04 18:03:29 +01:00
Alejandro Celaya
5bf84144e7 Tagged v3.0.1 in changelog 2022-02-04 17:53:29 +01:00
Alejandro Celaya
d9adff5749 Merge pull request #1368 from acelaya-forks/feature/stable-pdo-sqlsrv
Updated to stable pdo_sqlsrv in docker images
2022-02-03 22:17:07 +01:00
Alejandro Celaya
a1cd8baf3e Updated to stable pdo_sqlsrv in docker images 2022-02-03 22:03:20 +01:00
Alejandro Celaya
87cadce0ac Merge pull request #1366 from acelaya-forks/feature/fix-autoresolve-titles
Feature/fix autoresolve titles
2022-02-01 20:00:49 +01:00
Alejandro Celaya
f22f50afa2 Updated changelog 2022-02-01 19:46:36 +01:00
Alejandro Celaya
d0fa6f7e03 Added missing test covering URL validation with valid URL but title resolutio is disabled 2022-02-01 19:44:14 +01:00
Alejandro Celaya
d29c58dce5 Unified test:unit:pretty command 2022-02-01 19:25:18 +01:00
Alejandro Celaya
9ea8f3b590 Fixed URL validation still being true by default 2022-02-01 19:12:53 +01:00
Alejandro Celaya
ffffc68144 Updated readme 2022-02-01 07:36:44 +01:00
Alejandro Celaya
086de9f2a0 Merge pull request #1362 from acelaya-forks/feature/deprecate-webhooks
Deprecated webhooks
2022-01-31 12:41:44 +01:00
Alejandro Celaya
1b731aa4a3 Deprecated webhooks 2022-01-31 12:30:29 +01:00
Alejandro Celaya
12913f6b90 Merge pull request #1360 from acelaya-forks/feature/hide-db-commands
Marked database commands as hidden
2022-01-30 13:04:10 +01:00
Alejandro Celaya
1d4186392c Marked database commands as hidden 2022-01-30 12:15:53 +01:00
Alejandro Celaya
48d3ab0cb4 Changed file name used for inlined OAS 2022-01-30 09:37:57 +01:00
Alejandro Celaya
a9d04729eb Merge pull request #1351 from shlinkio/develop
Release 3.0.0
2022-01-28 16:31:55 +01:00
Alejandro Celaya
7adf2292bd Merge pull request #1353 from acelaya-forks/feature/profiling
Feature/profiling
2022-01-28 16:12:27 +01:00
Alejandro Celaya
c8f55f9c05 Added release date for Shlink 3.0.0 2022-01-28 16:00:40 +01:00
Alejandro Celaya
93de62f81d Fixed typo in UPGRADE.md 2022-01-28 13:06:56 +01:00
Alejandro Celaya
9766231d41 Added v3.0.0 to changelog 2022-01-27 20:59:05 +01:00
Alejandro Celaya
9df80e5bec Added explicit versions for shlink dependencies 2022-01-27 20:56:52 +01:00
Alejandro Celaya
81b00e4302 Merge branch 'develop' into feature/profiling 2022-01-27 20:20:15 +01:00
Alejandro Celaya
25ac7c31c4 Minor doc improvements 2022-01-25 20:39:31 +01:00
Alejandro Celaya
11c6c9a2b8 Removed unneeded lines 2022-01-23 18:17:39 +01:00
Alejandro Celaya
066268765a Fixed merge conflicts 2022-01-23 18:17:13 +01:00
Alejandro Celaya
356b33ced0 Merge pull request #1350 from acelaya-forks/feature/fix-memory-leak
Updated to shlink-common 4.4, which no longer uses doctrine/cache
2022-01-23 18:14:00 +01:00
Alejandro Celaya
77088d55f9 Updated to shlink-common 4.4, which no longer uses doctrine/cache 2022-01-23 17:54:49 +01:00
Alejandro Celaya
8d965655a8 Merge pull request #1348 from acelaya-forks/feature/drop-swoole-support
Feature/drop swoole support
2022-01-23 11:52:19 +01:00
Alejandro Celaya
3ace4952e6 Changed swoole with openswoole in issue templates 2022-01-23 11:37:46 +01:00
Alejandro Celaya
299f9f3a10 Documented support on swoole being dropped 2022-01-23 11:36:05 +01:00
Alejandro Celaya
0e6790cdab Replaced references to regular swoole by openswoole 2022-01-23 11:29:53 +01:00
Alejandro Celaya
1f90af3aec Merge pull request #1345 from acelaya-forks/feature/extended-tags-ordering
Feature/extended tags ordering
2022-01-23 11:08:02 +01:00
Alejandro Celaya
cdb18a5baf Documented performance issue when sorting by visits or short URLs count 2022-01-23 10:48:38 +01:00
Alejandro Celaya
8adb6596fb Refactored TagInfo to wrap the raw tag name instead of a Tag entity 2022-01-23 09:37:02 +01:00
Alejandro Celaya
dd6bcd68cc Removed not-needed extra line 2022-01-22 20:36:50 +01:00
Alejandro Celaya
1c9ce0ede0 Fixed default/fallback tags with stats ordering 2022-01-21 22:22:55 +01:00
Alejandro Celaya
6b409b06cc Simplified TagRepository test for tags info list, making it more predictable 2022-01-21 22:04:53 +01:00
Alejandro Celaya
361e864415 Added fallback ordering to tags list 2022-01-21 20:12:16 +01:00
Alejandro Celaya
d5606114cd Documented new ordering fields supported on tags list 2022-01-21 20:02:52 +01:00
Alejandro Celaya
afca66d655 Added tests covering tags info with counted ordering and limit 2022-01-21 19:58:56 +01:00
Alejandro Celaya
33a6c9fda7 Added support to order tags with stats by short URLs or visits count. In a non-performant way 2022-01-21 19:52:25 +01:00
Alejandro Celaya
a198484ab6 Updated test utils lib 2022-01-21 19:21:30 +01:00
Alejandro Celaya
dd5bce9694 Merge pull request #1344 from acelaya-forks/feature/strinct-env-vars
Feature/strinct env vars
2022-01-20 21:08:33 +01:00
Alejandro Celaya
bef17ff76d Fixed inverted condition when determining locks 2022-01-20 20:56:38 +01:00
Alejandro Celaya
7202605fc8 Created EnvVarsTest 2022-01-20 20:40:34 +01:00
Alejandro Celaya
747dac531d Added a more strict way to handle valid and expected env vars 2022-01-20 20:16:37 +01:00
Alejandro Celaya
07d24f70e1 Merge pull request #1343 from acelaya-forks/feature/inline-specs-improvements
Feature/inline specs improvements
2022-01-18 20:27:02 +01:00
Alejandro Celaya
d0546a2ea2 Split spec to join ApiKey spec with short URLs, into inlined and regular versions 2022-01-18 20:14:24 +01:00
Alejandro Celaya
9e9621e7b2 Standardized how inlined or regular specs are applied to query builders 2022-01-18 20:06:32 +01:00
Alejandro Celaya
d39f3b4265 Enhanced TagRepositoryTest and replaced inlined quoting by doctrine connection quoting 2022-01-18 19:50:48 +01:00
Alejandro Celaya
223339cd61 Merge pull request #1337 from acelaya-forks/feature/short-urls-filtering
Feature/short urls filtering
2022-01-17 20:33:40 +01:00
Alejandro Celaya
dc430bae10 Refactored method in ShortUrlsRepository 2022-01-17 20:21:35 +01:00
Alejandro Celaya
661b07e12f Refactored ShortUrlRepository to wrap args into DTOs 2022-01-17 20:10:41 +01:00
Alejandro Celaya
0727c7bdfb Updated readme file 2022-01-17 19:12:50 +01:00
Alejandro Celaya
b4c52116b4 Enabled stryker report for infection 2022-01-17 07:41:33 +01:00
Alejandro Celaya
89dc6108b7 Merge pull request #1334 from acelaya-forks/feature/tackle-todos
Feature/tackle todos
2022-01-16 16:06:21 +01:00
Alejandro Celaya
492eba3a8b Fixed duplicated slashes generated in path when doing not-found redirects with placeholders 2022-01-16 15:54:22 +01:00
Alejandro Celaya
77fee1390f Renamed class to a more appropriate name 2022-01-16 15:41:20 +01:00
Alejandro Celaya
bfb54189b8 Moved some config to the proper namespace, now that config is no longer part of the public contract 2022-01-16 15:34:13 +01:00
Alejandro Celaya
fb43885d85 Merge pull request #1333 from acelaya-forks/feature/all-visits-endpoint
Feature/all visits endpoint
2022-01-16 12:49:01 +01:00
Alejandro Celaya
7c1f705e64 Created NonOrphanVisitsPaginatorAdapter test 2022-01-16 12:29:36 +01:00
Alejandro Celaya
fe1fa7689a Created endpoint to list non-orphan visits 2022-01-16 12:24:02 +01:00
Alejandro Celaya
8b79eee081 Updated changelog 2022-01-16 12:08:11 +01:00
Alejandro Celaya
4a3e04ced9 Added tests covering count non orphan visits with different combinations of filters 2022-01-16 11:44:12 +01:00
Alejandro Celaya
61618250ec Renamed countVisits to countNonOrphanVisits, and updated its signature to expect a VisitsCountFiltering DTO 2022-01-16 11:15:39 +01:00
Alejandro Celaya
60c0ca3ae5 Changed VisitsCountFiltering and VisitsListFiltering so that they encapsulate an ApiKey instead of a Spec 2022-01-16 10:56:37 +01:00
Alejandro Celaya
3436405c55 Merge branch 'develop' into feature/all-visits-endpoint 2022-01-16 10:23:22 +01:00
Alejandro Celaya
d43c3ec865 Merge pull request #1326 from acelaya-forks/feature/high-priority-env-vars
Feature/high priority env vars
2022-01-15 17:46:03 +01:00
Alejandro Celaya
545da96d15 Updated env vars ADR 2022-01-15 17:21:36 +01:00
Alejandro Celaya
f53305c404 Added ADR for the changes to load env vars on top of installer config 2022-01-15 17:17:22 +01:00
Alejandro Celaya
199d976e3d Updated changelog and upgrading doc 2022-01-15 16:55:57 +01:00
Alejandro Celaya
a1366f0ef1 Exposed port 8888 on php container for experimentation 2022-01-15 16:52:48 +01:00
Alejandro Celaya
91192a8a8f Updated to latest shlink-installer and shlink-config, ensuring env vars are properly loaded 2022-01-15 16:06:24 +01:00
Alejandro Celaya
c6f16b0558 Updated to latest installer with support for env vars 2022-01-15 11:34:17 +01:00
Alejandro Celaya
0d37eb65c9 Used PhpFileProvider to load installer generated config 2022-01-13 17:11:23 +01:00
Alejandro Celaya
f7e3a74794 Merge pull request #1323 from acelaya-forks/feature/doctrine-2.11
Updated to doctrine 2.11
2022-01-12 21:03:02 +01:00
Alejandro Celaya
976b07cd61 Updated to doctrine 2.11 2022-01-12 20:48:42 +01:00
Alejandro Celaya
cff9cd5fb8 Documented endpoint to get all non-orphan visits 2022-01-10 22:23:00 +01:00
Alejandro Celaya
f0fd947046 Moved existing paginator adapters that are related with visits to the Visits namespace 2022-01-10 22:16:33 +01:00
Alejandro Celaya
7f4ada9c4b Created method in VisitRepository to fetch all non-orphan visits 2022-01-10 21:43:32 +01:00
Alejandro Celaya
db4ef328b1 Renamed some visits paginator adapters for consistency 2022-01-10 20:26:33 +01:00
Alejandro Celaya
b438802e71 Merge pull request #1321 from acelaya-forks/feature/update-docker-deps
Feature/update docker deps
2022-01-10 17:30:34 +01:00
Alejandro Celaya
632a19ceeb Updated changelog 2022-01-10 17:12:09 +01:00
Alejandro Celaya
629f8ece7a Updated to latest docker images and openswoole 2022-01-10 17:10:36 +01:00
Alejandro Celaya
9215f9beb5 Merge pull request #1320 from acelaya-forks/feature/infection-update
Updated to infection 0.26
2022-01-10 15:37:05 +01:00
Alejandro Celaya
154431e86c Updated to infection 0.26 2022-01-10 15:15:16 +01:00
Alejandro Celaya
8cfb14198b Merge pull request #1319 from acelaya-forks/feature/emoji-support
Feature/emoji support
2022-01-10 14:51:13 +01:00
Alejandro Celaya
2ed475fc76 Ensure database fields are created with proper charset and collation in MySQL 2022-01-10 14:37:44 +01:00
Alejandro Celaya
34512da2fb Fixed indentation 2022-01-10 13:21:12 +01:00
Alejandro Celaya
5b3c6f7752 Fixed charset in local entity manager config 2022-01-10 13:09:24 +01:00
Alejandro Celaya
f4dd27ca3f Updated changelog 2022-01-10 13:05:40 +01:00
Alejandro Celaya
ce47d8c591 Added full support for emojis 2022-01-10 13:04:16 +01:00
Alejandro Celaya
b941ee9aa9 Removed usage of deprecated methods from migrations 2022-01-10 12:05:01 +01:00
Alejandro Celaya
45de3f0128 Ensured emojis in short URLs are not URL-encoded 2022-01-10 11:13:16 +01:00
Alejandro Celaya
41d3826c1a Ensured bars are replaced by dashes in custom slugs 2022-01-10 10:43:20 +01:00
Alejandro Celaya
f2ff6e6a70 Merge pull request #1318 from acelaya-forks/feature/custom-slug-simplification
Simplified how the custom slugs are processed, allowing more characte…
2022-01-10 10:28:17 +01:00
Alejandro Celaya
e47c90c645 Simplified how the custom slugs are processed, allowing more characters in the process 2022-01-09 21:02:23 +01:00
Alejandro Celaya
d2fef20239 Merge pull request #1317 from acelaya-forks/feature/tag-stats-endpoint
Feature/tag stats endpoint
2022-01-09 18:02:03 +01:00
Alejandro Celaya
3b359cfc4f Reduced amount of duplicated code in API tests 2022-01-09 17:47:19 +01:00
Alejandro Celaya
acfc5a4676 Updated changelog 2022-01-09 17:38:45 +01:00
Alejandro Celaya
a6b1647f27 Created TagStatsActionTest 2022-01-09 17:37:00 +01:00
Alejandro Celaya
d5851bbb6a Created TagsStats endpoint 2022-01-09 17:24:07 +01:00
Alejandro Celaya
397bbe2655 Merge pull request #1316 from acelaya-forks/feature/tags-ordering
Feature/tags ordering
2022-01-09 14:10:34 +01:00
Alejandro Celaya
95d8d3ef72 Added ordering by name support for tags list with stats 2022-01-09 13:38:59 +01:00
Alejandro Celaya
1b51a1aedd Added ordering support for tags list when not requesting stats 2022-01-09 13:31:08 +01:00
Alejandro Celaya
ff75b3cd1f Enhanced test covering list short URLs with invalid params 2022-01-09 11:28:32 +01:00
Alejandro Celaya
2abcaf02e2 Standardized ordering field handling and added validation for short URLs list 2022-01-09 11:23:27 +01:00
Alejandro Celaya
d0c9f5a776 Fixed merge conflicts 2022-01-08 17:40:49 +01:00
Alejandro Celaya
a46d510e2b Merge pull request #1314 from acelaya-forks/feature/paginated-tags-performance
Feature/paginated tags performance
2022-01-08 17:37:01 +01:00
Alejandro Celaya
2d861b4077 Improved performance when loading paginated tags, by using an ugly compound of native queries and DQL 2022-01-08 17:25:09 +01:00
Alejandro Celaya
a667c957ee Added Twitter follow badge to readme 2022-01-07 16:15:47 +01:00
Alejandro Celaya
107c09604a Fixed performance issues on list tags endpoint when requesting it with stats 2022-01-06 19:01:00 +01:00
Alejandro Celaya
2b0567b368 Fixed typo 2022-01-06 18:35:50 +01:00
Alejandro Celaya
d00a56bec0 Fixed query to count tags when a search term is present 2022-01-06 12:22:05 +01:00
Alejandro Celaya
ead8cc6cec Merge pull request #1302 from acelaya-forks/feature/paginated-tags
Feature/paginated tags
2022-01-06 11:54:30 +01:00
Alejandro Celaya
806ff9daaf Updated changelog 2022-01-06 11:40:20 +01:00
Alejandro Celaya
b3863a3e10 Improved TagServiceTest, covering tagsInfo method with params 2022-01-06 11:36:08 +01:00
Alejandro Celaya
5559107776 Changed namespace for database tests to ShlinkioDbTest 2022-01-06 11:01:21 +01:00
Alejandro Celaya
af1cf806f0 Created tag paginator adapter tests 2022-01-06 10:55:57 +01:00
Alejandro Celaya
0cf33c6119 Added DB test for TagsPaginator 2022-01-06 10:35:01 +01:00
Alejandro Celaya
b38b8a3365 Extended TagRepositoryTest, covering filterings on tags 2022-01-06 10:13:37 +01:00
Alejandro Celaya
e998c8434d Extracted tags filtering params to a DTO 2022-01-06 09:50:43 +01:00
Alejandro Celaya
4b90cf93d3 Created DB-level paginator for tags with stats 2022-01-05 23:44:14 +01:00
Alejandro Celaya
3dd4e33758 Created DB-level paginator for tags without stats 2022-01-05 23:30:35 +01:00
Alejandro Celaya
6caeb11598 Added output logs for swoole during API tests 2022-01-05 22:14:09 +01:00
Alejandro Celaya
11a383b7e5 Extracted common logic from TagService to a private method 2022-01-05 19:25:50 +01:00
Alejandro Celaya
fd2a2530b1 Documented pagination for tags endpoint 2022-01-05 19:21:32 +01:00
Alejandro Celaya
2f42b2d072 Added API tests covering pagination for tags 2022-01-05 19:16:49 +01:00
Alejandro Celaya
775f58f972 Added support for pagination in tags lists 2022-01-05 19:12:08 +01:00
Alejandro Celaya
5c0abb3d96 Created TagsParams class 2022-01-05 18:19:29 +01:00
Alejandro Celaya
3dc46bc5a3 Updated to latest shlink-common and shlink-config 2022-01-05 17:46:38 +01:00
Alejandro Celaya
e2871fc048 Merge pull request #1301 from acelaya-forks/feature/desc-default-order
Changed default ordering of short URLs, returning newest first
2022-01-05 15:15:44 +01:00
Alejandro Celaya
44e3f9b49f Changed default ordering of short URLs, returning newest first 2022-01-05 14:10:24 +01:00
Alejandro Celaya
d3f4263639 Merge pull request #1298 from acelaya-forks/feature/filter-all-tags
Feature/filter all tags
2022-01-04 14:59:22 +01:00
Alejandro Celaya
9dec05f62d Added API test covering invalid tagsMode 2022-01-04 14:42:31 +01:00
Alejandro Celaya
0447aa07fa Added more API tests covering the new tagsMode param on short URLs list 2022-01-04 14:34:31 +01:00
Alejandro Celaya
0e25af790d Updated changelog 2022-01-04 14:28:00 +01:00
Alejandro Celaya
d8484e777f Added logic to actually filter short URLs by any tag or all tags 2022-01-04 14:23:21 +01:00
Alejandro Celaya
665a3dbcbf Documented tagsMode param for short URLs list 2022-01-04 12:22:36 +01:00
Alejandro Celaya
103af2e2c1 Added support for a new tagsMode param when listing short URLs 2022-01-04 12:11:47 +01:00
Alejandro Celaya
d0daeb0078 Merge pull request #1297 from acelaya-forks/feature/docker-image-size
Feature/docker image size
2022-01-03 19:49:12 +01:00
Alejandro Celaya
a9aa49c2e5 Updated changelog 2022-01-03 19:37:05 +01:00
Alejandro Celaya
aad24389a7 Slightly reduced docker image size by merging mssql and openswoole installation steps 2022-01-03 19:34:36 +01:00
Alejandro Celaya
4b4f6f3201 Removed gmp extension as bcmath does the same 2022-01-03 19:10:58 +01:00
Alejandro Celaya
81f82d3b73 Reduced docker image size by ensuring dev native libs are not included in final image 2022-01-03 18:48:08 +01:00
Alejandro Celaya
4103ccf791 Merge pull request #1292 from acelaya-forks/feature/simplify-matches
Simplified some match expressions
2022-01-01 21:15:45 +01:00
Alejandro Celaya
e2ed11f960 Updated installer 2022-01-01 18:43:41 +01:00
Alejandro Celaya
8e1cd67a3d Simplified some match expressions 2022-01-01 18:40:48 +01:00
Alejandro Celaya
18b4caa55e Fixed merge conflicts 2021-12-21 14:48:06 +01:00
Alejandro Celaya
5e781a9010 Merge pull request #1284 from acelaya-forks/feature/visits-threshold-change
Feature/visits threshold change
2021-12-19 09:41:34 +01:00
Alejandro Celaya
277d817429 Removed API test which is no longer relevant 2021-12-18 18:41:11 +01:00
Alejandro Celaya
970f202757 Updated changelog 2021-12-18 18:26:27 +01:00
Alejandro Celaya
2c6b2b47a4 Updated installer 2021-12-18 18:23:27 +01:00
Alejandro Celaya
5c8be4b21f Updated logic to handle visits threshold env var so that it is disabled if not provided 2021-12-18 18:18:30 +01:00
Alejandro Celaya
558a4a2b30 Merge pull request #1281 from acelaya-forks/feature/update-deps
Updated dependencies
2021-12-16 22:08:17 +01:00
Alejandro Celaya
203ad7d594 Updated dependencies 2021-12-16 21:46:52 +01:00
Alejandro Celaya
04cf1aed9c Merge pull request #1279 from acelaya-forks/feature/remove-deprecated-stuff
Feature/remove deprecated stuff
2021-12-14 23:02:38 +01:00
Alejandro Celaya
8c14526f85 Fixed tests and updated changelog 2021-12-14 22:30:09 +01:00
Alejandro Celaya
1ff241411b Removed everything that was deprecated 2021-12-14 22:21:53 +01:00
274 changed files with 4676 additions and 2815 deletions

View File

@@ -9,6 +9,7 @@ data/GeoLite2-City*
data/database.sqlite
data/shlink-tests.db
CHANGELOG.md
CONTRIBUTING.md
UPGRADE.md
composer.lock
vendor

View File

@@ -18,7 +18,7 @@ With that said, please fill in the information requested next. More information
* Shlink Version: x.y.z
* PHP Version: x.y.z
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted swoole|Docker image
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted openswoole|Docker image
* Database engine used: MySQL|MariaDB|PostgreSQL|MicrosoftSQL|SQLite (x.y.z)
#### Summary

View File

@@ -18,7 +18,7 @@ With that said, please fill in the information requested next. More information
* Shlink Version: x.y.z
* PHP Version: x.y.z
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted swoole|Docker image
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted openswoole|Docker image
* Database engine used: MySQL|MariaDB|PostgreSQL|MicrosoftSQL|SQLite (x.y.z)
#### Summary

View File

@@ -6,6 +6,7 @@ on:
branches:
- main
- develop
- 2.x
jobs:
static-analysis:
@@ -22,7 +23,7 @@ jobs:
with:
php-version: ${{ matrix.php-version }}
tools: composer
extensions: openswoole-4.8.1
extensions: openswoole-4.11.0
coverage: none
- run: composer install --no-interaction --prefer-dist
- run: composer ${{ matrix.command }}
@@ -44,7 +45,7 @@ jobs:
with:
php-version: ${{ matrix.php-version }}
tools: composer
extensions: openswoole-4.8.1
extensions: openswoole-4.11.0
coverage: pcov
ini-values: pcov.directory=module
- run: composer install --no-interaction --prefer-dist
@@ -79,7 +80,7 @@ jobs:
with:
php-version: ${{ matrix.php-version }}
tools: composer
extensions: openswoole-4.8.1, pdo_sqlsrv-5.10.0beta2
extensions: openswoole-4.11.0, pdo_sqlsrv-5.10.0
coverage: pcov
ini-values: pcov.directory=module
- run: composer install --no-interaction --prefer-dist
@@ -114,7 +115,7 @@ jobs:
with:
php-version: ${{ matrix.php-version }}
tools: composer
extensions: openswoole-4.8.1
extensions: openswoole-4.11.0
coverage: pcov
ini-values: pcov.directory=module
- run: composer install --no-interaction --prefer-dist

View File

@@ -20,7 +20,7 @@ jobs:
with:
php-version: ${{ matrix.php-version }}
tools: composer
extensions: openswoole-4.8.1
extensions: openswoole-4.11.0
- if: ${{ matrix.swoole == 'yes' }}
run: ./build.sh ${GITHUB_REF#refs/tags/v}
- if: ${{ matrix.swoole == 'no' }}

View File

@@ -23,12 +23,12 @@ jobs:
with:
php-version: ${{ matrix.php-version }}
tools: composer
extensions: openswoole-4.8.1
extensions: openswoole-4.11.0
coverage: none
- run: composer install --no-interaction --prefer-dist
- run: composer swagger:inline
- run: mkdir ${{ steps.determine_version.outputs.version }}
- run: mv docs/swagger/swagger-inlined.json ${{ steps.determine_version.outputs.version }}/oas.json
- run: mv docs/swagger/swagger-inlined.json ${{ steps.determine_version.outputs.version }}/open-api-spec.json
- name: Publish spec
uses: JamesIves/github-pages-deploy-action@4.1.7
with:

View File

@@ -4,6 +4,156 @@ 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.1.0] - 2022-04-23
### Added
* [#1294](https://github.com/shlinkio/shlink/issues/1294) Allowed to provide a specific domain when importing URLs from YOURLS.
* [#1416](https://github.com/shlinkio/shlink/issues/1416) Added support to import URLs from Kutt.it.
* [#1418](https://github.com/shlinkio/shlink/issues/1418) Added support to customize the timezone used by Shlink, falling back to the default one set in PHP config.
The timezone can be set via the `TIMEZONE` env var, or using the installer tool.
* [#1309](https://github.com/shlinkio/shlink/issues/1309) Improved URL importing, ensuring individual errors do not make the whole process fail, and instead, failing URLs are skipped.
* [#1162](https://github.com/shlinkio/shlink/issues/1162) Added new endpoint to get visits by domain.
The endpoint is `GET /domains/{domain}/visits`, and it has the same capabilities as any other visits endpoint, allowing pagination and filtering.
### Changed
* [#1359](https://github.com/shlinkio/shlink/issues/1359) Hidden database commands.
* [#1385](https://github.com/shlinkio/shlink/issues/1385) Prevented a big error message from being logged when using Shlink without mercure.
* [#1398](https://github.com/shlinkio/shlink/issues/1398) Increased required mutation score for unit tests to 85%.
* [#1419](https://github.com/shlinkio/shlink/issues/1419) Input dates are now parsed to Shlink's configured timezone or default timezone before using them for database queries.
* [#1428](https://github.com/shlinkio/shlink/issues/1428) Updated native dependencies in docker image and base image to PHP v8.1.5.
### Deprecated
* [#1340](https://github.com/shlinkio/shlink/issues/1340) Deprecated webhooks. New events will only be added to other real-time updates approaches, and webhooks will be completely removed in Shlink 4.0.0.
### Removed
* *Nothing*
### Fixed
* [#1397](https://github.com/shlinkio/shlink/issues/1397) Fixed `db:create` command always reporting the schema exists if the `db:migrate` command has been run before by mistake.
* [#1402](https://github.com/shlinkio/shlink/issues/1402) Fixed the base path getting appended with the default domain by mistake, causing multiple side effects in several places.
## [3.0.3] - 2022-02-19
### Added
* *Nothing*
### Changed
* [#1382](https://github.com/shlinkio/shlink/issues/1382) Updated docker image to PHP 8.1.3.
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* [#1377](https://github.com/shlinkio/shlink/issues/1377) Fixed installer always setting delete threshold with value 1.
* [#1379](https://github.com/shlinkio/shlink/issues/1379) Ensured API keys cannot be created with a domain-only role linked to default domain.
## [3.0.2] - 2022-02-10
### Added
* *Nothing*
### Changed
* *Nothing*
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* [#1373](https://github.com/shlinkio/shlink/issues/1373) Fixed incorrect config import when updating from Shlink 2.x using SQLite.
* [#1369](https://github.com/shlinkio/shlink/issues/1369) Fixed slow regexps in `.htaccess` file.
## [3.0.1] - 2022-02-04
### Added
* *Nothing*
### Changed
* *Nothing*
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* [#1363](https://github.com/shlinkio/shlink/issues/1363) Fixed titles being resolved no matter what when `validateUrl` is not set or is explicitly set to true.
* [#1352](https://github.com/shlinkio/shlink/issues/1352) Updated to stable pdo_sqlsrv in docker image.
## [3.0.0] - 2022-01-28
### Added
* [#767](https://github.com/shlinkio/shlink/issues/767) Added full support to use emojis everywhere, whether it is custom slugs, titles, referrers, etc.
* [#1274](https://github.com/shlinkio/shlink/issues/1274) Added support to filter short URLs lists by all provided tags.
The `GET /short-urls` endpoint now accepts a `tagsMode=all` param which will make only short URLs matching **all** the tags in the `tags[]` query param, to be returned.
The `short-urls:list` command now accepts a `-i`/`--including-all-tags` flag which behaves the same.
* [#1273](https://github.com/shlinkio/shlink/issues/1273) Added support for pagination in tags lists, allowing to improve performance by loading subsets of tags.
For backwards compatibility, lists continue returning all items by default, but the `GET /tags` endpoint now supports `page` and `itemsPerPage` query params, to make sure only a subset of the tags is returned.
This is supported both when invoking the endpoint with and without `withStats=true` query param.
Additionally, the endpoint also supports filtering by `searchTerm` query param. When provided, only tags matching it will be returned.
* [#1063](https://github.com/shlinkio/shlink/issues/1063) Added new endpoint that allows fetching all existing non-orphan visits, in case you need a global view of all visits received by your Shlink instance.
This can be achieved using the `GET /visits/non-orphan` endpoint.
### Changed
* [#1277](https://github.com/shlinkio/shlink/issues/1277) Reduced docker image size to 45% of the original size.
* [#1268](https://github.com/shlinkio/shlink/issues/1268) Updated dependencies, including symfony/console 6 and mezzio/mezzio-swoole 4.
* [#1283](https://github.com/shlinkio/shlink/issues/1283) Changed behavior of `DELETE_SHORT_URL_THRESHOLD` env var, disabling the feature if a value was not provided.
* [#1300](https://github.com/shlinkio/shlink/issues/1300) Changed default ordering for short URLs list, returning always from newest to oldest.
* [#1299](https://github.com/shlinkio/shlink/issues/1299) Updated to the latest base docker images, based in PHP 8.1.1, and bumped openswoole to v4.9.1.
* [#1282](https://github.com/shlinkio/shlink/issues/1282) Env vars now have precedence over installer options.
* [#1328](https://github.com/shlinkio/shlink/issues/1328) Refactored ShortUrlsRepository to use DTOs in methods with too many arguments.
### Deprecated
* [#1315](https://github.com/shlinkio/shlink/issues/1315) Deprecated `GET /tags?withStats=true` endpoint. Use `GET /tags/stats` instead.
### Removed
* [#1275](https://github.com/shlinkio/shlink/issues/1275) Removed everything that was deprecated in Shlink 2.x.
See [UPGRADE](UPGRADE.md#from-v2x-to-v3x) doc in order to get details on how to migrate to this version.
* [#1347](https://github.com/shlinkio/shlink/issues/1347) Dropped support for regular swoole in favor of openswoole.
Since openswoole support was introduced in the previous release version, Shlink will still consider the swoole extension as openswoole, as at the moment, functionality hasn't deviated too much, and will simplify migrating to Shlink 3.0.0
However, there's no longer active testing with regular swoole, and it is considered no longer supported. If some incompatibility arises, the only supported solution will be to migrate to openswoole.
### Fixed
* *Nothing*
## [2.10.3] - 2022-01-23
### Added
* *Nothing*
### Changed
* *Nothing*
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* [#1349](https://github.com/shlinkio/shlink/issues/1349) Fixed memory leak in cache implementation.
## [2.10.2] - 2022-01-07
### Added
* *Nothing*
@@ -889,7 +1039,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
* Preview generation feature completely removed.
* Authentication against REST API using JWT is no longer supported.
See [UPGRADE](UPGRADE.md) doc in order to get details on how to migrate to this version.
See [UPGRADE](UPGRADE.md#from-v1x-to-v2x) doc in order to get details on how to migrate to this version.
### Fixed
* [#600](https://github.com/shlinkio/shlink/issues/600) Fixed health action so that it works with and without version in the path.

View File

@@ -31,7 +31,7 @@ Then you will have to follow these steps:
* Run `./indocker bin/cli db:migrate` to get database migrations up to date.
* Run `./indocker bin/cli api-key:generate` to get your first API key generated.
Once you finish this, you will have the project exposed in ports `8000` through nginx+php-fpm and `8080` through swoole.
Once you finish this, you will have the project exposed in ports `8000` through nginx+php-fpm and `8080` through openswoole.
> Note: The `indocker` shell script is a helper tool used to run commands inside the main docker container.
@@ -46,9 +46,7 @@ This is a simplified version of the project structure:
```
shlink
├── bin
── cli
│ ├── install
│ └── update
── cli
├── config
│ ├── autoload
│ ├── params
@@ -75,12 +73,12 @@ shlink
The purposes of every folder are:
* `bin`: It contains the CLI tools. The `cli` one is the main entry point to run shlink from the command line, while `install` and `update` are helper tools used to install and update shlink when not using the docker image.
* `bin`: It contains the CLI tools. The `cli` one is the main entry point to run shlink from the command line.
* `config`: Contains application-wide configurations, which are later merged with the ones provided by every module.
* `data`: Common runtime-generated git-ignored assets, like logs, caches, etc.
* `docs`: Any project documentation is stored here, like API spec definitions or architectural decision records.
* `module`: Contains a subfolder for every module in the project. Modules contain the source code, tests and configurations for every context in the project.
* `public`: Few assets (like `favicon.ico` or `robots.txt`) and the web entry point are stored here. This web entry point is not used when serving the app with swoole.
* `module`: Contains a sub-folder for every module in the project. Modules contain the source code, tests and configurations for every context in the project.
* `public`: Few assets (like `favicon.ico` or `robots.txt`) and the web entry point are stored here. This web entry point is not used when serving the app with openswoole.
## Project tests
@@ -96,7 +94,7 @@ In order to ensure stability and no regressions are introduced while developing
The project provides some tooling to run them against any of the supported database engines.
* **API tests**: These are E2E tests that spin up an instance of the app with swoole, and test it from the outside by interacting with the REST API.
* **API tests**: These are E2E tests that spin up an instance of the app with openswoole, and test it from the outside by interacting with the REST API.
These are the best tests to catch regressions, and to verify everything behaves as expected.
@@ -125,12 +123,6 @@ Depending on the kind of contribution, maybe not all kinds of tests are needed,
* 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.
> Note: Due to some limitations in the tooling used by shlink, the testing databases need to exist beforehand, both for db and api tests (except sqlite).
>
> However, they just need to be created empty, with no tables. Also, once created, they are automatically reset before every new execution.
>
> The testing database is always called `shlink_test`. You can create it using the database client of your choice. [DBeaver](https://dbeaver.io/) is a good multi-platform desktop database client which supports all the engines supported by shlink.
## Pull request process
**Important!**: Before starting to work on a pull request, make sure you always [open an issue](https://github.com/shlinkio/shlink/issues/new/choose) first.

View File

@@ -1,9 +1,9 @@
FROM php:8.1.0-alpine3.15 as base
FROM php:8.1.5-alpine3.15 as base
ARG SHLINK_VERSION=latest
ENV SHLINK_VERSION ${SHLINK_VERSION}
ENV OPENSWOOLE_VERSION 4.8.1
ENV PDO_SQLSRV_VERSION 5.10.0beta2
ENV OPENSWOOLE_VERSION 4.11.0
ENV PDO_SQLSRV_VERSION 5.10.0
ENV MS_ODBC_SQL_VERSION 17.5.2.2
ENV LC_ALL "C"
@@ -11,42 +11,28 @@ WORKDIR /etc/shlink
# Install required PHP extensions
RUN \
# Install extensions with no extra dependencies
docker-php-ext-install -j"$(nproc)" pdo_mysql calendar sockets bcmath && \
# Install sqlite
apk add --no-cache sqlite-libs sqlite-dev && \
# Temp install dev dependencies needed to compile the extensions
apk add --no-cache --virtual .dev-deps sqlite-dev postgresql-dev icu-dev libzip-dev zlib-dev libpng-dev && \
docker-php-ext-install -j"$(nproc)" pdo_mysql pdo_pgsql intl calendar sockets bcmath zip gd && \
apk add --no-cache sqlite-libs && \
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
# Install postgres
apk add --no-cache postgresql-dev && \
docker-php-ext-install -j"$(nproc)" pdo_pgsql && \
# Install intl
apk add --no-cache icu-dev && \
docker-php-ext-install -j"$(nproc)" intl && \
# Install zip and gd
apk add --no-cache libzip-dev zlib-dev libpng-dev && \
docker-php-ext-install -j"$(nproc)" zip gd && \
# Install gmp
apk add --no-cache gmp-dev && \
docker-php-ext-install -j"$(nproc)" gmp
# Remove temp dev extensions, and install prod equivalents that are required at runtime
apk del .dev-deps && \
apk add --no-cache postgresql icu libzip libpng
# Install sqlsrv driver
RUN 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 --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} && \
docker-php-ext-enable pdo_sqlsrv && \
apk del .phpize-deps && \
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk ; \
fi
# Install openswoole
RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} && \
# 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 [ $(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 && \
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} && \
docker-php-ext-enable pdo_sqlsrv && \
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk ; \
fi; \
apk del .phpize-deps
# Install shlink
FROM base as builder
COPY . .

View File

@@ -6,9 +6,10 @@
[![Latest Stable Version](https://img.shields.io/github/release/shlinkio/shlink.svg?style=flat-square)](https://packagist.org/packages/shlinkio/shlink)
[![Docker pulls](https://img.shields.io/docker/pulls/shlinkio/shlink.svg?logo=docker&style=flat-square)](https://hub.docker.com/r/shlinkio/shlink/)
[![License](https://img.shields.io/github/license/shlinkio/shlink.svg?style=flat-square)](https://github.com/shlinkio/shlink/blob/main/LICENSE)
[![Twitter](https://img.shields.io/twitter/follow/shlinkio?color=blue&label=follow&logo=twitter&style=flat-square)](https://twitter.com/shlinkio)
[![Paypal donate](https://img.shields.io/badge/Donate-paypal-blue.svg?style=flat-square&logo=paypal&colorA=aaaaaa)](https://slnk.to/donate)
A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own custom domain.
A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own domain.
## Table of Contents
@@ -26,7 +27,7 @@ This document contains the very basics to get started with Shlink. If you want t
## Docker image
Starting with version 1.15.0, an official docker image is provided. You can learn how to use it by reading [the docs](https://shlink.io/documentation/install-docker-image/).
You can learn how to use the official docker image by reading [the docs](https://shlink.io/documentation/install-docker-image/).
The idea is that you can just generate a container using the image and provide the custom config via env vars.
@@ -35,12 +36,13 @@ 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
* The next PHP extensions: json, curl, pdo, intl, gd and gmp.
* apcu extension is recommended if you don't plan to use swoole or openswoole.
* 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.
* sockets and bcmath extensions are required if you want to integrate with a RabbitMQ instance.
* MySQL, MariaDB, PostgreSQL, Microsoft SQL Server or SQLite.
* The web server of your choice with PHP integration (Apache or Nginx recommended).
* MySQL, MariaDB, PostgreSQL, MicrosoftSQL or SQLite.
* You will also need the corresponding pdo variation for the database you are planning to use: `pdo_mysql`, `pdo_pgsql`, `pdo_sqlsrv` or `pdo_sqlite`.
* The [openswoole](https://openswoole.com/) PHP extension (if you plan to serve Shlink with openswoole) or the web server of your choice with PHP integration (like Apache or Nginx).
### Download
@@ -50,7 +52,7 @@ In order to run Shlink, you will need a built version of the project. There are
The easiest way to install shlink is by using one of the pre-bundled distributable packages.
Go to the [latest version](https://github.com/shlinkio/shlink/releases/latest) and download the `shlink*_dist.zip` file that suits your needs. You will find one for every supported PHP version and with/without swoole/openswoole integration.
Go to the [latest version](https://github.com/shlinkio/shlink/releases/latest) and download the `shlink*_dist.zip` file that suits your needs. You will find one for every supported PHP version and with/without openswoole integration.
Finally, decompress the file in the location of your choice.
@@ -60,7 +62,7 @@ In order to run Shlink, you will need a built version of the project. There are
* Clone the project with git (`git clone https://github.com/shlinkio/shlink.git`), or download it by clicking the **Clone or download** green button.
* Download the [Composer](https://getcomposer.org/download/) PHP package manager inside the project folder.
* Run `./build.sh 1.0.0`, replacing the version with the version number you are going to build (the version number is used as part of the generated dist file name, and to set the value returned when running `shlink -V` from the command line).
* Run `./build.sh 3.0.0`, replacing the version with the version number you are going to build (the version number is used as part of the generated dist file name, and to set the value returned when running `shlink -V` from the command line).
After that, you will have a dist file inside the `build` directory, that you need to decompress in the location of your choice.
@@ -72,24 +74,24 @@ Despite how you built the project, you now need to configure it, by following th
* If you are going to use MySQL, MariaDB, PostgreSQL or Microsoft SQL Server, create an empty database with the name of your choice.
* Recursively grant write permissions to the `data` directory. Shlink uses it to cache some information.
* Setup the application by running the `bin/install` script. It is a command line tool that will guide you through the installation process. **Take into account that this tool has to be run directly on the server where you plan to host Shlink. Do not run it before uploading/moving it there.**
* Generate your first API key by running `bin/cli api-key:generate`. You will need the key in order to interact with shlink's API.
* Set up the application by running the `vendor/bin/shlink-installer install` script. It is a command line tool that will guide you through the installation process. **Take into account that this tool has to be run directly on the server where you plan to host Shlink. Do not run it before uploading/moving it there.**
* Generate your first API key by running `bin/cli api-key:generate`. You will need the key in order to interact with Shlink's API.
## Using shlink
Once shlink is installed, there are two main ways to interact with it:
* **The command line**. Try running `bin/cli` and see all the [available commands](#shlink-cli-help).
* **The command line**: Try running `bin/cli` to see all the available commands.
All of those commands can be run with the `--help`/`-h` flag in order to see how to use them and all the available options.
All of them can be run with the `--help`/`-h` flag in order to see how to use them and all the available options.
It is probably a good idea to symlink the CLI entry point (`bin/cli`) to somewhere in your path, so that you can run shlink from any directory.
* **The REST API**. The complete docs on how to use the API can be found [here](https://shlink.io/documentation/api-docs), and a sandbox which also documents every endpoint can be found in the [API Spec](https://api-spec.shlink.io/) portal.
* **The REST API**: The complete docs on how to use the API can be found [here](https://shlink.io/documentation/api-docs), and a sandbox which also documents every endpoint can be found in the [API Spec](https://api-spec.shlink.io/) portal.
However, you probably don't want to consume the raw API yourself. That's why a nice [web client](https://github.com/shlinkio/shlink-web-client) is provided that can be directly used from [https://app.shlink.io](https://app.shlink.io), or hosted by yourself.
Both the API and CLI allow you to do the same operations, except for API key management, which can be done from the command line interface only.
Both the API and CLI allow you to do mostly the same operations, except for API key management, which can be done from the command line interface only.
## Contributing

View File

@@ -1,5 +1,53 @@
# Upgrading
## From v2.x to v3.x
### Changes in REST API
* The `type` property returned when trying to delete a URL that reached the visits threshold, when using the `DELETE /short-urls/{shortCode}` endpoint, is now `INVALID_SHORT_URL_DELETION` instead of `INVALID_SHORTCODE_DELETION`.
* The `INVALID_AUTHORIZATION` error no longer includes the `expectedTypes` property. Use `expectedHeaders` one instead.
* The `GET /rest/v2/short-urls` endpoint no longer allows ordering by `visitsCount`, `visitCount` or `originalUrl`. Use `visits` instead of the first two, and `longUrl` instead of the last one.
* The `GET /rest/v2/short-urls` endpoint no longer allows providing the ordering params with array notation, as in `/shortUrls?orderBy[longUrl]=DESC`. Instead, use the following notation `/shortUrls?orderBy=longUrl-DESC`.
* The `GET /rest/v2/short-urls` endpoint now has a default ordering of newest-to-oldest. Use `/shortUrls?orderBy=dateCreated-ASC` in order to keep the oldest-to-newest behavior.
* Requests expecting a body no longer support url-encoded payloads. Instead, always use JSON bodies with `Content-Type: application/json`.
* The next endpoints have been removed:
* `PUT /rest/v2/short-urls/{shortCode}/tags`: Use the `PATCH /rest/v2/short-urls/{shortCode}` endpoint to set the short URL tags.
* `POST /rest/v2/tags`: Use `POST /rest/v2/short-urls` or `PATCH /rest/v2/short-urls/{shortCodes}` to create new tags already attached to a short URL. Creating orphan tags makes no sense.
### Changes in CLI
* The next commands have been removed:
* `short-url:generate`: Use `short-url:create` instead.
* `tag:create`: Creating orphan tags makes no sense.
* Params in camelCase format are no longer supported. They all have an equivalent kebab-case replacement. (for example, from `--startDate` to `--start-date`).
* The `short-url:create` command no longer accepts the `--no-validate-url` flag. Now URLs are never validated, unless `--validate-url` is passed.
* The CLI installer tool entry-points have changed.
* `bin/install`: replaced by `vendor/bin/shlink-installer install`
* `bin/update`: replaced by `vendor/bin/shlink-installer update`
* `bin/set-option`: replaced by `vendor/bin/shlink-installer set-option`
### Changes in config
* The next env vars have been removed:
* `INVALID_SHORT_URL_REDIRECT_TO`: Replaced by `DEFAULT_INVALID_SHORT_URL_REDIRECT`.
* `REGULAR_404_REDIRECT_TO`: Replaced by `DEFAULT_REGULAR_404_REDIRECT`.
* `BASE_URL_REDIRECT_TO`: Replaced by `DEFAULT_BASE_URL_REDIRECT`.
* `SHORT_DOMAIN_HOST`: Replaced by `DEFAULT_DOMAIN`.
* `SHORT_DOMAIN_SCHEMA`: Replaced by `IS_HTTPS_ENABLED`.
* `USE_HTTPS`: Replaced by `IS_HTTPS_ENABLED`.
* `VALIDATE_URLS`: There's no replacement. URLs are not validated, unless explicitly requested during creation or edition.
* The next env vars behavior has changed:
* `DELETE_SHORT_URL_THRESHOLD`: Now, if this env var is not provided, the "visits threshold" won't be checked at all when deleting short URLs. Make sure you explicitly provide a value if you want to enable this feature.
* Environment variables now have precedence over configuration set via the installer tool.
### Other changes
* A default GeoLite2 license key is no longer provided. If you don't provide your own as explained in [the docs](https://shlink.io/documentation/geolite-license-key/), Shlink will not try to update the file anymore.
* The docker image no longer accepts providing configuration via json files mounted in the `config/params` folder. Only env vars are supported now.
* If you were manually serving Shlink with swoole, the entry script has to be changed from `/path/to/shlink/vendor/bin/mezzio-swoole start` to `/path/to/shlink/vendor/bin/laminas mezzio:swoole:start`
* The `GET /{shortCode}/qr-code/{size}` url has been removed. Use `GET /{shortCode}/qr-code?size={size}` instead.
* Regular swoole extension is no longer supported. Use openswoole instead, as a direct replacement. In most of the cases you just need to uninstall one and install the other, the rest is transparent.
## From v1.x to v2.x
### PHP 7.4 required

View File

@@ -1,51 +0,0 @@
#!/usr/bin/env php
<?php
/**
* @deprecated To be removed with Shlink 3.0.0
* This script is provided to keep backwards compatibility on how to run shlink with swoole while being still able to
* update to mezzio/mezzio-swoole 3.x
*/
declare(strict_types=1);
namespace Mezzio\Swoole\Command;
use Laminas\ServiceManager\ServiceManager;
use PackageVersions\Versions;
use Symfony\Component\Console\Application as CommandLine;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use function explode;
use function Functional\filter;
use function str_starts_with;
use function strstr;
/** @var ServiceManager $container */
$container = require __DIR__ . '/../../config/container.php';
$version = strstr(Versions::getVersion('mezzio/mezzio-swoole'), '@', true);
$commandsPrefix = 'mezzio:swoole:';
$commands = filter(
$container->get('config')['laminas-cli']['commands'] ?? [],
fn ($c, string $command) => str_starts_with($command, $commandsPrefix),
);
$registeredCommands = [];
foreach ($commands as $newName => $commandServiceName) {
[, $oldName] = explode($commandsPrefix, $newName);
$registeredCommands[$oldName] = $commandServiceName;
$container->addDelegator($commandServiceName, static function ($c, $n, callable $factory) use ($oldName) {
/** @var Command $command */
$command = $factory();
$command->setAliases([$oldName]);
return $command;
});
}
$commandLine = new CommandLine('Mezzio web server', $version);
$commandLine->setAutoExit(true);
$commandLine->setCommandLoader(new ContainerCommandLoader($container, $registeredCommands));
$commandLine->run();

View File

@@ -1,12 +0,0 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink;
use function chdir;
use function dirname;
chdir(dirname(__DIR__));
[$install] = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
$install();

View File

@@ -1,14 +0,0 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink;
use Shlinkio\Shlink\Installer\Command\SetOptionCommand;
use function chdir;
use function dirname;
chdir(dirname(__DIR__));
[,, $installer] = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
$installer(SetOptionCommand::NAME);

View File

@@ -4,7 +4,10 @@ export DB_DRIVER=postgres
export TEST_ENV=api
export GENERATE_COVERAGE=${GENERATE_COVERAGE:-"no"}
# Reset logs
rm -rf data/log/api-tests
mkdir data/log/api-tests
touch data/log/api-tests/output.log
# Try to stop server just in case it hanged in last execution
vendor/bin/laminas mezzio:swoole:stop

View File

@@ -1,12 +0,0 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink;
use function chdir;
use function dirname;
chdir(dirname(__DIR__));
[, $update] = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
$update();

View File

@@ -10,7 +10,7 @@ fi
version=$1
noSwoole=$2
phpVersion=$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')
[[ $noSwoole ]] && swooleSuffix="" || swooleSuffix="_swoole"
[[ $noSwoole ]] && swooleSuffix="" || swooleSuffix="_openswoole"
distId="shlink${version}_php${phpVersion}${swooleSuffix}_dist"
builtContent="./build/${distId}"
projectdir=$(pwd)
@@ -34,11 +34,8 @@ ${composerBin} self-update
${composerBin} install --no-dev --prefer-dist $composerFlags
if [[ $noSwoole ]]; then
# If generating a dist not for swoole, uninstall mezzio-swoole
# If generating a dist not for openswoole, uninstall mezzio-swoole
${composerBin} remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev $composerFlags
else
# Copy mezzio helper script to vendor (deprecated - Remove with Shlink 3.0.0)
cp "${projectdir}/bin/helper/mezzio-swoole" "./vendor/bin"
fi
# Delete development files

View File

@@ -17,9 +17,8 @@
"ext-pdo": "*",
"akrabat/ip-address-middleware": "^2.1",
"cakephp/chronos": "^2.3",
"cocur/slugify": "^4.0",
"doctrine/migrations": "^3.3",
"doctrine/orm": "^2.10",
"doctrine/orm": "^2.11",
"endroid/qr-code": "^4.4",
"geoip2/geoip2": "^2.12",
"guzzlehttp/guzzle": "^7.4",
@@ -29,7 +28,7 @@
"laminas/laminas-config-aggregator": "^1.7",
"laminas/laminas-diactoros": "^2.8",
"laminas/laminas-inputfilter": "^2.13",
"laminas/laminas-servicemanager": "^3.10",
"laminas/laminas-servicemanager": "^3.11.2",
"laminas/laminas-stdlib": "^3.6",
"lcobucci/jwt": "^4.1",
"league/uri": "^6.4",
@@ -37,7 +36,7 @@
"mezzio/mezzio": "^3.7",
"mezzio/mezzio-fastroute": "^3.3",
"mezzio/mezzio-problem-details": "^1.5",
"mezzio/mezzio-swoole": "^3.5",
"mezzio/mezzio-swoole": "^4.0",
"mlocati/ip-lib": "^1.17",
"monolog/monolog": "^2.3",
"nikolaposa/monolog-factory": "^3.1",
@@ -48,25 +47,25 @@
"predis/predis": "^1.1",
"pugx/shortid-php": "^1.0",
"ramsey/uuid": "^4.2",
"shlinkio/shlink-common": "^4.3",
"shlinkio/shlink-config": "^1.4",
"shlinkio/shlink-common": "^4.4",
"shlinkio/shlink-config": "^1.6",
"shlinkio/shlink-event-dispatcher": "^2.3",
"shlinkio/shlink-importer": "^2.5",
"shlinkio/shlink-installer": "^6.3",
"shlinkio/shlink-importer": "dev-main#af0e05e as 3.0",
"shlinkio/shlink-installer": "dev-develop#fbbc8f5 as 7.1",
"shlinkio/shlink-ip-geolocation": "^2.2",
"symfony/console": "^5.4",
"symfony/filesystem": "^6.0 || ^5.4",
"symfony/lock": "^6.0 || ^5.4",
"symfony/console": "^6.0",
"symfony/filesystem": "^6.0",
"symfony/lock": "^6.0",
"symfony/mercure": "^0.6",
"symfony/process": "^6.0 || ^5.4",
"symfony/string": "^6.0 || ^5.4"
"symfony/process": "^6.0",
"symfony/string": "^6.0"
},
"require-dev": {
"cebe/php-openapi": "^1.5",
"cebe/php-openapi": "^1.7",
"devster/ubench": "^2.1",
"dms/phpunit-arraysubset-asserts": "^0.3.0",
"eaglewu/swoole-ide-helper": "dev-master",
"infection/infection": "^0.25.4",
"infection/infection": "^0.26.5",
"openswoole/ide-helper": "~4.11.0",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/phpstan": "^1.2",
"phpstan/phpstan-doctrine": "^1.0",
@@ -75,7 +74,7 @@
"phpunit/phpunit": "^9.5",
"roave/security-advisories": "dev-master",
"shlinkio/php-coding-standard": "~2.2.0",
"shlinkio/shlink-test-utils": "^2.5",
"shlinkio/shlink-test-utils": "^3.0.1",
"symfony/var-dumper": "^6.0",
"veewee/composer-run-parallel": "^1.1"
},
@@ -95,10 +94,8 @@
"ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test",
"ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test",
"ShlinkioApiTest\\Shlink\\Rest\\": "module/Rest/test-api",
"ShlinkioTest\\Shlink\\Core\\": [
"module/Core/test",
"module/Core/test-db"
]
"ShlinkioTest\\Shlink\\Core\\": "module/Core/test",
"ShlinkioDbTest\\Shlink\\Core\\": "module/Core/test-db"
},
"files": [
"config/test/constants.php"
@@ -131,7 +128,7 @@
],
"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:pretty": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-html build/coverage-unit/coverage-html",
"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",
"test:db:sqlite:ci": "@test:db:sqlite --coverage-php build/coverage-db.cov --coverage-xml=build/coverage-db/coverage-xml --log-junit=build/coverage-db/junit.xml",
@@ -142,7 +139,7 @@
"test:api": "bin/test/run-api-tests.sh",
"test:api:ci": "GENERATE_COVERAGE=yes composer test:api",
"infect:ci:base": "infection --threads=4 --log-verbosity=default --only-covered --only-covering-test-cases --skip-initial-tests",
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=80",
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=85",
"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",

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
return [
'app_options' => [
'version' => 'latest',
],
];

View File

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

View File

@@ -4,15 +4,17 @@ declare(strict_types=1);
namespace Shlinkio\Shlink;
use function Shlinkio\Shlink\Common\env;
use Shlinkio\Shlink\Core\Config\EnvVars;
use const Shlinkio\Shlink\DEFAULT_DELETE_SHORT_URL_THRESHOLD;
return (static function (): array {
$threshold = EnvVars::DELETE_SHORT_URL_THRESHOLD()->loadFromEnv();
return [
return [
'delete_short_urls' => [
'check_visits_threshold' => true,
'visits_threshold' => (int) env('DELETE_SHORT_URL_THRESHOLD', DEFAULT_DELETE_SHORT_URL_THRESHOLD),
],
'delete_short_urls' => [
'check_visits_threshold' => $threshold !== null,
'visits_threshold' => (int) ($threshold ?? DEFAULT_DELETE_SHORT_URL_THRESHOLD),
],
];
];
})();

View File

@@ -3,12 +3,12 @@
declare(strict_types=1);
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
use Shlinkio\Shlink\Core\Config\EnvVars;
use function Functional\contains;
use function Shlinkio\Shlink\Common\env;
return (static function (): array {
$driver = env('DB_DRIVER');
$driver = EnvVars::DB_DRIVER()->loadFromEnv();
$isMysqlCompatible = contains(['maria', 'mysql'], $driver);
$resolveDriver = static fn () => match ($driver) {
@@ -21,20 +21,27 @@ return (static function (): array {
'mssql' => '1433',
default => '3306',
};
$resolveConnection = static fn () => match (true) {
$driver === null || $driver === 'sqlite' => [
$resolveCharset = static fn () => match ($driver) {
// This does not determine charsets or collations in tables or columns, but the charset used in the data
// flowing in the connection, so it has to match what has been set in the database.
'maria', 'mysql' => 'utf8mb4',
'postgres' => 'utf8',
default => null,
};
$resolveConnection = static fn () => match ($driver) {
null, 'sqlite' => [
'driver' => 'pdo_sqlite',
'path' => 'data/database.sqlite',
],
default => [
'driver' => $resolveDriver(),
'dbname' => env('DB_NAME', 'shlink'),
'user' => env('DB_USER'),
'password' => env('DB_PASSWORD'),
'host' => env('DB_HOST', $driver === 'postgres' ? env('DB_UNIX_SOCKET') : null),
'port' => env('DB_PORT', $resolveDefaultPort()),
'unix_socket' => $isMysqlCompatible ? env('DB_UNIX_SOCKET') : null,
'charset' => 'utf8',
'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

@@ -11,6 +11,7 @@ return [
'driver' => 'pdo_mysql',
'host' => 'shlink_db_mysql',
'dbname' => 'shlink',
'charset' => 'utf8mb4',
],
],

View File

@@ -2,14 +2,14 @@
declare(strict_types=1);
use function Shlinkio\Shlink\Common\env;
use Shlinkio\Shlink\Core\Config\EnvVars;
return [
'geolite2' => [
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
'temp_dir' => __DIR__ . '/../../data',
'license_key' => env('GEOLITE_LICENSE_KEY', 'G4Lm0C60yJsnkdPi'), // Deprecated. Remove hardcoded license on v3
'license_key' => EnvVars::GEOLITE_LICENSE_KEY()->loadFromEnv(),
],
];

View File

@@ -18,22 +18,20 @@ return [
Option\Database\DatabaseUserConfigOption::class,
Option\Database\DatabasePasswordConfigOption::class,
Option\Database\DatabaseUnixSocketConfigOption::class,
Option\Database\DatabaseSqlitePathConfigOption::class,
Option\Database\DatabaseMySqlOptionsConfigOption::class,
Option\UrlShortener\ShortDomainHostConfigOption::class,
Option\UrlShortener\ShortDomainSchemaConfigOption::class,
Option\UrlShortener\ValidateUrlConfigOption::class,
Option\Visit\VisitsWebhooksConfigOption::class,
Option\Visit\OrphanVisitsWebhooksConfigOption::class,
Option\Redirect\BaseUrlRedirectConfigOption::class,
Option\Redirect\InvalidShortUrlRedirectConfigOption::class,
Option\Redirect\Regular404RedirectConfigOption::class,
Option\Visit\CheckVisitsThresholdConfigOption::class,
Option\Visit\VisitsThresholdConfigOption::class,
Option\BasePathConfigOption::class,
Option\TimezoneConfigOption::class,
Option\Worker\TaskWorkerNumConfigOption::class,
Option\Worker\WebWorkerNumConfigOption::class,
Option\RedisServersConfigOption::class,
Option\Redis\RedisServersConfigOption::class,
Option\Redis\RedisSentinelServiceConfigOption::class,
Option\UrlShortener\ShortCodeLengthOption::class,
Option\Mercure\EnableMercureConfigOption::class,
Option\Mercure\MercurePublicUrlConfigOption::class,

View File

@@ -5,10 +5,9 @@ declare(strict_types=1);
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
use Predis\ClientInterface as PredisClient;
use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
use Shlinkio\Shlink\Core\Config\EnvVars;
use Symfony\Component\Lock;
use function Shlinkio\Shlink\Common\env;
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
return [
@@ -25,7 +24,7 @@ return [
LOCAL_LOCK_FACTORY => ConfigAbstractFactory::class,
],
'aliases' => [
'lock_store' => env('REDIS_SERVERS') === null ? 'local_lock_store' : 'redis_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,

View File

@@ -4,20 +4,19 @@ declare(strict_types=1);
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
use Shlinkio\Shlink\Common\Mercure\LcobucciJwtProvider;
use Shlinkio\Shlink\Core\Config\EnvVars;
use Symfony\Component\Mercure\Hub;
use Symfony\Component\Mercure\HubInterface;
use function Shlinkio\Shlink\Common\env;
return (static function (): array {
$publicUrl = env('MERCURE_PUBLIC_HUB_URL');
$publicUrl = EnvVars::MERCURE_PUBLIC_HUB_URL()->loadFromEnv();
return [
'mercure' => [
'public_hub_url' => $publicUrl,
'internal_hub_url' => env('MERCURE_INTERNAL_HUB_URL', $publicUrl),
'jwt_secret' => env('MERCURE_JWT_SECRET'),
'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL()->loadFromEnv($publicUrl),
'jwt_secret' => EnvVars::MERCURE_JWT_SECRET()->loadFromEnv(),
'jwt_issuer' => 'Shlink',
],

View File

@@ -2,7 +2,7 @@
declare(strict_types=1);
use function Shlinkio\Shlink\Common\env;
use Shlinkio\Shlink\Core\Config\EnvVars;
use const Shlinkio\Shlink\DEFAULT_QR_CODE_ERROR_CORRECTION;
use const Shlinkio\Shlink\DEFAULT_QR_CODE_FORMAT;
@@ -13,11 +13,15 @@ use const Shlinkio\Shlink\DEFAULT_QR_CODE_SIZE;
return [
'qr_codes' => [
'size' => (int) env('DEFAULT_QR_CODE_SIZE', DEFAULT_QR_CODE_SIZE),
'margin' => (int) env('DEFAULT_QR_CODE_MARGIN', DEFAULT_QR_CODE_MARGIN),
'format' => env('DEFAULT_QR_CODE_FORMAT', DEFAULT_QR_CODE_FORMAT),
'error_correction' => env('DEFAULT_QR_CODE_ERROR_CORRECTION', DEFAULT_QR_CODE_ERROR_CORRECTION),
'round_block_size' => (bool) env('DEFAULT_QR_CODE_ROUND_BLOCK_SIZE', DEFAULT_QR_CODE_ROUND_BLOCK_SIZE),
'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(
DEFAULT_QR_CODE_ROUND_BLOCK_SIZE,
),
],
];

View File

@@ -5,18 +5,17 @@ declare(strict_types=1);
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use function Shlinkio\Shlink\Common\env;
use Shlinkio\Shlink\Core\Config\EnvVars;
return [
'rabbitmq' => [
'enabled' => (bool) env('RABBITMQ_ENABLED', false),
'host' => env('RABBITMQ_HOST'),
'port' => (int) env('RABBITMQ_PORT', '5672'),
'user' => env('RABBITMQ_USER'),
'password' => env('RABBITMQ_PASSWORD'),
'vhost' => env('RABBITMQ_VHOST', '/'),
'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' => [

View File

@@ -2,7 +2,7 @@
declare(strict_types=1);
use function Shlinkio\Shlink\Common\env;
use Shlinkio\Shlink\Core\Config\EnvVars;
use const Shlinkio\Shlink\DEFAULT_REDIRECT_CACHE_LIFETIME;
use const Shlinkio\Shlink\DEFAULT_REDIRECT_STATUS_CODE;
@@ -10,16 +10,16 @@ use const Shlinkio\Shlink\DEFAULT_REDIRECT_STATUS_CODE;
return [
'not_found_redirects' => [
// Deprecated env vars
'invalid_short_url' => env('DEFAULT_INVALID_SHORT_URL_REDIRECT', env('INVALID_SHORT_URL_REDIRECT_TO')),
'regular_404' => env('DEFAULT_REGULAR_404_REDIRECT', env('REGULAR_404_REDIRECT_TO')),
'base_url' => env('DEFAULT_BASE_URL_REDIRECT', env('BASE_URL_REDIRECT_TO')),
'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(),
],
'url_shortener' => [
// TODO Move these options to their own config namespace. Maybe "redirects".
'redirect_status_code' => (int) env('REDIRECT_STATUS_CODE', DEFAULT_REDIRECT_STATUS_CODE),
'redirect_cache_lifetime' => (int) env('REDIRECT_CACHE_LIFETIME', DEFAULT_REDIRECT_CACHE_LIFETIME),
'redirects' => [
'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

@@ -2,19 +2,18 @@
declare(strict_types=1);
use function Shlinkio\Shlink\Common\env;
use Shlinkio\Shlink\Core\Config\EnvVars;
return (static function (): array {
$redisServers = env('REDIS_SERVERS');
$redisServers = EnvVars::REDIS_SERVERS()->loadFromEnv();
return match (true) {
$redisServers === null => [],
return match ($redisServers) {
null => [],
default => [
'cache' => [
'default_lifetime' => 86400, // 24h
'redis' => [
'servers' => $redisServers,
'sentinel_service' => env('REDIS_SENTINEL_SERVICE'),
'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE()->loadFromEnv(),
],
],
],

View File

@@ -3,13 +3,12 @@
declare(strict_types=1);
use Mezzio\Router\FastRouteRouter;
use function Shlinkio\Shlink\Common\env;
use Shlinkio\Shlink\Core\Config\EnvVars;
return [
'router' => [
'base_path' => env('BASE_PATH', ''),
'base_path' => EnvVars::BASE_PATH()->loadFromEnv(''),
'fastroute' => [
FastRouteRouter::CONFIG_CACHE_ENABLED => true,

View File

@@ -2,12 +2,12 @@
declare(strict_types=1);
use function Shlinkio\Shlink\Common\env;
use Shlinkio\Shlink\Core\Config\EnvVars;
use const Shlinkio\Shlink\MIN_TASK_WORKERS;
return (static function () {
$taskWorkers = (int) env('TASK_WORKER_NUM', 16);
return (static function (): array {
$taskWorkers = (int) EnvVars::TASK_WORKER_NUM()->loadFromEnv(16);
return [
@@ -17,11 +17,11 @@ return (static function () {
'swoole-http-server' => [
'host' => '0.0.0.0',
'port' => (int) env('PORT', 8080),
'port' => (int) EnvVars::PORT()->loadFromEnv(8080),
'process-name' => 'shlink',
'options' => [
'worker_num' => (int) env('WEB_WORKER_NUM', 16),
'worker_num' => (int) EnvVars::WEB_WORKER_NUM()->loadFromEnv(16),
'task_worker_num' => max($taskWorkers, MIN_TASK_WORKERS),
],
],

View File

@@ -2,35 +2,35 @@
declare(strict_types=1);
use function Shlinkio\Shlink\Common\env;
use Shlinkio\Shlink\Core\Config\EnvVars;
return [
'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) env('ANONYMIZE_REMOTE_ADDR', true),
'anonymize_remote_addr' => (bool) EnvVars::ANONYMIZE_REMOTE_ADDR()->loadFromEnv(true),
// Tells if visits to not-found URLs should be tracked. The disable_tracking option takes precedence
'track_orphan_visits' => (bool) env('TRACK_ORPHAN_VISITS', true),
'track_orphan_visits' => (bool) EnvVars::TRACK_ORPHAN_VISITS()->loadFromEnv(true),
// A query param that, if provided, will disable tracking of one particular visit. Always takes precedence
'disable_track_param' => env('DISABLE_TRACK_PARAM'),
'disable_track_param' => EnvVars::DISABLE_TRACK_PARAM()->loadFromEnv(),
// If true, visits will not be tracked at all
'disable_tracking' => (bool) env('DISABLE_TRACKING', false),
'disable_tracking' => (bool) EnvVars::DISABLE_TRACKING()->loadFromEnv(false),
// If true, visits will be tracked, but neither the IP address, nor the location will be resolved
'disable_ip_tracking' => (bool) env('DISABLE_IP_TRACKING', false),
'disable_ip_tracking' => (bool) EnvVars::DISABLE_IP_TRACKING()->loadFromEnv(false),
// If true, the referrer will not be tracked
'disable_referrer_tracking' => (bool) env('DISABLE_REFERRER_TRACKING', false),
'disable_referrer_tracking' => (bool) EnvVars::DISABLE_REFERRER_TRACKING()->loadFromEnv(false),
// If true, the user agent will not be tracked
'disable_ua_tracking' => (bool) env('DISABLE_UA_TRACKING', false),
'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' => env('DISABLE_TRACKING_FROM'),
'disable_tracking_from' => EnvVars::DISABLE_TRACKING_FROM()->loadFromEnv(),
],
];

View File

@@ -2,40 +2,27 @@
declare(strict_types=1);
use function Shlinkio\Shlink\Common\env;
use Shlinkio\Shlink\Core\Config\EnvVars;
use const Shlinkio\Shlink\DEFAULT_SHORT_CODES_LENGTH;
use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH;
return (static function (): array {
$shortCodesLength = max(
(int) env('DEFAULT_SHORT_CODES_LENGTH', DEFAULT_SHORT_CODES_LENGTH),
(int) EnvVars::DEFAULT_SHORT_CODES_LENGTH()->loadFromEnv(DEFAULT_SHORT_CODES_LENGTH),
MIN_SHORT_CODES_LENGTH,
);
$resolveSchema = static function (): string {
// Deprecated. For v3, IS_HTTPS_ENABLED should be true by default, instead of null
// return ((bool) env('IS_HTTPS_ENABLED', true)) ? 'https' : 'http';
$isHttpsEnabled = env('IS_HTTPS_ENABLED', env('USE_HTTPS'));
if ($isHttpsEnabled !== null) {
$boolIsHttpsEnabled = (bool) $isHttpsEnabled;
return $boolIsHttpsEnabled ? 'https' : 'http';
}
return env('SHORT_DOMAIN_SCHEMA', 'http');
};
return [
'url_shortener' => [
'domain' => [
// Deprecated SHORT_DOMAIN_* env vars
'schema' => $resolveSchema(),
'hostname' => env('DEFAULT_DOMAIN', env('SHORT_DOMAIN_HOST', '')),
'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(''),
],
'validate_url' => (bool) env('VALIDATE_URLS', false), // Deprecated
'default_short_codes_length' => $shortCodesLength,
'auto_resolve_titles' => (bool) env('AUTO_RESOLVE_TITLES', false),
'append_extra_path' => (bool) env('REDIRECT_APPEND_EXTRA_PATH', false),
'auto_resolve_titles' => (bool) EnvVars::AUTO_RESOLVE_TITLES()->loadFromEnv(false),
'append_extra_path' => (bool) EnvVars::REDIRECT_APPEND_EXTRA_PATH()->loadFromEnv(false),
],
];

View File

@@ -2,17 +2,18 @@
declare(strict_types=1);
use function Shlinkio\Shlink\Common\env;
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 = env('VISITS_WEBHOOKS');
$webhooks = EnvVars::VISITS_WEBHOOKS()->loadFromEnv();
return [
'url_shortener' => [
// TODO Move these options to their own config namespace
'visits_webhooks' => $webhooks === null ? [] : explode(',', $webhooks),
'notify_orphan_visits_to_webhooks' => (bool) env('NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS', false),
'visits_webhooks' => [
'webhooks' => $webhooks === null ? [] : explode(',', $webhooks),
'notify_orphan_visits_to_webhooks' =>
(bool) EnvVars::NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS()->loadFromEnv(false),
],
];

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Application as CliApp;
return (static function () {
return (static function (): CliApp {
/** @var ContainerInterface $container */
$container = include __DIR__ . '/container.php';
return $container->get(CliApp::class);

View File

@@ -9,15 +9,20 @@ use Laminas\Diactoros;
use Mezzio;
use Mezzio\ProblemDetails;
use Mezzio\Swoole;
use Shlinkio\Shlink\Config\ConfigAggregator\EnvVarLoaderProvider;
use function class_exists;
use function Shlinkio\Shlink\Common\env;
use function Shlinkio\Shlink\Config\env;
use const PHP_SAPI;
$isCli = PHP_SAPI === 'cli';
$isTestEnv = env('APP_ENV') === 'test';
return (new ConfigAggregator\ConfigAggregator([
! $isTestEnv
? new EnvVarLoaderProvider('config/params/generated_config.php', Core\Config\EnvVars::cases())
: new ConfigAggregator\ArrayProvider([]),
Mezzio\ConfigProvider::class,
Mezzio\Router\ConfigProvider::class,
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
@@ -35,12 +40,9 @@ return (new ConfigAggregator\ConfigAggregator([
CLI\ConfigProvider::class,
Rest\ConfigProvider::class,
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
env('APP_ENV') === 'test'
$isTestEnv
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
// Deprecated. When the SimplifiedConfigParser is removed, load only generated_config.php here
: new ConfigAggregator\LaminasConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'),
: new ConfigAggregator\ArrayProvider([]),
], 'data/cache/app_config.php', [
Core\Config\SimplifiedConfigParser::class,
Core\Config\BasePathPrefixer::class,
Core\Config\DeprecatedConfigParser::class,
]))->getMergedConfig();

View File

@@ -12,11 +12,11 @@ const MIN_SHORT_CODES_LENGTH = 4;
const DEFAULT_REDIRECT_STATUS_CODE = StatusCodeInterface::STATUS_FOUND;
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
const CUSTOM_SLUGS_REGEXP = '/[^\pL\pN._~]/u'; // Any unicode letter or number, plus ".", "_" and "~" chars
const TITLE_TAG_VALUE = '/<title[^>]*>(.*?)<\/title>/i'; // Matches the value inside an html title tag
const TITLE_TAG_VALUE = '/<title[^>]*>(.*?)<\/title>/i'; // Matches the value inside a html title tag
const DEFAULT_QR_CODE_SIZE = 300;
const DEFAULT_QR_CODE_MARGIN = 0;
const DEFAULT_QR_CODE_FORMAT = 'png';
const DEFAULT_QR_CODE_ERROR_CORRECTION = 'l';
const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = true;
const MIN_TASK_WORKERS = 4;
const MIGRATIONS_TABLE = 'migrations';

View File

@@ -3,6 +3,7 @@
declare(strict_types=1);
use Laminas\ServiceManager\ServiceManager;
use Shlinkio\Shlink\Core\Config\EnvVars;
use Symfony\Component\Lock;
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
@@ -11,6 +12,9 @@ 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()));
// 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
if (! class_exists(LOCAL_LOCK_FACTORY)) {
@@ -18,7 +22,7 @@ if (! class_exists(LOCAL_LOCK_FACTORY)) {
}
// Build container
return (function () {
return (static function (): ServiceManager {
$config = require __DIR__ . '/config.php';
$container = new ServiceManager($config['dependencies']);
$container->setService('config', $config);

View File

@@ -3,9 +3,10 @@
declare(strict_types=1);
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Container\ContainerInterface;
return (static function () {
return (static function (): EntityManagerInterface {
/** @var ContainerInterface $container */
$container = include __DIR__ . '/container.php';
return $container->get(EntityManager::class);

View File

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

View File

@@ -22,7 +22,7 @@ use SebastianBergmann\CodeCoverage\Report\PHP;
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as Xml;
use function Laminas\Stratigility\middleware;
use function Shlinkio\Shlink\Common\env;
use function Shlinkio\Shlink\Config\env;
use function sprintf;
use function sys_get_temp_dir;
@@ -55,6 +55,7 @@ $buildDbConnection = static function (): array {
'user' => 'postgres',
'password' => 'root',
'dbname' => 'shlink_test',
'charset' => 'utf8',
],
'mssql' => [
'driver' => 'pdo_sqlsrv',
@@ -70,6 +71,7 @@ $buildDbConnection = static function (): array {
'user' => 'root',
'password' => 'root',
'dbname' => 'shlink_test',
'charset' => 'utf8mb4',
],
};
};
@@ -107,6 +109,7 @@ return [
'process-name' => 'shlink_test',
'options' => [
'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid',
'log_file' => __DIR__ . '/../../data/log/api-tests/output.log',
'enable_coroutine' => false,
],
],

View File

@@ -1,4 +1,4 @@
/var/log/shlink/shlink_swoole.log {
/var/log/shlink/shlink_openswoole.log {
su root root
daily
missingok
@@ -8,6 +8,6 @@
notifempty
create 0640 root root
postrotate
/etc/init.d/shlink_swoole restart
/etc/init.d/shlink_openswoole restart
endscript
}

View File

@@ -1,26 +1,26 @@
#!/bin/bash
### BEGIN INIT INFO
# Provides: shlink_swoole
# Provides: shlink_openswoole
# Required-Start: $local_fs $network $named $time $syslog
# Required-Stop: $local_fs $network $named $time $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Description: Shlink non-blocking server with swoole
# Description: Shlink non-blocking server with openswoole
### END INIT INFO
SCRIPT=/path/to/shlink/vendor/bin/laminas\ mezzio:swoole:start
RUNAS=root
PIDFILE=/var/run/shlink_swoole.pid
PIDFILE=/var/run/shlink_openswoole.pid
LOGDIR=/var/log/shlink
LOGFILE=${LOGDIR}/shlink_swoole.log
LOGFILE=${LOGDIR}/shlink_openswoole.log
start() {
if [[ -f "$PIDFILE" ]] && kill -0 $(cat "$PIDFILE"); then
echo 'Shlink with swoole already running' >&2
echo 'Shlink with openswoole already running' >&2
return 1
fi
echo 'Starting shlink with swoole' >&2
echo 'Starting shlink with openswoole' >&2
mkdir -p "$LOGDIR"
touch "$LOGFILE"
local CMD="$SCRIPT &> \"$LOGFILE\" & echo \$!"
@@ -30,10 +30,10 @@ start() {
stop() {
if [[ ! -f "$PIDFILE" ]] || ! kill -0 $(cat "$PIDFILE"); then
echo 'Shlink with swoole not running' >&2
echo 'Shlink with openswoole not running' >&2
return 1
fi
echo 'Stopping shlink with swoole' >&2
echo 'Stopping shlink with openswoole' >&2
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
echo 'Shlink stopped' >&2
}

View File

@@ -1,15 +1,14 @@
FROM php:8.1.0-fpm-alpine3.15
FROM php:8.1.5-fpm-alpine3.15
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
ENV APCU_VERSION 5.1.21
ENV PDO_SQLSRV_VERSION 5.10.0beta2
ENV PDO_SQLSRV_VERSION 5.10.0
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 iconv
RUN docker-php-ext-install calendar
RUN apk add --no-cache oniguruma-dev
@@ -31,9 +30,6 @@ RUN docker-php-ext-install gd
RUN apk add --no-cache postgresql-dev
RUN docker-php-ext-install pdo_pgsql
RUN apk add --no-cache gmp-dev
RUN docker-php-ext-install gmp
RUN docker-php-ext-install sockets
RUN docker-php-ext-install bcmath

View File

@@ -1,17 +1,16 @@
FROM php:8.1.0-alpine3.15
FROM php:8.1.5-alpine3.15
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
ENV APCU_VERSION 5.1.21
ENV INOTIFY_VERSION 3.0.0
ENV OPENSWOOLE_VERSION 4.8.1
ENV PDO_SQLSRV_VERSION 5.10.0beta2
ENV OPENSWOOLE_VERSION 4.11.0
ENV PDO_SQLSRV_VERSION 5.10.0
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 iconv
RUN docker-php-ext-install calendar
RUN apk add --no-cache oniguruma-dev
@@ -33,9 +32,6 @@ RUN docker-php-ext-install gd
RUN apk add --no-cache postgresql-dev
RUN docker-php-ext-install pdo_pgsql
RUN apk add --no-cache gmp-dev
RUN docker-php-ext-install gmp
RUN docker-php-ext-install sockets
RUN docker-php-ext-install bcmath

View File

@@ -5,45 +5,45 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\Migrations\AbstractMigration;
use function is_subclass_of;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20160819142757 extends AbstractMigration
{
private const MYSQL = 'mysql';
private const SQLITE = 'sqlite';
/**
* @throws Exception
* @throws SchemaException
*/
public function up(Schema $schema): void
{
$db = $this->connection->getDatabasePlatform()->getName();
$platformClass = $this->connection->getDatabasePlatform();
$table = $schema->getTable('short_urls');
$column = $table->getColumn('short_code');
if ($db === self::MYSQL) {
$column->setPlatformOption('collation', 'utf8_bin');
} elseif ($db === self::SQLITE) {
$column->setPlatformOption('collate', 'BINARY');
}
match (true) {
is_subclass_of($platformClass, MySQLPlatform::class) => $column
->setPlatformOption('charset', 'utf8mb4')
->setPlatformOption('collation', 'utf8mb4_bin'),
is_subclass_of($platformClass, SqlitePlatform::class) => $column->setPlatformOption('collate', 'BINARY'),
default => null,
};
}
/**
* @throws Exception
*/
public function down(Schema $schema): void
{
$this->connection->getDatabasePlatform()->getName();
// Nothing to roll back
}
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
@@ -76,6 +77,6 @@ class Version20160820191203 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\DBAL\Types\Types;
@@ -48,6 +49,6 @@ class Version20171021093246 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\DBAL\Types\Types;
@@ -45,6 +46,6 @@ class Version20171022064541 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\Migrations\AbstractMigration;
@@ -42,6 +43,6 @@ final class Version20180801183328 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
use PDO;
@@ -69,6 +70,6 @@ final class Version20180913205455 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\Migrations\AbstractMigration;
@@ -50,6 +51,6 @@ final class Version20180915110857 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\DBAL\Schema\Table;
@@ -58,7 +59,7 @@ final class Version20181020060559 extends AbstractMigration
foreach (self::COLUMNS as $camelCaseName => $snakeCaseName) {
$qb->set($snakeCaseName, $camelCaseName);
}
$qb->execute();
$qb->executeStatement();
}
public function down(Schema $schema): void
@@ -68,6 +69,6 @@ final class Version20181020060559 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\Migrations\AbstractMigration;
@@ -41,6 +42,6 @@ final class Version20181020065148 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
@@ -37,6 +38,6 @@ final class Version20181110175521 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
@@ -37,6 +38,6 @@ final class Version20190824075137 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\DBAL\Types\Types;
@@ -55,6 +56,6 @@ final class Version20190930165521 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
@@ -49,6 +50,6 @@ final class Version20191001201532 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
@@ -37,6 +38,6 @@ final class Version20191020074522 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -5,6 +5,8 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
@@ -38,7 +40,7 @@ final class Version20200105165647 extends AbstractMigration
'zeroValue' => '0',
'emptyString' => '',
])
->execute();
->executeStatement();
}
}
@@ -61,14 +63,14 @@ final class Version20200105165647 extends AbstractMigration
*/
public function postUp(Schema $schema): void
{
$platformName = $this->connection->getDatabasePlatform()->getName();
$castType = $platformName === 'postgres' ? 'DOUBLE PRECISION' : 'DECIMAL(9,2)';
$isPostgres = $this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform;
$castType = $isPostgres ? 'DOUBLE PRECISION' : 'DECIMAL(9,2)';
foreach (self::COLUMNS as $newName => $oldName) {
$qb = $this->connection->createQueryBuilder();
$qb->update('visit_locations')
->set($newName, 'CAST(' . $oldName . ' AS ' . $castType . ')')
->execute();
->executeStatement();
}
}
@@ -78,7 +80,7 @@ final class Version20200105165647 extends AbstractMigration
$qb = $this->connection->createQueryBuilder();
$qb->update('visit_locations')
->set($oldName, $newName)
->execute();
->executeStatement();
}
}
@@ -96,6 +98,6 @@ final class Version20200105165647 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
@@ -47,6 +48,6 @@ final class Version20200106215144 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
@@ -36,6 +38,9 @@ final class Version20200110182849 extends AbstractMigration
);
}
/**
* @throws Exception
*/
public function setDefaultValueForColumnInTable(string $tableName, string $columnName): void
{
$qb = $this->connection->createQueryBuilder();
@@ -43,7 +48,7 @@ final class Version20200110182849 extends AbstractMigration
->set($columnName, ':emptyValue')
->setParameter('emptyValue', self::DEFAULT_EMPTY_VALUE)
->where($qb->expr()->isNull($columnName))
->execute();
->executeStatement();
}
public function down(Schema $schema): void
@@ -53,6 +58,6 @@ final class Version20200110182849 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
@@ -32,7 +33,7 @@ final class Version20200323190014 extends AbstractMigration
->andWhere($qb->expr()->eq('lon', 0))
->setParameter('isEmpty', true)
->setParameter('emptyString', '')
->execute();
->executeStatement();
}
public function down(Schema $schema): void
@@ -45,6 +46,6 @@ final class Version20200323190014 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
@@ -27,6 +28,6 @@ final class Version20200503170404 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
@@ -44,6 +45,6 @@ final class Version20201023090929 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -6,6 +6,7 @@ namespace ShlinkMigrations;
use Cake\Chronos\Chronos;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
@@ -86,6 +87,6 @@ final class Version20201102113208 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
@@ -52,6 +53,6 @@ final class Version20210102174433 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
@@ -26,6 +27,6 @@ final class Version20210118153932 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
@@ -36,6 +37,6 @@ final class Version20210202181026 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
@@ -43,6 +44,6 @@ final class Version20210207100807 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
@@ -37,6 +38,6 @@ final class Version20210306165711 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
@@ -26,6 +27,6 @@ final class Version20210522051601 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
@@ -28,6 +29,6 @@ final class Version20210522124633 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Types;
@@ -41,6 +42,6 @@ final class Version20210720143824 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
@@ -26,6 +27,6 @@ final class Version20211002072605 extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20220110113313 extends AbstractMigration
{
private const CHARSET = 'utf8mb4';
private const COLLATIONS = [
'short_urls' => [
'original_url' => 'unicode_ci',
'short_code' => 'bin',
'import_original_short_code' => 'unicode_ci',
'title' => 'unicode_ci',
],
'domains' => [
'authority' => 'unicode_ci',
'base_url_redirect' => 'unicode_ci',
'regular_not_found_redirect' => 'unicode_ci',
'invalid_short_url_redirect' => 'unicode_ci',
],
'tags' => [
'name' => 'unicode_ci',
],
'visits' => [
'referer' => 'unicode_ci',
'user_agent' => 'unicode_ci',
'visited_url' => 'unicode_ci',
],
'visit_locations' => [
'country_code' => 'unicode_ci',
'country_name' => 'unicode_ci',
'region_name' => 'unicode_ci',
'city_name' => 'unicode_ci',
'timezone' => 'unicode_ci',
],
];
public function up(Schema $schema): void
{
$this->skipIf(! $this->isMySql(), 'This only sets MySQL-specific database options');
foreach (self::COLLATIONS as $tableName => $columns) {
$table = $schema->getTable($tableName);
foreach ($columns as $columnName => $collation) {
$table->getColumn($columnName)
->setPlatformOption('charset', self::CHARSET)
->setPlatformOption('collation', self::CHARSET . '_' . $collation);
}
}
}
public function down(Schema $schema): void
{
// No down
}
public function isTransactional(): bool
{
return ! $this->isMySql();
}
private function isMySql(): bool
{
return $this->connection->getDatabasePlatform() instanceof MySQLPlatform;
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace <namespace>;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
@@ -21,6 +22,6 @@ final class <className> extends AbstractMigration
public function isTransactional(): bool
{
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
}
}

View File

@@ -18,6 +18,8 @@ services:
build:
context: .
dockerfile: ./data/infra/php.Dockerfile
ports:
- '8888:8888'
volumes:
- ./:/home/shlink/www
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
@@ -98,7 +100,7 @@ services:
shlink_db_maria:
container_name: shlink_db_maria
image: mariadb:10.5
image: mariadb:10.7
ports:
- "3308:3306"
volumes:

View File

@@ -5,7 +5,7 @@
This image provides an easy way to set up [shlink](https://shlink.io) on a container-based runtime.
It exposes a shlink instance served with [openswoole](https://www.swoole.co.uk/), which can be linked to external databases to persist data.
It exposes a shlink instance served with [openswoole](https://openswoole.com/), which can be linked to external databases to persist data.
## Usage

View File

@@ -24,11 +24,9 @@ if [ ! -z "${GEOLITE_LICENSE_KEY}" ]; then
php bin/cli visit:download-db -n ${flags}
fi
# Periodicaly run visit:locate every hour
# https://shlink.io/documentation/long-running-tasks/#locate-visits
# set env var "ENABLE_PERIODIC_VISIT_LOCATE=true" to enable
# Periodically run visit:locate every hour, if ENABLE_PERIODIC_VISIT_LOCATE=true was provided
if [ $ENABLE_PERIODIC_VISIT_LOCATE ]; then
echo "Configuring periodic visit locate..."
echo "Configuring periodic visit location..."
echo "0 * * * * php /etc/shlink/bin/cli visit:locate -q" > /etc/crontabs/root
/usr/sbin/crond &
fi

View File

@@ -0,0 +1,51 @@
# Update env vars behavior to have precedence over installer options
* Status: Accepted
* Date: 2022-01-15
## Context and problem statement
Shlink supports providing configuration via the installer tool that generates a config file that gets merged with the rest of the config, or via environment variables.
It is potentially possible to combine both, but if you do so, you will find out the installer tool config has precedence over env vars, which is not very intuitive.
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
* 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.
* Make the installer generate a config file which also includes the logic to load env vars on it.
* Make the installer no longer generate the config structure, and instead generate a map with env vars and their values. Then Shlink would define those env vars if not defined already.
## Decision outcome
The most viable option was finally to re-think the installer tool, and make it generate a map of env vars and their values.
Then Shlink reads this as the first config file, which sets the values as env vars if not yet defined, and later on, the values are read as usual wherever needed.
## Pros and Cons of the Options
### Read all env vars in a single config file
* Bad: This option had to be discarded, as it would always override the installer config no matter what.
### Read all env vars in a config post-processor
* Good because it would not require any change in the installer.
* Bad because it requires moving all env var reading logic somewhere else, while having it together with their contextual config is quite convenient.
* Bad because it requires defining a map between the config path from the installer and the env var to set.
### Make the installer generate a config file which also reads env vars
* Good because it would not require changing Shlink.
* Bad because it requires looking for a new way to generate the installer config.
* Bad because it would mean reading the env vars in multiple places.
### Re-think the installer to no longer generate internal config, and instead, just define values for regular env vars
* Bad because it requires changes both in Shlink and the installer.
* Bad because it's more error-prone, and the option with higher chances to introduce a regression.
* Good because it finally decouples Shlink internal config (which is an implementation detail) from any external tool, including the installer, allowing to change it at will.
* Good because it opens the door to eventually simplify the installer. For the moment, it requires a bit of extra logic to support importing the old config.
* Good because it allows keeping the logic to read env vars next to the config where it applies.

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-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)
* [2021-01-17 Support restrictions and permissions in API keys](2021-01-17-support-restrictions-and-permissions-in-api-keys.md)

View File

@@ -1,7 +1,7 @@
{
"value": {
"detail":"No URL found with short code \"abc123\"",
"title":"Short URL not found",
"detail": "No URL found with short code \"abc123\"",
"title": "Short URL not found",
"type": "INVALID_SHORTCODE",
"status": 404,
"shortCode": "abc123"

View File

@@ -49,10 +49,20 @@
}
}
},
{
"name": "tagsMode",
"in": "query",
"description": "Tells how the filtering by tags should work, returning short URLs containing \"any\" of the tags, or \"all\" the tags. It's ignored if no tags are provided, and defaults to \"any\" if not provided.",
"required": false,
"schema": {
"type": "string",
"enum": ["any", "all"]
}
},
{
"name": "orderBy",
"in": "query",
"description": "The field from which you want to order the result. (Since v1.3.0)",
"description": "The field from which you want to order the result.",
"required": false,
"schema": {
"type": "string",

View File

@@ -320,7 +320,7 @@
},
"example": {
"title": "Cannot delete short URL",
"type": "INVALID_SHORTCODE_DELETION",
"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",

View File

@@ -1,106 +0,0 @@
{
"put": {
"deprecated": true,
"operationId": "editShortUrlTags",
"tags": [
"Short URLs"
],
"summary": "Edit tags on short URL",
"description": "Edit the tags on URL identified by provided short code.<br />This endpoint is deprecated. Use the [Edit short URL](#/Short%20URLs/editShortUrl) endpoint to edit tags.",
"parameters": [
{
"$ref": "../parameters/version.json"
},
{
"name": "shortCode",
"in": "path",
"description": "The short code for the short URL in which we want to edit tags.",
"required": true,
"schema": {
"type": "string"
}
},
{
"$ref": "../parameters/domain.json"
}
],
"requestBody": {
"description": "Request body.",
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"tags"
],
"properties": {
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "The list of tags to set to the short URL."
}
}
}
}
}
},
"security": [
{
"ApiKey": []
}
],
"responses": {
"200": {
"description": "List of tags.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"tags": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
},
"400": {
"description": "The request body does not contain a \"tags\" param with array type.",
"content": {
"application/problem+json": {
"schema": {
"$ref": "../definitions/Error.json"
}
}
}
},
"404": {
"description": "No short URL was found for provided short code.",
"content": {
"application/json": {
"schema": {
"$ref": "../definitions/Error.json"
}
}
}
},
"default": {
"description": "Unexpected error.",
"content": {
"application/json": {
"schema": {
"$ref": "../definitions/Error.json"
}
}
}
}
}
}
}

View File

@@ -5,7 +5,7 @@
"Tags"
],
"summary": "List existing tags",
"description": "Returns the list of all tags used in any short URL, ordered by name",
"description": "Returns the list of all tags used in any short URL",
"security": [
{
"ApiKey": []
@@ -17,7 +17,8 @@
},
{
"name": "withStats",
"description": "Whether you want to include also a list with general stats by tag or not.",
"deprecated": true,
"description": "**[Deprecated]** Use [GET /tags/stats](#/Tags/tagsWithStats) endpoint to get tags with their stats.",
"in": "query",
"required": false,
"schema": {
@@ -27,6 +28,46 @@
"false"
]
}
},
{
"name": "page",
"in": "query",
"description": "The page to display. Defaults to 1",
"required": false,
"schema": {
"type": "number"
}
},
{
"name": "itemsPerPage",
"in": "query",
"description": "The amount of items to return on every page. Defaults to all the items",
"required": false,
"schema": {
"type": "number"
}
},
{
"name": "searchTerm",
"in": "query",
"description": "A query used to filter results by searching for it on the tag name.",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "orderBy",
"in": "query",
"description": "To determine how to order the results.",
"required": false,
"schema": {
"type": "string",
"enum": [
"tag-ASC",
"tag-DESC"
]
}
}
],
"responses": {
@@ -53,122 +94,28 @@
"items": {
"$ref": "../definitions/TagInfo.json"
}
},
"pagination": {
"$ref": "../definitions/Pagination.json"
}
}
}
}
},
"examples": {
"Without stats": {
"value": {
"tags": {
"data": [
"games",
"php",
"shlink",
"tech"
]
}
}
},
"With stats": {
"value": {
"tags": {
"data": [
"games",
"shlink"
],
"stats": [
{
"tag": "games",
"shortUrlsCount": 10,
"visitsCount": 521
},
{
"tag": "shlink",
"shortUrlsCount": 7,
"visitsCount": 1087
}
]
}
}
}
}
}
}
},
"default": {
"description": "Unexpected error.",
"content": {
"application/problem+json": {
"schema": {
"$ref": "../definitions/Error.json"
}
}
}
}
}
},
"post": {
"deprecated": true,
"operationId": "createTags",
"tags": [
"Tags"
],
"summary": "Create tags",
"description": "Provided a list of tags, creates all that do not yet exist<br />This endpoint is deprecated, as tags are automatically created while creating a short URL",
"security": [
{
"ApiKey": []
}
],
"parameters": [
{
"$ref": "../parameters/version.json"
}
],
"requestBody": {
"description": "Request body.",
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"tags"
],
"properties": {
"example": {
"tags": {
"description": "The list of tag names to create",
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "The list of tags",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"tags": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "string"
}
}
}
"data": [
"games",
"php",
"shlink",
"tech"
],
"pagination": {
"currentPage": 5,
"pagesCount": 10,
"itemsPerPage": 4,
"itemsInCurrentPage": 4,
"totalItems": 38
}
}
}

View File

@@ -0,0 +1,172 @@
{
"get": {
"operationId": "getDomainVisits",
"tags": [
"Visits"
],
"summary": "List visits for domain",
"description": "Get the list of visits on any short URL which belongs to provided domain.",
"parameters": [
{
"$ref": "../parameters/version.json"
},
{
"name": "domain",
"in": "path",
"description": "The domain from which we want to get the visits, or **DEFAULT** keyword for default domain.",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "startDate",
"in": "query",
"description": "The date (in ISO-8601 format) from which we want to get visits.",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "endDate",
"in": "query",
"description": "The date (in ISO-8601 format) until which we want to get visits.",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "page",
"in": "query",
"description": "The page to display. Defaults to 1",
"required": false,
"schema": {
"type": "number"
}
},
{
"name": "itemsPerPage",
"in": "query",
"description": "The amount of items to return on every page. Defaults to all the items",
"required": false,
"schema": {
"type": "number"
}
},
{
"name": "excludeBots",
"in": "query",
"description": "Tells if visits from potential bots should be excluded from the result set",
"required": false,
"schema": {
"type": "string",
"enum": ["true"]
}
}
],
"security": [
{
"ApiKey": []
}
],
"responses": {
"200": {
"description": "List of visits.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"visits": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "../definitions/Visit.json"
}
},
"pagination": {
"$ref": "../definitions/Pagination.json"
}
}
}
}
},
"example": {
"visits": {
"data": [
{
"referer": "https://twitter.com",
"date": "2015-08-20T05:05:03+04:00",
"userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0",
"visitLocation": null,
"potentialBot": false
},
{
"referer": "https://t.co",
"date": "2015-08-20T05:05:03+04:00",
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
"visitLocation": {
"cityName": "Cupertino",
"countryCode": "US",
"countryName": "United States",
"latitude": 37.3042,
"longitude": -122.0946,
"regionName": "California",
"timezone": "America/Los_Angeles"
},
"potentialBot": false
},
{
"referer": null,
"date": "2015-08-20T05:05:03+04:00",
"userAgent": "some_web_crawler/1.4",
"visitLocation": null,
"potentialBot": true
}
],
"pagination": {
"currentPage": 5,
"pagesCount": 12,
"itemsPerPage": 10,
"itemsInCurrentPage": 10,
"totalItems": 115
}
}
}
}
}
},
"404": {
"description": "The domain does not exist.",
"content": {
"application/problem+json": {
"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"
}
}
}
},
"default": {
"description": "Unexpected error.",
"content": {
"application/problem+json": {
"schema": {
"$ref": "../definitions/Error.json"
}
}
}
}
}
}
}

View File

@@ -0,0 +1,127 @@
{
"get": {
"operationId": "tagsWithStats",
"tags": [
"Tags"
],
"summary": "Get tags with stats",
"description": "Returns the list of all tags used in any short URL, together with the amount of short URLs and visits for it",
"security": [
{
"ApiKey": []
}
],
"parameters": [
{
"$ref": "../parameters/version.json"
},
{
"name": "page",
"in": "query",
"description": "The page to display. Defaults to 1",
"required": false,
"schema": {
"type": "number"
}
},
{
"name": "itemsPerPage",
"in": "query",
"description": "The amount of items to return on every page. Defaults to all the items",
"required": false,
"schema": {
"type": "number"
}
},
{
"name": "searchTerm",
"in": "query",
"description": "A query used to filter results by searching for it on the tag name.",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "orderBy",
"in": "query",
"description": "To determine how to order the results.<br /><br />**Important!** Ordering by `shortUrlsCount` or `visitsCount` has a [known performance issue](https://github.com/shlinkio/shlink/issues/1346) which makes loading a subset of the list take as much as loading the whole list.<br />If you plan to order by any of these fields, it's worth loading the whole list with no pagination.",
"required": false,
"schema": {
"type": "string",
"enum": [
"tag-ASC",
"tag-DESC",
"shortUrlsCount-ASC",
"shortUrlsCount-DESC",
"visitsCount-ASC",
"visitsCount-DESC"
]
}
}
],
"responses": {
"200": {
"description": "The list of tags",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"tags": {
"type": "object",
"required": ["data"],
"properties": {
"data": {
"description": "The tag stats will be returned only if the withStats param was provided with value 'true'",
"type": "array",
"items": {
"$ref": "../definitions/TagInfo.json"
}
},
"pagination": {
"$ref": "../definitions/Pagination.json"
}
}
}
}
},
"example": {
"tags": {
"data": [
{
"tag": "games",
"shortUrlsCount": 10,
"visitsCount": 521
},
{
"tag": "shlink",
"shortUrlsCount": 7,
"visitsCount": 1087
}
],
"pagination": {
"currentPage": 5,
"pagesCount": 5,
"itemsPerPage": 10,
"itemsInCurrentPage": 2,
"totalItems": 42
}
}
}
}
}
},
"default": {
"description": "Unexpected error.",
"content": {
"application/problem+json": {
"schema": {
"$ref": "../definitions/Error.json"
}
}
}
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"get": {
"operationId": "getNonOrphanVisits",
"tags": [
"Visits"
],
"summary": "List non-orphan visits",
"description": "Get the list of visits to any short URL.",
"parameters": [
{
"$ref": "../parameters/version.json"
},
{
"name": "startDate",
"in": "query",
"description": "The date (in ISO-8601 format) from which we want to get visits.",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "endDate",
"in": "query",
"description": "The date (in ISO-8601 format) until which we want to get visits.",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "page",
"in": "query",
"description": "The page to display. Defaults to 1",
"required": false,
"schema": {
"type": "number"
}
},
{
"name": "itemsPerPage",
"in": "query",
"description": "The amount of items to return on every page. Defaults to all the items",
"required": false,
"schema": {
"type": "number"
}
},
{
"name": "excludeBots",
"in": "query",
"description": "Tells if visits from potential bots should be excluded from the result set",
"required": false,
"schema": {
"type": "string",
"enum": ["true"]
}
}
],
"security": [
{
"ApiKey": []
}
],
"responses": {
"200": {
"description": "List of visits.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"visits": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "../definitions/Visit.json"
}
},
"pagination": {
"$ref": "../definitions/Pagination.json"
}
}
}
}
},
"example": {
"visits": {
"data": [
{
"referer": "https://twitter.com",
"date": "2015-08-20T05:05:03+04:00",
"userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0",
"visitLocation": null,
"potentialBot": false
},
{
"referer": "https://t.co",
"date": "2015-08-20T05:05:03+04:00",
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
"visitLocation": {
"cityName": "Cupertino",
"countryCode": "US",
"countryName": "United States",
"latitude": 37.3042,
"longitude": -122.0946,
"regionName": "California",
"timezone": "America/Los_Angeles"
},
"potentialBot": false
},
{
"referer": null,
"date": "2015-08-20T05:05:03+04:00",
"userAgent": "some_web_crawler/1.4",
"visitLocation": null,
"potentialBot": true
}
],
"pagination": {
"currentPage": 5,
"pagesCount": 12,
"itemsPerPage": 10,
"itemsInCurrentPage": 10,
"totalItems": 115
}
}
}
}
}
},
"default": {
"description": "Unexpected error.",
"content": {
"application/problem+json": {
"schema": {
"$ref": "../definitions/Error.json"
}
}
}
}
}
}
}

View File

@@ -1,66 +0,0 @@
{
"get": {
"operationId": "shortUrlQrCodeSize",
"deprecated": true,
"tags": [
"URL Shortener"
],
"summary": "Short URL QR code",
"description": "Generates a QR code image pointing to a short URL",
"parameters": [
{
"name": "shortCode",
"in": "path",
"description": "The short code to resolve.",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "size",
"in": "path",
"description": "The size of the image to be returned.",
"required": true,
"schema": {
"type": "integer",
"minimum": 50,
"maximum": 1000,
"default": 300
}
},
{
"name": "format",
"in": "query",
"description": "The format for the QR code image, being valid values png and svg. Not providing the param or providing any other value will fall back to png.",
"required": false,
"schema": {
"type": "string",
"enum": [
"png",
"svg"
]
}
}
],
"responses": {
"200": {
"description": "QR code in PNG format",
"content": {
"image/png": {
"schema": {
"type": "string",
"format": "binary"
}
},
"image/svg+xml": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
}
}
}
}

View File

@@ -78,13 +78,13 @@
"/rest/v{version}/short-urls/{shortCode}": {
"$ref": "paths/v1_short-urls_{shortCode}.json"
},
"/rest/v{version}/short-urls/{shortCode}/tags": {
"$ref": "paths/v1_short-urls_{shortCode}_tags.json"
},
"/rest/v{version}/tags": {
"$ref": "paths/v1_tags.json"
},
"/rest/v{version}/tags/stats": {
"$ref": "paths/v2_tags_stats.json"
},
"/rest/v{version}/visits": {
"$ref": "paths/v2_visits.json"
@@ -95,9 +95,15 @@
"/rest/v{version}/tags/{tag}/visits": {
"$ref": "paths/v2_tags_{tag}_visits.json"
},
"/rest/v{version}/domains/{domain}/visits": {
"$ref": "paths/v2_domains_{domain}_visits.json"
},
"/rest/v{version}/visits/orphan": {
"$ref": "paths/v2_visits_orphan.json"
},
"/rest/v{version}/visits/non-orphan": {
"$ref": "paths/v2_visits_non-orphan.json"
},
"/rest/v{version}/domains": {
"$ref": "paths/v2_domains.json"
@@ -122,9 +128,6 @@
},
"/{shortCode}/qr-code": {
"$ref": "paths/{shortCode}_qr-code.json"
},
"/{shortCode}/qr-code/{size}": {
"$ref": "paths/{shortCode}_qr-code_{size}.json"
}
}
}

View File

@@ -7,6 +7,7 @@
"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"
},

View File

@@ -7,6 +7,7 @@
"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"
},

View File

@@ -7,10 +7,11 @@
"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",
"badge": {
"branch": "develop"
"stryker": {
"report": "develop"
}
},
"tmpDir": "build/infection-unit/temp",

View File

@@ -2,13 +2,15 @@
declare(strict_types=1);
use const Shlinkio\Shlink\MIGRATIONS_TABLE;
return [
'migrations_paths' => [
'ShlinkMigrations' => 'data/migrations',
],
'table_storage' => [
'table_name' => 'migrations',
'table_name' => MIGRATIONS_TABLE,
],
'custom_template' => 'data/migrations_template.txt',

View File

@@ -22,7 +22,6 @@ return [
Command\Api\ListKeysCommand::NAME => Command\Api\ListKeysCommand::class,
Command\Tag\ListTagsCommand::NAME => Command\Tag\ListTagsCommand::class,
Command\Tag\CreateTagCommand::NAME => Command\Tag\CreateTagCommand::class,
Command\Tag\RenameTagCommand::NAME => Command\Tag\RenameTagCommand::class,
Command\Tag\DeleteTagsCommand::NAME => Command\Tag\DeleteTagsCommand::class,

View File

@@ -53,7 +53,6 @@ return [
Command\Api\ListKeysCommand::class => ConfigAbstractFactory::class,
Command\Tag\ListTagsCommand::class => ConfigAbstractFactory::class,
Command\Tag\CreateTagCommand::class => ConfigAbstractFactory::class,
Command\Tag\RenameTagCommand::class => ConfigAbstractFactory::class,
Command\Tag\DeleteTagsCommand::class => ConfigAbstractFactory::class,
@@ -73,7 +72,7 @@ return [
TrackingOptions::class,
],
Util\ProcessRunner::class => [SymfonyCli\Helper\ProcessHelper::class],
ApiKey\RoleResolver::class => [DomainService::class],
ApiKey\RoleResolver::class => [DomainService::class, 'config.url_shortener.domain.hostname'],
Command\ShortUrl\CreateShortUrlCommand::class => [
Service\UrlShortener::class,
@@ -101,7 +100,6 @@ return [
Command\Api\ListKeysCommand::class => [ApiKeyService::class],
Command\Tag\ListTagsCommand::class => [TagService::class],
Command\Tag\CreateTagCommand::class => [TagService::class],
Command\Tag\RenameTagCommand::class => [TagService::class],
Command\Tag\DeleteTagsCommand::class => [TagService::class],

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