Compare commits

...

145 Commits

Author SHA1 Message Date
Alejandro Celaya
3479bbbb36 Merge pull request #567 from acelaya-forks/hotfix/v1.20.2
Hotfix/v1.20.2
2019-12-06 23:09:20 +01:00
Alejandro Celaya
e1a1a0652f Merge pull request #566 from acelaya-forks/bugfix/date-parsing
Bugfix/date parsing
2019-12-06 22:52:22 +01:00
Alejandro Celaya
3e9b775114 Fixed failing test 2019-12-06 22:45:15 +01:00
Alejandro Celaya
57c91aca3c Updated changelog with v1.20.2 2019-12-06 22:40:08 +01:00
Alejandro Celaya
05a64b8d9e Ensured dates parsing does not mask actual validation errors 2019-12-06 22:38:22 +01:00
Alejandro Celaya
30780f9c5f Merge pull request #565 from acelaya-forks/bugfix/control-rename-tag
Bugfix/control rename tag
2019-12-06 21:13:54 +01:00
Alejandro Celaya
3455df9214 Updated changelog 2019-12-06 21:06:47 +01:00
Alejandro Celaya
27aa8f9875 Handled rename tag error from command 2019-12-06 21:04:52 +01:00
Alejandro Celaya
05451e3d1a Handled tag conflict from rename tag action 2019-12-06 21:03:27 +01:00
Alejandro Celaya
b9b3295b52 Ensured a specific exception is thrown from TagService when trying to rename a tag to the name of another tag which already exists 2019-12-06 20:44:41 +01:00
Alejandro Celaya
f62ed66e26 Created TagConflictException 2019-12-06 10:20:56 +01:00
Alejandro Celaya
e2a9a989ab Merge pull request #563 from acelaya-forks/bugfix/missing-yaml
Bugfix/missing yaml
2019-12-06 09:59:09 +01:00
Alejandro Celaya
4af27650cd Updated changelog 2019-12-06 09:52:23 +01:00
Alejandro Celaya
76a603104d Migrated migrations config file from yaml to plain PHP 2019-12-06 09:50:37 +01:00
Alejandro Celaya
115ca0da0f Added v1.20.1 to changelog 2019-11-17 11:29:54 +01:00
Alejandro Celaya
673b545a83 Merge pull request #551 from acelaya-forks/feature/non-shared-locker
Feature/non shared locker
2019-11-17 11:21:27 +01:00
Alejandro Celaya
d030fd1aa6 Updated GeolocationDbUpdater to always use a local lock even if redis config is provided 2019-11-17 11:09:37 +01:00
Alejandro Celaya
7c1e40be88 Updated docker docs regarding image versioning 2019-11-17 10:38:05 +01:00
Alejandro Celaya
b739619532 Merge pull request #550 from acelaya-forks/feature/fix-db-silent-errors
Feature/fix db silent errors
2019-11-17 10:08:57 +01:00
Alejandro Celaya
372b83d92f Updated changelog 2019-11-17 10:02:03 +01:00
Alejandro Celaya
4e3b5419d5 Created small helper composer command 2019-11-17 10:00:29 +01:00
Alejandro Celaya
c34d5a35e2 Updated database commands so that internal commands are run with mustRun 2019-11-17 09:52:45 +01:00
Alejandro Celaya
a959b5bf02 Merge pull request #549 from acelaya-forks/feature/use-own-test-domains
Replaced third party domains used in tests by custom shlink domains
2019-11-16 13:47:37 +01:00
Alejandro Celaya
45ac2c3c51 Replaced third party domains used in tests by custom shlink domains 2019-11-16 13:37:53 +01:00
Alejandro Celaya
f6bddc6f24 Merge pull request #548 from acelaya-forks/feature/redirect-to-idn
Handled IDN domains also on internal redirections when validating a URL
2019-11-16 12:46:02 +01:00
Alejandro Celaya
6b8fc3228e Handled IDN domains also on internal redirections when validating a URL 2019-11-16 12:38:45 +01:00
Alejandro Celaya
8cf1a95df5 Swoole is no longer experimental 2019-11-16 10:59:56 +01:00
Alejandro Celaya
b3ea2969c5 Merge pull request #547 from acelaya-forks/feature/support-idn
Feature/support idn
2019-11-16 10:32:49 +01:00
Alejandro Celaya
054bbb8d5a Updated changelog 2019-11-16 10:22:00 +01:00
Alejandro Celaya
19c1b29f59 Added tests for UrlValidator 2019-11-16 10:19:25 +01:00
Alejandro Celaya
264b8c2a9e Added support for IDN 2019-11-16 10:06:55 +01:00
Alejandro Celaya
ec33b95f97 Brought intl extension back to docker images and kept as a requirement 2019-11-16 09:46:42 +01:00
Alejandro Celaya
4437d5305f Merge pull request #546 from acelaya-forks/feature/image-version
Feature/image version
2019-11-15 22:24:42 +01:00
Alejandro Celaya
f20f01e22e Removed Intl from docker image 2019-11-15 22:23:07 +01:00
Alejandro Celaya
1ee30fe5dc Updated dev docker images 2019-11-15 22:05:34 +01:00
Alejandro Celaya
4dc026d7fc Merge pull request #544 from Starbix/master
Update dependencies and baseimage
2019-11-15 22:01:14 +01:00
Cédric Laubacher
1e862a8ee8 Readd specific alpine version 2019-11-15 21:42:07 +01:00
Alejandro Celaya
5ece2d1939 Merge pull request #539 from acelaya-forks/feature/forward-query
Feature/forward query
2019-11-15 20:43:11 +01:00
Alejandro Celaya
146e9100be Updated changelog 2019-11-15 20:30:36 +01:00
Cédric Laubacher
0c854edc6b Use specific PHP version 2019-11-15 19:16:29 +01:00
Cédric Laubacher
07d031e7b9 Update Dockerfile 2019-11-15 17:55:06 +01:00
Alejandro Celaya
705dc2ec39 Added forward of query string from short URLs to long one 2019-11-13 21:04:44 +01:00
Alejandro Celaya
3b9221c7d2 Ensured options for short.url:list command have required values 2019-11-13 20:24:59 +01:00
Alejandro Celaya
576d602ed0 Merge pull request #537 from acelaya-forks/feature/installer-3.1
Updated to installer 3.1
2019-11-10 13:15:30 +01:00
Alejandro Celaya
9df8bd63d4 Updated to installer 3.1 2019-11-10 13:07:57 +01:00
Alejandro Celaya
99c4802367 Fixed docker docs line break 2019-11-10 12:14:00 +01:00
Alejandro Celaya
94dc6f2053 Merge pull request #536 from acelaya-forks/feature/simplified-config-workers
Added workers nums handling to simplified config parser
2019-11-10 12:11:41 +01:00
Alejandro Celaya
d4005da35c Added workers nums handling to simplified config parser 2019-11-10 12:04:14 +01:00
Alejandro Celaya
cbe2c362d5 Merge pull request #535 from acelaya-forks/feature/api-test-script
Updated API tests script so that it throws the same exit code returne…
2019-11-09 12:15:09 +01:00
Alejandro Celaya
8bf79db66a Fixed typo 2019-11-09 12:08:22 +01:00
Alejandro Celaya
b87964f716 Updated API tests script so that it throws the same exit code returned by phpunit 2019-11-09 11:25:33 +01:00
Alejandro Celaya
b0a574f578 Merge pull request #533 from acelaya-forks/feature/custom-workers
Feature/custom workers
2019-11-09 11:17:03 +01:00
Alejandro Celaya
92dc3019de Updated changelog 2019-11-09 11:08:28 +01:00
Alejandro Celaya
d8f92cb2be Added web worker num and task worker num to docker image config 2019-11-09 11:05:54 +01:00
Alejandro Celaya
bf24660ddb Merge pull request #526 from acelaya-forks/feature/enhanced-not-found-redirect
Feature/enhanced not found redirect
2019-11-02 21:24:34 +01:00
Alejandro Celaya
b66268867a Updated to shlink-installer 3 2019-11-02 21:15:26 +01:00
Alejandro Celaya
9abaa243e0 Some extra minor improvements 2019-11-02 19:08:07 +01:00
Alejandro Celaya
7030138ff4 Updated changelog 2019-11-02 18:57:16 +01:00
Alejandro Celaya
906dfe60f8 Simplified code to render not-foubnd templates by infering the template to be used inside NotFoundHandler 2019-11-02 18:49:24 +01:00
Alejandro Celaya
01f60614ef Removed hardcoded route names for core routes and used action class names instead 2019-11-02 18:36:15 +01:00
Alejandro Celaya
eeb5306883 Moved all logic to redirect to specific URLs when a 404 is found to the NotFoundHandler 2019-11-02 18:33:26 +01:00
Alejandro Celaya
24c3a3e84c Defined new options for new redirect configuration 2019-11-02 17:23:21 +01:00
Alejandro Celaya
8b9663aea0 Created DeprecatedConfigParserTest 2019-11-02 17:04:49 +01:00
Alejandro Celaya
b59f4e2805 Defined new configs for not found redirects 2019-11-02 11:30:09 +01:00
Alejandro Celaya
6293d57fde Merge pull request #523 from acelaya-forks/feature/refactorings
Feature/refactorings
2019-11-01 17:31:43 +01:00
Alejandro Celaya
39ac2efe26 Updated to latest shlink-common with bug fixes 2019-11-01 17:16:56 +01:00
Alejandro Celaya
1f449e8ce1 Disabled coroutines on swoole during API tests 2019-11-01 10:10:43 +01:00
Alejandro Celaya
ad906000c7 Removed typehint making phpstan throw false positive 2019-11-01 10:04:25 +01:00
Alejandro Celaya
5361f33cc1 Some more refactorings 2019-11-01 09:52:56 +01:00
Alejandro Celaya
1937f3ea22 Trying to automatically persist tags 2019-10-26 09:01:51 +02:00
Alejandro Celaya
f4e9d0c8fe Merge pull request #517 from acelaya-forks/feature/too-long-lock
Updated to shlink-ip-geolocation v1.1
2019-10-25 20:12:54 +02:00
Alejandro Celaya
9b767ee9f3 Updated to shlink-ip-geolocation v1.1 2019-10-25 20:00:26 +02:00
Alejandro Celaya
43cb91bf52 Merge pull request #516 from acelaya-forks/feature/qr-code-with-domain
Feature/qr code with domain
2019-10-22 20:03:37 +02:00
Alejandro Celaya
f784ee5b28 Fixed unit tests 2019-10-22 19:52:28 +02:00
Alejandro Celaya
cd6f067fe5 Ensured domain is taken into account when generating QR codes 2019-10-22 19:43:53 +02:00
Alejandro Celaya
6d366188c9 Added github funding 2019-10-22 19:37:35 +02:00
Alejandro Celaya
c4ca59dcb0 Merge pull request #515 from acelaya-forks/feature/host-and-port
Feature/host and port
2019-10-20 11:14:44 +02:00
Alejandro Celaya
74675ad314 Updated changelog 2019-10-20 10:30:51 +02:00
Alejandro Celaya
b5e4da847a Allowed port number on domain field when creating shotr URLs 2019-10-20 10:30:11 +02:00
Alejandro Celaya
232bf5a68b Merge pull request #513 from acelaya-forks/feature/fix-long-urls
Feature/fix long urls
2019-10-20 10:15:38 +02:00
Alejandro Celaya
67958a78d3 Updated changelog 2019-10-20 09:55:00 +02:00
Alejandro Celaya
b8cdc29d8f Increased long URL size in DB to 2048 characters 2019-10-20 09:53:11 +02:00
Alejandro Celaya
30e4ddb950 Updated to latest infection version 2019-10-20 09:42:46 +02:00
Alejandro Celaya
a61a7db275 Merge pull request #510 from acelaya-forks/feature/update-common
Updated common and qr-code
2019-10-15 19:17:44 +02:00
Alejandro Celaya
0f5e4e7fa2 Updated common and qr-code 2019-10-15 19:06:37 +02:00
Alejandro Celaya
eb17eae781 Merge pull request #506 from acelaya-forks/feature/improved-shortcodes
Feature/improved shortcodes
2019-10-11 12:51:24 +02:00
Alejandro Celaya
740a65f880 Updated references to SHORTCODE_CHARS in docker docs 2019-10-11 11:41:14 +02:00
Alejandro Celaya
5bd7b53e0a Added more tests for new logics 2019-10-11 11:28:53 +02:00
Alejandro Celaya
9538f474de Added logic to check if a short code is in use and regenerate it otherwise 2019-10-11 11:09:33 +02:00
Alejandro Celaya
8f2e78c946 Moved logic to generate random short codes to external function 2019-10-11 09:35:09 +02:00
Alejandro Celaya
2f09ff456c Updated logic to generate random short codes, increasing entropy 2019-10-11 09:14:25 +02:00
Alejandro Celaya
c8d950e04d Merge pull request #504 from acelaya-forks/feature/mariadb
Feature/mariadb
2019-10-06 12:37:22 +02:00
Alejandro Celaya
0f6a0da7a4 Added MariaDB to documentation 2019-10-06 12:27:11 +02:00
Alejandro Celaya
4300fb225f Ensured tests on maria DB are not run on travis, since it would conflict with MySQL 2019-10-06 12:23:02 +02:00
Alejandro Celaya
159021e87c Added MariaDB to issue templates 2019-10-06 11:39:53 +02:00
Alejandro Celaya
9dc6ea9eeb Documented how to use maria db with docker image 2019-10-06 11:38:23 +02:00
Alejandro Celaya
42e84e526e Added mariadb container for development environment 2019-10-06 11:21:41 +02:00
Alejandro Celaya
700ee40109 Merge pull request #503 from acelaya-forks/feature/update-coding-standard
Feature/update coding standard
2019-10-05 18:07:12 +02:00
Alejandro Celaya
4909bd9550 Updated changelog 2019-10-05 17:29:07 +02:00
Alejandro Celaya
e0d20bf8ff Updated to coding styles v2 2019-10-05 17:26:10 +02:00
Alejandro Celaya
09a5284675 Merge pull request #501 from acelaya-forks/feature/v1.19-final
Updated changelog adding final release 1.19.0
2019-10-05 11:09:12 +02:00
Alejandro Celaya
1112f3acdd Updated changelog adding final release 1.19.0 2019-10-05 11:08:50 +02:00
Alejandro Celaya
05e3071db2 Merge pull request #500 from acelaya-forks/feature/multiple-domains
Feature/multiple domains
2019-10-04 23:39:11 +02:00
Alejandro Celaya
403773bc17 Documented new feature in CHANGELOG 2019-10-04 21:46:41 +02:00
Alejandro Celaya
636df2a736 Read request's authority when tracking a visit and passed it down 2019-10-04 21:36:54 +02:00
Alejandro Celaya
baf3093893 Added support for domain param to command and action to resolve a short URL 2019-10-04 21:17:02 +02:00
Alejandro Celaya
8d3a49a319 Fixed issue with postgres when fetching resultset ordering by nullable column 2019-10-04 18:07:26 +02:00
Alejandro Celaya
eced1af21d Added more database cases covering different combinations of finding short URL by short code and domain 2019-10-04 17:34:34 +02:00
Alejandro Celaya
49c3c9bec1 Ensured domain is taken into account when looking for a short URL 2019-10-04 17:21:22 +02:00
Alejandro Celaya
2ffaabe594 Added option to define domain to GenerateShortUrlCommand 2019-10-02 20:29:13 +02:00
Alejandro Celaya
f31dc6c6e5 Added missing return type hints 2019-10-02 20:15:14 +02:00
Alejandro Celaya
f067d0e831 Allowed to provide the domain when creating a short URL 2019-10-02 20:13:25 +02:00
Alejandro Celaya
a892f72425 Added migration to make the combination of slug+domain unique 2019-10-02 20:01:15 +02:00
Alejandro Celaya
25f64a2fc4 Added check for domain when matching an existing short URL 2019-10-01 22:15:11 +02:00
Alejandro Celaya
fd1fe90731 Created tests for new domain resolvers 2019-10-01 22:00:46 +02:00
Alejandro Celaya
495643f4f1 Ensured domain is taken into account when checking if a slug is in use 2019-10-01 21:42:35 +02:00
Alejandro Celaya
8da6b336f5 Added API test which checks short URLs with a domain are parsed as such 2019-10-01 20:24:11 +02:00
Alejandro Celaya
d0bb86ca8f Added simple way to resolve domains from entity manager when creating a short URL 2019-10-01 20:16:27 +02:00
Alejandro Celaya
1085809fa5 Moved code to convert a ShortUrl into a full link as string to the entity itself 2019-09-30 20:01:36 +02:00
Alejandro Celaya
7b1857dcda Added entities config for domains 2019-09-30 19:42:27 +02:00
Alejandro Celaya
6f38790d47 Created migration which adds domains table 2019-09-30 19:15:14 +02:00
Alejandro Celaya
a81ac85af6 Merge pull request #498 from acelaya-forks/feature/improved-issue-templates
Feature/improved issue templates
2019-09-29 09:38:53 +02:00
Alejandro Celaya
8f4d5b6fce Added funding config file 2019-09-29 09:30:18 +02:00
Alejandro Celaya
8468a48eaa Added generic issue template 2019-09-29 09:23:37 +02:00
Alejandro Celaya
fc0885e5d5 Removed shlink set-up info from feature request issue template 2019-09-29 09:21:56 +02:00
Alejandro Celaya
e1a9e347c3 Merge pull request #496 from acelaya-forks/feature/installer2
Feature/installer2
2019-09-28 09:45:09 +02:00
Alejandro Celaya
1b0e3b686d Ignored false positive in phpstan 2019-09-28 09:35:59 +02:00
Alejandro Celaya
a09208582e Updated changelog 2019-09-28 09:31:41 +02:00
Alejandro Celaya
df1de020d1 Updated to shlink installer 2 2019-09-28 09:30:20 +02:00
Alejandro Celaya
9b363368a2 Merge pull request #490 from acelaya-forks/feature/issue-template
Feature/issue template
2019-09-17 20:17:51 +02:00
Alejandro Celaya
9fac69675a Updated how shlink info is requested to users on issue templates 2019-09-17 20:10:27 +02:00
Alejandro Celaya
1d2cfde7f7 Created individual issue templates 2019-09-17 20:04:04 +02:00
Alejandro Celaya
452612ee00 Merge pull request #485 from acelaya-forks/feature/base-path
Feature/base path
2019-09-13 20:54:45 +02:00
Alejandro Celaya
8d74e0c3ff Fixed undefined-index errors in BasePathPrefixerTest 2019-09-13 20:46:49 +02:00
Alejandro Celaya
0a1786c89a Added support for basepath on docker image 2019-09-13 20:36:40 +02:00
Alejandro Celaya
bc07d77d06 Removed duplicated code from BasePathPrefixer 2019-09-13 20:22:41 +02:00
Alejandro Celaya
6e38457655 Created BasePathPrefixerTest 2019-09-13 20:17:30 +02:00
Alejandro Celaya
d7a3aeb0a2 Created a config prost-processor which adds the base path on every applicable configuration 2019-09-13 20:03:53 +02:00
Alejandro Celaya
76541d5563 Merge pull request #484 from acelaya-forks/feature/disable-asking-for-lang
Ensured installer does not ask for the locale
2019-09-13 19:02:34 +02:00
Alejandro Celaya
28b5d8445e Ensured installer does not ask for the locale 2019-09-13 18:51:51 +02:00
Alejandro Celaya
d17533fd0f Merge pull request #483 from acelaya-forks/feature/remoive-translations
Feature/remoive translations
2019-09-12 20:30:45 +02:00
Alejandro Celaya
01d62b7aea Removed escape characters no longer needed in templates 2019-09-12 19:45:24 +02:00
Alejandro Celaya
bd97804ca6 Updated changelog 2019-09-12 08:10:41 +02:00
Alejandro Celaya
7b0ccc9f69 Removed references to anything related with translations 2019-09-12 08:09:17 +02:00
Alejandro Celaya
fdb98fa2a9 Added service aliases that were removed from shlink-common 2019-09-11 20:25:04 +02:00
355 changed files with 2878 additions and 909 deletions

View File

@@ -3,11 +3,6 @@ APP_ENV=
SECRET_KEY=
SHORTENED_URL_SCHEMA=
SHORTENED_URL_HOSTNAME=
SHORTCODE_CHARS=
# Language
DEFAULT_LOCALE=
CLI_LOCALE=
# Database
DB_USER=

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
github: ['acelaya']
custom: ['https://acel.me/donate']

View File

@@ -2,5 +2,5 @@
Before opening an issue, just take into account that this is a completely free of charge open source project.
I'm always happy to help and provide support, but some understanding will be required.
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
Try to be polite, and understand it is impossible for a project to cover all use cases.
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
-->

37
.github/ISSUE_TEMPLATE/Bug.md vendored Normal file
View File

@@ -0,0 +1,37 @@
---
name: Bug report
about: Something on shlink is broken or not working as documented?
labels: bug
---
<!--
Before opening an issue, just take into account that this is a completely free of charge open source project.
I'm always happy to help and provide support, but some understanding will be required.
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
-->
#### How Shlink is set-up
* 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
* Database engine used: MySQL|MariaDB|PostgreSQL|SQLite (x.y.z)
#### Summary
<!-- Provide a summary describing the problem you are experiencing. -->
#### Current behavior
<!-- How is it actually behaving (and it shouldn't)? -->
#### Expected behavior
<!-- How did you expected to behave? -->
#### How to reproduce
<!-- Provide steps to reproduce the bug. -->

View File

@@ -0,0 +1,18 @@
---
name: Feature request
about: Do you find shlink is missing some important feature that would make it more useful?
labels: feature
---
<!--
Before opening an issue, just take into account that this is a completely free of charge open source project.
I'm always happy to help and provide support, but some understanding will be required.
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
-->
#### Summary
<!-- Describe the new feature you would like to request. -->

View File

@@ -0,0 +1,25 @@
---
name: Question - Support
about: Do you have a problem setting up or using shlink?
labels: question
---
<!--
Before opening an issue, just take into account that this is a completely free of charge open source project.
I'm always happy to help and provide support, but some understanding will be required.
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
-->
#### How Shlink is set-up
* 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
* Database engine used: MySQL|MariaDB|PostgreSQL|SQLite (x.y.z)
#### Summary
<!-- Describe the issue you are facing here. -->

View File

@@ -18,6 +18,10 @@ services:
- postgresql
- docker
cache:
directories:
- $HOME/.composer/cache/files
before_install:
- echo 'extension = apcu.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- yes | pecl install swoole
@@ -25,7 +29,7 @@ before_install:
install:
- composer self-update
- composer install --no-interaction
- composer install --no-interaction --prefer-dist
before_script:
- mysql -e 'CREATE DATABASE shlink_test;'

View File

@@ -4,6 +4,144 @@ 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).
## 1.20.2 - 2019-12-06
#### Added
* *Nothing*
#### Changed
* *Nothing*
#### Deprecated
* *Nothing*
#### Removed
* *Nothing*
#### Fixed
* [#561](https://github.com/shlinkio/shlink/issues/561) Fixed `db:migrate` command failing because yaml extension is not installed, which makes config file not to be readable.
* [#562](https://github.com/shlinkio/shlink/issues/562) Fixed internal server error being returned when renaming a tag to another tag's name. Now a meaningful API error with status 409 is returned.
* [#555](https://github.com/shlinkio/shlink/issues/555) Fixed internal server error being returned when invalid dates are provided for new short URLs. Now a 400 is returned, as intended.
## 1.20.1 - 2019-11-17
#### Added
* [#519](https://github.com/shlinkio/shlink/issues/519) Documented how to customize web workers and task workers for the docker image.
#### Changed
* *Nothing*
#### Deprecated
* *Nothing*
#### Removed
* *Nothing*
#### Fixed
* [#512](https://github.com/shlinkio/shlink/issues/512) Fixed query params not being properly forwarded from short URL to long one.
* [#540](https://github.com/shlinkio/shlink/issues/540) Fixed errors thrown when creating short URLs if the original URL has an internationalized domain name and URL validation is enabled.
* [#528](https://github.com/shlinkio/shlink/issues/528) Ensured `db:create` and `db:migrate` commands do not silently fail when run as part of `install` or `update`.
* [#518](https://github.com/shlinkio/shlink/issues/518) Fixed service which updates Geolite db file to use a local lock instead of a shared one, since every shlink instance holds its own db instance.
## 1.20.0 - 2019-11-02
#### Added
* [#491](https://github.com/shlinkio/shlink/issues/491) Added improved short code generation logic.
Now, short codes are truly random, which removes the guessability factor existing in previous versions.
Generated short codes have 5 characters, and shlink makes sure they keep unique, while making it backwards-compatible.
* [#418](https://github.com/shlinkio/shlink/issues/418) and [#419](https://github.com/shlinkio/shlink/issues/419) Added support to redirect any 404 error to a custom URL.
It was already possible to configure this but only for invalid short URLs. Shlink now also support configuring redirects for the base URL and any other kind of "not found" error.
The three URLs can be different, and it is already possible to pass them to the docker image via configuration or env vars.
The installer also asks for these two new configuration options.
* [#497](https://github.com/shlinkio/shlink/issues/497) Officially added support for MariaDB.
#### Changed
* [#458](https://github.com/shlinkio/shlink/issues/458) Updated coding styles to use [shlinkio/php-coding-standard](https://github.com/shlinkio/php-coding-standard) v2.0.0.
#### Deprecated
* *Nothing*
#### Removed
* *Nothing*
#### Fixed
* [#507](https://github.com/shlinkio/shlink/issues/507) Fixed error with too long original URLs by increasing size to the maximum value (2048) based on [the standard](https://stackoverflow.com/a/417184).
* [#502](https://github.com/shlinkio/shlink/issues/502) Fixed error when providing the port as part of the domain on short URLs.
* [#509](https://github.com/shlinkio/shlink/issues/509) Fixed error when trying to generate a QR code for a short URL which uses a custom domain.
* [#522](https://github.com/shlinkio/shlink/issues/522) Highly mitigated errors thrown when lots of short URLs are created concurrently including new and existing tags.
## 1.19.0 - 2019-10-05
#### Added
* [#482](https://github.com/shlinkio/shlink/issues/482) Added support to serve shlink under a sub path.
The `router.base_path` config option can be defined now to set the base path from which shlink is served.
```php
return [
'router' => [
'base_path' => '/foo/bar',
],
];
```
This option will also be available on shlink-installer 1.3.0, so the installer will ask for it. It can also be provided for the docker image as the `BASE_PATH` env var.
* [#479](https://github.com/shlinkio/shlink/issues/479) Added preliminary support for multiple domains.
Endpoints and commands which create short URLs support providing the `domain` now (via query param or CLI flag). If not provided, the short URLs will still be "attached" to the default domain.
Custom slugs can be created on multiple domains, allowing to share links like `https://doma.in/my-compaign` and `https://example.com/my-campaign`, under the same shlink instance.
When resolving a short URL to redirect end users, the following rules are applied:
* If the domain used for the request plus the short code/slug are found, the user is redirected to that long URL and the visit is tracked.
* If the domain is not known but the short code/slug is defined for default domain, the user is redirected there and the visit is tracked.
* In any other case, no redirection happens and no visit is tracked (if a fall back redirection is configured for not-found URLs, it will still happen).
#### Changed
* [#486](https://github.com/shlinkio/shlink/issues/486) Updated to [shlink-installer](https://github.com/shlinkio/shlink-installer) v2, which supports asking for base path in which shlink is served.
#### Deprecated
* *Nothing*
#### Removed
* [#435](https://github.com/shlinkio/shlink/issues/435) Removed translations for error pages. All error pages are in english now.
#### Fixed
* *Nothing*
## 1.18.1 - 2019-08-24
#### Added

View File

@@ -1,10 +1,10 @@
FROM php:7.3.8-cli-alpine3.10
FROM php:7.3.11-alpine3.10
LABEL maintainer="Alejandro Celaya <alejandro@alejandrocelaya.com>"
ARG SHLINK_VERSION=1.18.1
ARG SHLINK_VERSION=1.20.0
ENV SHLINK_VERSION ${SHLINK_VERSION}
ENV SWOOLE_VERSION 4.3.3
ENV COMPOSER_VERSION 1.9.0
ENV SWOOLE_VERSION 4.4.12
ENV COMPOSER_VERSION 1.9.1
WORKDIR /etc/shlink
@@ -17,7 +17,7 @@ RUN \
# Install postgres
apk add --no-cache postgresql-dev && \
docker-php-ext-install -j"$(nproc)" pdo_pgsql && \
# [Deprecated] Install intl
# Install intl
apk add --no-cache icu-dev && \
docker-php-ext-install -j"$(nproc)" intl && \
# Install zip and gd

View File

@@ -22,7 +22,7 @@ A PHP-based self-hosted URL shortener that can be used to serve shortened URLs u
First make sure the host where you are going to run shlink fulfills these requirements:
* PHP 7.2 or greater with JSON, APCu, intl, curl, PDO and gd extensions enabled.
* MySQL, PostgreSQL or SQLite.
* MySQL, MariaDB, PostgreSQL or SQLite.
* The web server of your choice with PHP integration (Apache or Nginx recommended).
Then, you will need a built version of the project. There are a few ways to get it.
@@ -49,7 +49,7 @@ Then, you will need a built version of the project. There are a few ways to get
Despite how you built the project, you are going to need to install it now, by following these steps:
* If you are going to use MySQL or PostgreSQL, create an empty database with the name of your choice.
* If you are going to use MySQL, MariaDB or PostgreSQL, 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.**
* Expose shlink to the web, either by using a traditional web server + fast CGI approach, or by using a [swoole](https://www.swoole.co.uk/) non-blocking server.
@@ -103,8 +103,6 @@ Despite how you built the project, you are going to need to install it now, by f
* **Using swoole:**
**Important!** Swoole support is still experimental. Use it with care, and report any found issue.
First you need to install the swoole PHP extension with [pecl](https://pecl.php.net/package/swoole), `pecl install swoole`.
Once installed, it's actually pretty easy to get shlink up and running with swoole. Just run `./vendor/bin/zend-expressive-swoole start -d` and you will get shlink running on port 8080.

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env sh
set -e
export APP_ENV=test
export DB_DRIVER=mysql
# Try to stop server just in case it hanged in last execution
vendor/bin/zend-expressive-swoole stop
@@ -11,4 +10,9 @@ vendor/bin/zend-expressive-swoole start -d
sleep 2
vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --colors=always
testsExitCode=$?
vendor/bin/zend-expressive-swoole stop
# Exit this script with the same code as the tests. If tests failed, this script has to fail
exit $testsExitCode

View File

@@ -23,7 +23,7 @@
"doctrine/dbal": "^2.9",
"doctrine/migrations": "^2.0",
"doctrine/orm": "^2.5",
"endroid/qr-code": "^1.7",
"endroid/qr-code": "^3.6",
"firebase/php-jwt": "^4.0",
"geoip2/geoip2": "^2.9",
"guzzlehttp/guzzle": "^6.3",
@@ -33,10 +33,11 @@
"ocramius/proxy-manager": "~2.2.2",
"phly/phly-event-dispatcher": "^1.0",
"predis/predis": "^1.1",
"shlinkio/shlink-common": "^1.0",
"pugx/shortid-php": "^0.5",
"shlinkio/shlink-common": "^2.2.1",
"shlinkio/shlink-event-dispatcher": "^1.0",
"shlinkio/shlink-installer": "^1.2.1",
"shlinkio/shlink-ip-geolocation": "^1.0",
"shlinkio/shlink-installer": "^3.1",
"shlinkio/shlink-ip-geolocation": "^1.1",
"symfony/console": "^4.3",
"symfony/filesystem": "^4.3",
"symfony/lock": "^4.3",
@@ -50,7 +51,6 @@
"zendframework/zend-expressive-helpers": "^5.3",
"zendframework/zend-expressive-platesrenderer": "^2.1",
"zendframework/zend-expressive-swoole": "^2.4",
"zendframework/zend-i18n": "^2.9",
"zendframework/zend-inputfilter": "^2.10",
"zendframework/zend-paginator": "^2.8",
"zendframework/zend-servicemanager": "^3.4",
@@ -60,12 +60,12 @@
"devster/ubench": "^2.0",
"eaglewu/swoole-ide-helper": "dev-master",
"filp/whoops": "^2.4",
"infection/infection": "^0.13.4",
"phpstan/phpstan": "^0.11.2",
"infection/infection": "^0.14.2",
"phpstan/phpstan": "^0.11.16",
"phpunit/phpcov": "^6.0",
"phpunit/phpunit": "^8.3",
"roave/security-advisories": "dev-master",
"shlinkio/php-coding-standard": "~1.2.2",
"shlinkio/php-coding-standard": "~2.0.0",
"shlinkio/shlink-test-utils": "^1.0",
"symfony/dotenv": "^4.3",
"symfony/var-dumper": "^4.3",
@@ -78,7 +78,10 @@
"Shlinkio\\Shlink\\Rest\\": "module/Rest/src",
"Shlinkio\\Shlink\\Core\\": "module/Core/src",
"Shlinkio\\Shlink\\PreviewGenerator\\": "module/PreviewGenerator/src/"
}
},
"files": [
"module/Core/functions/functions.php"
]
},
"autoload-dev": {
"psr-4": {
@@ -99,11 +102,9 @@
"@test:ci",
"@infect:ci"
],
"cs": "phpcs",
"cs:fix": "phpcbf",
"stan": "phpstan analyse module/*/src/ module/*/config config docker/config --level=5 -c phpstan.neon",
"test": [
"@test:unit",
"@test:db",
@@ -111,37 +112,42 @@
],
"test:ci": [
"@test:unit:ci",
"@test:db",
"@test:db:ci",
"@test:api"
],
"test:unit": "phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --testdox",
"test:unit:ci": "phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --coverage-clover=build/clover.xml --coverage-xml=build/coverage-xml --log-junit=build/phpunit.junit.xml --testdox",
"test:db": [
"@test:db:sqlite",
"@test:db:mysql",
"@test:db:maria",
"@test:db:postgres"
],
"test:db:ci": [
"@test:db:sqlite",
"@test:db:mysql",
"@test:db:postgres"
],
"test:db:sqlite": "APP_ENV=test phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always -c phpunit-db.xml --coverage-php build/coverage-db.cov --testdox",
"test:db:mysql": "DB_DRIVER=mysql composer test:db:sqlite",
"test:db:maria": "DB_DRIVER=maria composer test:db:sqlite",
"test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite",
"test:api": "bin/test/run-api-tests.sh",
"test:pretty": [
"@test",
"phpdbg -qrr vendor/bin/phpcov merge build --html build/html"
],
"test:unit:pretty": "phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always --coverage-html build/coverage",
"infect": "infection --threads=4 --min-msi=75 --log-verbosity=default --only-covered",
"infect:ci": "infection --threads=4 --min-msi=75 --log-verbosity=default --only-covered --coverage=build",
"infect:show": "infection --threads=4 --min-msi=75 --log-verbosity=default --only-covered --show-mutations",
"infect:test": [
"@test:unit:ci",
"@infect:ci"
]
],
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
},
"scripts-descriptions": {
"check": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"test\" and \"infect\"</>",
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"test:ci\" and \"infect:ci\"</>",
"cs": "<fg=blue;options=bold>Checks coding styles</>",
"cs:fix": "<fg=blue;options=bold>Fixes coding styles, when possible</>",
@@ -150,9 +156,11 @@
"test:ci": "<fg=blue;options=bold>Runs all test suites, generating all needed reports and logs for CI envs</>",
"test:unit": "<fg=blue;options=bold>Runs unit test suites</>",
"test:unit:ci": "<fg=blue;options=bold>Runs unit test suites, generating all needed reports and logs for CI envs</>",
"test:db": "<fg=blue;options=bold>Runs database test suites on a SQLite, MySQL and PostgreSQL</>",
"test:db": "<fg=blue;options=bold>Runs database test suites on a SQLite, MySQL, MariaDB and PostgreSQL</>",
"test:db:ci": "<fg=blue;options=bold>Runs database test suites on a SQLite, MySQL and PostgreSQL</>",
"test:db:sqlite": "<fg=blue;options=bold>Runs database test suites on a SQLite database</>",
"test:db:mysql": "<fg=blue;options=bold>Runs database test suites on a MySQL database</>",
"test:db:maria": "<fg=blue;options=bold>Runs database test suites on a MariaDB database</>",
"test:db:postgres": "<fg=blue;options=bold>Runs database test suites on a PostgreSQL database</>",
"test:api": "<fg=blue;options=bold>Runs API test suites</>",
"test:pretty": "<fg=blue;options=bold>Runs all test suites and generates an HTML code coverage report</>",
@@ -160,7 +168,8 @@
"infect": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing</>",
"infect:ci": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing with existing reports and logs</>",
"infect:show": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing and shows applied mutators</>",
"infect:test": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing</>"
"infect:test": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing</>",
"clean:dev": "<fg=blue;options=bold>Deletes artifacts which are gitignored and could affect dev env</>"
},
"config": {
"sort-packages": true

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
use function Shlinkio\Shlink\Common\env;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
return [

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
use Zend\ConfigAggregator\ConfigAggregator;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
use Zend\Expressive;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Common;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
return [

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
use Shlinkio\Shlink\Installer\Config\Plugin;
@@ -6,17 +7,10 @@ use Shlinkio\Shlink\Installer\Config\Plugin;
return [
'installer_plugins_expected_config' => [
Plugin\LanguageConfigCustomizer::class => [
Plugin\LanguageConfigCustomizer::DEFAULT_LANG,
],
Plugin\UrlShortenerConfigCustomizer::class => [
Plugin\UrlShortenerConfigCustomizer::SCHEMA,
Plugin\UrlShortenerConfigCustomizer::HOSTNAME,
Plugin\UrlShortenerConfigCustomizer::CHARS,
Plugin\UrlShortenerConfigCustomizer::VALIDATE_URL,
Plugin\UrlShortenerConfigCustomizer::ENABLE_NOT_FOUND_REDIRECTION,
Plugin\UrlShortenerConfigCustomizer::NOT_FOUND_REDIRECT_TO,
],
Plugin\ApplicationConfigCustomizer::class => [
@@ -24,6 +18,9 @@ return [
Plugin\ApplicationConfigCustomizer::DISABLE_TRACK_PARAM,
Plugin\ApplicationConfigCustomizer::CHECK_VISITS_THRESHOLD,
Plugin\ApplicationConfigCustomizer::VISITS_THRESHOLD,
Plugin\ApplicationConfigCustomizer::BASE_PATH,
Plugin\ApplicationConfigCustomizer::WEB_WORKER_NUM,
Plugin\ApplicationConfigCustomizer::TASK_WORKER_NUM,
],
Plugin\DatabaseConfigCustomizer::class => [
@@ -34,6 +31,12 @@ return [
Plugin\DatabaseConfigCustomizer::HOST,
Plugin\DatabaseConfigCustomizer::PORT,
],
Plugin\RedirectsConfigCustomizer::class => [
Plugin\RedirectsConfigCustomizer::INVALID_SHORT_URL_REDIRECT_TO,
Plugin\RedirectsConfigCustomizer::REGULAR_404_REDIRECT_TO,
Plugin\RedirectsConfigCustomizer::BASE_URL_REDIRECT_TO,
],
],
'installation_commands' => [

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
use Shlinkio\Shlink\Common\Cache\RedisFactory;
@@ -7,6 +8,10 @@ use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
use Symfony\Component\Lock;
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
// This class alias tricks the ConfigAbstractFactory to return Lock\Factory instances even with a different service name
$localLockFactory = 'Shlinkio\Shlink\LocalLockFactory';
class_alias(Lock\Factory::class, $localLockFactory);
return [
'locks' => [
@@ -18,11 +23,14 @@ return [
Lock\Store\FlockStore::class => ConfigAbstractFactory::class,
Lock\Store\RedisStore::class => ConfigAbstractFactory::class,
Lock\Factory::class => ConfigAbstractFactory::class,
$localLockFactory => ConfigAbstractFactory::class,
],
'aliases' => [
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
'lock_store' => Lock\Store\FlockStore::class,
'lock_store' => 'local_lock_store',
'redis_lock_store' => Lock\Store\RedisStore::class,
'local_lock_store' => Lock\Store\FlockStore::class,
],
'delegators' => [
Lock\Store\RedisStore::class => [
@@ -38,6 +46,7 @@ return [
Lock\Store\FlockStore::class => ['config.locks.locks_dir'],
Lock\Store\RedisStore::class => [RedisFactory::SERVICE_NAME],
Lock\Factory::class => ['lock_store'],
$localLockFactory => ['local_lock_store'],
],
];

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink;
@@ -7,6 +8,7 @@ use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Monolog\Processor;
use Psr\Log\LoggerInterface;
use const PHP_EOL;
@@ -61,6 +63,11 @@ return [
'Logger_Shlink' => Common\Logger\LoggerFactory::class,
'Logger_Access' => Common\Logger\LoggerFactory::class,
],
'aliases' => [
'logger' => 'Logger_Shlink',
Logger::class => 'Logger_Shlink',
LoggerInterface::class => 'Logger_Shlink',
],
],
'zend-expressive-swoole' => [

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink;
@@ -47,9 +48,6 @@ return [
'post-routing' => [
'middleware' => [
Expressive\Router\Middleware\DispatchMiddleware::class,
// Only if a not found error is triggered, set-up the locale to be used
Common\Middleware\LocaleMiddleware::class,
Core\Response\NotFoundHandler::class,
],
'priority' => 1,

View File

@@ -1,7 +1,8 @@
<?php
declare(strict_types=1);
/** @deprecated */
/* @deprecated */
return [
'preview_generation' => [

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
return [
'not_found_redirects' => [
'invalid_short_url' => null, // Formerly url_shortener.not_found_short_url.redirect_to
'regular_404' => null,
'base_url' => null,
],
];

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
use Zend\Expressive\Router\FastRouteRouter;
@@ -6,6 +7,8 @@ use Zend\Expressive\Router\FastRouteRouter;
return [
'router' => [
'base_path' => '',
'fastroute' => [
FastRouteRouter::CONFIG_CACHE_ENABLED => true,
FastRouteRouter::CONFIG_CACHE_FILE => 'data/cache/fastroute_cached_routes.php',

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
return [

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
return [

View File

@@ -1,12 +0,0 @@
<?php
declare(strict_types=1);
use Shlinkio\Shlink\Common;
return [
'translator' => [
'locale' => Common\env('DEFAULT_LOCALE', 'en'),
],
];

View File

@@ -1,7 +1,6 @@
<?php
declare(strict_types=1);
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
declare(strict_types=1);
use function Shlinkio\Shlink\Common\env;
@@ -12,12 +11,7 @@ return [
'schema' => env('SHORTENED_URL_SCHEMA', 'http'),
'hostname' => env('SHORTENED_URL_HOSTNAME'),
],
'shortcode_chars' => env('SHORTCODE_CHARS', UrlShortenerOptions::DEFAULT_CHARS),
'validate_url' => true,
'not_found_short_url' => [
'enable_redirection' => false,
'redirect_to' => null,
],
],
];

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
return [

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
use Doctrine\ORM\EntityManager;
@@ -6,8 +7,10 @@ use Doctrine\ORM\Tools\Console\ConsoleRunner;
use Psr\Container\ContainerInterface;
use Zend\ServiceManager\ServiceManager;
/** @var ContainerInterface|ServiceManager $container */
$container = include __DIR__ . '/container.php';
$em = $container->get(EntityManager::class);
return (function () {
/** @var ContainerInterface|ServiceManager $container */
$container = include __DIR__ . '/container.php';
$em = $container->get(EntityManager::class);
return ConsoleRunner::createHelperSet($em);
return ConsoleRunner::createHelperSet($em);
})();

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink;
@@ -28,5 +29,7 @@ return (new ConfigAggregator\ConfigAggregator([
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
: new ConfigAggregator\ZendConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'),
], 'data/cache/app_config.php', [
Core\SimplifiedConfigParser::class,
Core\Config\SimplifiedConfigParser::class,
Core\Config\BasePathPrefixer::class,
Core\Config\DeprecatedConfigParser::class,
]))->getMergedConfig();

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
use Symfony\Component\Dotenv\Dotenv;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\TestUtils;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\TestUtils;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink;
@@ -15,40 +16,41 @@ use function sys_get_temp_dir;
$swooleTestingHost = '127.0.0.1';
$swooleTestingPort = 9999;
$buildDbConnection = function () {
$buildDbConnection = function (): array {
$driver = env('DB_DRIVER', 'sqlite');
$isCi = env('TRAVIS', false);
$getMysqlHost = function (string $driver) {
return sprintf('shlink_db%s', $driver === 'mysql' ? '' : '_maria');
};
switch ($driver) {
case 'sqlite':
return [
'driver' => 'pdo_sqlite',
'path' => sys_get_temp_dir() . '/shlink-tests.db',
];
case 'mysql':
return [
'driver' => 'pdo_mysql',
'host' => $isCi ? '127.0.0.1' : 'shlink_db',
'user' => 'root',
'password' => $isCi ? '' : 'root',
'dbname' => 'shlink_test',
'charset' => 'utf8',
'driverOptions' => [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
],
];
case 'postgres':
return [
'driver' => 'pdo_pgsql',
'host' => $isCi ? '127.0.0.1' : 'shlink_db_postgres',
'user' => 'postgres',
'password' => $isCi ? '' : 'root',
'dbname' => 'shlink_test',
'charset' => 'utf8',
];
default:
return [];
}
$driverConfigMap = [
'sqlite' => [
'driver' => 'pdo_sqlite',
'path' => sys_get_temp_dir() . '/shlink-tests.db',
],
'mysql' => [
'driver' => 'pdo_mysql',
'host' => $isCi ? '127.0.0.1' : $getMysqlHost($driver),
'user' => 'root',
'password' => $isCi ? '' : 'root',
'dbname' => 'shlink_test',
'charset' => 'utf8',
'driverOptions' => [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
],
],
'postgres' => [
'driver' => 'pdo_pgsql',
'host' => $isCi ? '127.0.0.1' : 'shlink_db_postgres',
'user' => 'postgres',
'password' => $isCi ? '' : 'root',
'dbname' => 'shlink_test',
'charset' => 'utf8',
],
];
$driverConfigMap['maria'] = $driverConfigMap['mysql'];
return $driverConfigMap[$driver] ?? [];
};
return [
@@ -64,6 +66,7 @@ return [
],
'zend-expressive-swoole' => [
'enable_coroutine' => false,
'swoole-http-server' => [
'host' => $swooleTestingHost,
'port' => $swooleTestingPort,
@@ -72,6 +75,7 @@ return [
'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid',
'worker_num' => 1,
'task_worker_num' => 1,
'enable_coroutine' => false,
],
],
],

2
data/infra/database_maria/.gitignore vendored Executable file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

View File

@@ -1,9 +1,9 @@
FROM php:7.3.1-fpm-alpine3.8
FROM php:7.3.11-fpm-alpine3.10
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
ENV APCU_VERSION 5.1.16
ENV APCU_BC_VERSION 1.0.4
ENV XDEBUG_VERSION "2.7.0RC1"
ENV APCU_VERSION 5.1.18
ENV APCU_BC_VERSION 1.0.5
ENV XDEBUG_VERSION 2.8.0
RUN apk update
@@ -13,17 +13,17 @@ RUN docker-php-ext-install iconv
RUN docker-php-ext-install mbstring
RUN docker-php-ext-install calendar
RUN apk add --no-cache --virtual sqlite-libs
RUN apk add --no-cache --virtual sqlite-dev
RUN apk add --no-cache sqlite-libs
RUN apk add --no-cache sqlite-dev
RUN docker-php-ext-install pdo_sqlite
RUN apk add --no-cache --virtual icu-dev
RUN apk add --no-cache icu-dev
RUN docker-php-ext-install intl
RUN apk add --no-cache --virtual libzip-dev zlib-dev
RUN apk add --no-cache libzip-dev zlib-dev
RUN docker-php-ext-install zip
RUN apk add --no-cache --virtual libpng-dev
RUN apk add --no-cache libpng-dev
RUN docker-php-ext-install gd
RUN apk add --no-cache postgresql-dev

View File

@@ -1,9 +1,10 @@
FROM php:7.3.1-cli-alpine3.8
FROM php:7.3.11-alpine3.10
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
ENV APCU_VERSION 5.1.16
ENV APCU_BC_VERSION 1.0.4
ENV APCU_VERSION 5.1.18
ENV APCU_BC_VERSION 1.0.5
ENV INOTIFY_VERSION 2.0.0
ENV SWOOLE_VERSION 4.4.12
RUN apk update
@@ -13,17 +14,17 @@ RUN docker-php-ext-install iconv
RUN docker-php-ext-install mbstring
RUN docker-php-ext-install calendar
RUN apk add --no-cache --virtual sqlite-libs
RUN apk add --no-cache --virtual sqlite-dev
RUN apk add --no-cache sqlite-libs
RUN apk add --no-cache sqlite-dev
RUN docker-php-ext-install pdo_sqlite
RUN apk add --no-cache --virtual icu-dev
RUN apk add --no-cache icu-dev
RUN docker-php-ext-install intl
RUN apk add --no-cache --virtual libzip-dev zlib-dev
RUN apk add --no-cache libzip-dev zlib-dev
RUN docker-php-ext-install zip
RUN apk add --no-cache --virtual libpng-dev
RUN apk add --no-cache libpng-dev
RUN docker-php-ext-install gd
RUN apk add --no-cache postgresql-dev
@@ -66,7 +67,7 @@ RUN rm /tmp/inotify.tar.gz
# Install swoole
# First line fixes an error when installing pecl extensions. Found in https://github.com/docker-library/php/issues/233
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS && \
pecl install swoole && \
pecl install swoole-${SWOOLE_VERSION} && \
docker-php-ext-enable swoole && \
apk del .phpize-deps

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\DBAL\Types\Type;
use Doctrine\Migrations\AbstractMigration;
final class Version20190930165521 extends AbstractMigration
{
/**
* @throws SchemaException
*/
public function up(Schema $schema): void
{
$shortUrls = $schema->getTable('short_urls');
if ($shortUrls->hasColumn('domain_id')) {
return;
}
$domains = $schema->createTable('domains');
$domains->addColumn('id', Type::BIGINT, [
'unsigned' => true,
'autoincrement' => true,
'notnull' => true,
]);
$domains->addColumn('authority', Type::STRING, [
'length' => 512,
'notnull' => true,
]);
$domains->addUniqueIndex(['authority']);
$domains->setPrimaryKey(['id']);
$shortUrls->addColumn('domain_id', Type::BIGINT, [
'unsigned' => true,
'notnull' => false,
]);
$shortUrls->addForeignKeyConstraint('domains', ['domain_id'], ['id'], [
'onDelete' => 'RESTRICT',
'onUpdate' => 'RESTRICT',
]);
}
/**
* @throws SchemaException
*/
public function down(Schema $schema): void
{
$schema->getTable('short_urls')->dropColumn('domain_id');
$schema->dropTable('domains');
}
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\Migrations\AbstractMigration;
use function array_reduce;
final class Version20191001201532 extends AbstractMigration
{
/**
* @throws SchemaException
*/
public function up(Schema $schema): void
{
$shortUrls = $schema->getTable('short_urls');
if ($shortUrls->hasIndex('unique_short_code_plus_domain')) {
return;
}
/** @var Index|null $shortCodesIndex */
$shortCodesIndex = array_reduce($shortUrls->getIndexes(), function (?Index $found, Index $current) {
[$column] = $current->getColumns();
return $column === 'short_code' ? $current : $found;
});
if ($shortCodesIndex === null) {
return;
}
$shortUrls->dropIndex($shortCodesIndex->getName());
$shortUrls->addUniqueIndex(['short_code', 'domain_id'], 'unique_short_code_plus_domain');
}
/**
* @throws SchemaException
*/
public function down(Schema $schema): void
{
$shortUrls = $schema->getTable('short_urls');
$shortUrls->dropIndex('unique_short_code_plus_domain');
$shortUrls->addUniqueIndex(['short_code']);
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace ShlinkMigrations;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\Migrations\AbstractMigration;
final class Version20191020074522 extends AbstractMigration
{
/**
* @throws SchemaException
*/
public function up(Schema $schema): void
{
$this->getOriginalUrlColumn($schema)->setLength(2048);
}
/**
* @throws SchemaException
*/
public function down(Schema $schema): void
{
$this->getOriginalUrlColumn($schema)->setLength(1024);
}
/**
* @throws SchemaException
*/
private function getOriginalUrlColumn(Schema $schema): Column
{
return $schema->getTable('short_urls')->getColumn('original_url');
}
}

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace <namespace>;

View File

@@ -24,3 +24,9 @@ services:
volumes:
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro
shlink_db_maria:
user: 1000:1000
volumes:
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro

View File

@@ -24,6 +24,7 @@ services:
links:
- shlink_db
- shlink_db_postgres
- shlink_db_maria
- shlink_redis
shlink_swoole:
@@ -33,11 +34,13 @@ services:
dockerfile: ./data/infra/swoole.Dockerfile
ports:
- "8080:8080"
- "9001:9001"
volumes:
- ./:/home/shlink
links:
- shlink_db
- shlink_db_postgres
- shlink_db_maria
- shlink_redis
shlink_db:
@@ -65,6 +68,19 @@ services:
POSTGRES_DB: shlink
PGDATA: /var/lib/postgresql/data/pgdata
shlink_db_maria:
container_name: shlink_db_maria
image: mariadb:10.2
ports:
- "3308:3306"
volumes:
- ./:/home/shlink/www
- ./data/infra/database_maria:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: shlink
MYSQL_INITDB_SKIP_TZINFO: 1
shlink_redis:
container_name: shlink_redis
image: redis:5.0-alpine

View File

@@ -56,16 +56,16 @@ docker exec -it shlink_container shlink
The image comes with a working sqlite database, but in production you will probably want to usa a distributed database.
It is possible to use a set of env vars to make this shlink instance interact with an external MySQL or PostgreSQL database.
It is possible to use a set of env vars to make this shlink instance interact with an external MySQL, MariaDB or PostgreSQL database.
* `DB_DRIVER`: **[Mandatory]**. Use the value **mysql** or **postgres** to prevent the sqlite database to be used.
* `DB_DRIVER`: **[Mandatory]**. Use the value **mysql**, **maria** or **postgres** to prevent the sqlite database to be used.
* `DB_NAME`: [Optional]. The database name to be used. Defaults to **shlink**.
* `DB_USER`: **[Mandatory]**. The username credential for the database server.
* `DB_PASSWORD`: **[Mandatory]**. The password credential for the database server.
* `DB_HOST`: **[Mandatory]**. The host name of the server running the database engine.
* `DB_PORT`: [Optional]. The port in which the database service is running.
* Default value is based on the driver:
* **mysql** -> `3306`
* Default value is based on the value provided for `DB_DRIVER`:
* **mysql** or **maria** -> `3306`
* **postgres** -> `5432`
> PostgreSQL is supported since v1.16.1 of this image. Do not try to use it with previous versions.
@@ -92,18 +92,24 @@ This is the complete list of supported env vars:
* `SHORT_DOMAIN_HOST`: The custom short domain used for this shlink instance. For example **doma.in**.
* `SHORT_DOMAIN_SCHEMA`: Either **http** or **https**.
* `SHORTCODE_CHARS`: A charset to use when building short codes. Only needed when using more than one shlink instance ([Multi instance considerations](#multi-instance-considerations)).
* `DB_DRIVER`: **sqlite** (which is the default value), **mysql** or **postgres**.
* `DB_DRIVER`: **sqlite** (which is the default value), **mysql**, **maria** or **postgres**.
* `DB_NAME`: The database name to be used when using an external database driver. Defaults to **shlink**.
* `DB_USER`: The username credential to be used when using an external database driver.
* `DB_PASSWORD`: The password credential to be used when using an external database driver.
* `DB_HOST`: The host name of the database server when using an external database driver.
* `DB_PORT`: The port in which the database service is running when using an external database driver. Defaults to **3306**.
* `DB_PORT`: The port in which the database service is running when using an external database driver.
* Default value is based on the value provided for `DB_DRIVER`:
* **mysql** or **maria** -> `3306`
* **postgres** -> `5432`
* `DISABLE_TRACK_PARAM`: The name of a query param that can be used to visit short URLs avoiding the visit to be tracked. This feature won't be available if not value is provided.
* `DELETE_SHORT_URL_THRESHOLD`: The amount of visits on short URLs which will not allow them to be deleted. Defaults to `15`.
* `LOCALE`: Defines the default language for error pages when a user accesses a short URL which does not exist. Supported values are **es** and **en**. Defaults to **en**.
* `VALIDATE_URLS`: Boolean which tells if shlink should validate a status 20x (after following redirects) is returned when trying to shorten a URL. Defaults to `true`.
* `NOT_FOUND_REDIRECT_TO`: If a URL is provided here, when a user tries to access an invalid short URL, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page.
* `INVALID_SHORT_URL_REDIRECT_TO`: If a URL is provided here, when a user tries to access an invalid short URL, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page.
* `REGULAR_404_REDIRECT_TO`: If a URL is provided here, when a user tries to access a URL not matching any one supported by the router, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page.
* `BASE_URL_REDIRECT_TO`: If a URL is provided here, when a user tries to access Shlink's base URL, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page.
* `BASE_PATH`: The base path from which you plan to serve shlink, in case you don't want to serve it from the root of the domain. Defaults to `''`.
* `WEB_WORKER_NUM`: The amount of concurrent http requests this shlink instance will be able to server. Defaults to 16.
* `TASK_WORKER_NUM`: The amount of concurrent background tasks this shlink instance will be able to execute. Defaults to 16.
* `REDIS_SERVERS`: A comma-separated list of redis servers where Shlink locks are stored (locks are used to prevent some operations to be run more than once in parallel).
This is important when running more than one Shlink instance ([Multi instance considerations](#multi-instance-considerations)). If not provided, Shlink stores locks on every instance separately.
@@ -112,6 +118,9 @@ This is the complete list of supported env vars:
In the future, these redis servers could be used for other caching operations performed by shlink.
* `NOT_FOUND_REDIRECT_TO`: **Deprecated since v1.20 in favor of `INVALID_SHORT_URL_REDIRECT_TO`** If a URL is provided here, when a user tries to access an invalid short URL, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page.
* `SHORTCODE_CHARS`: **Ignored when using Shlink 1.20 or newer**. A charset to use when building short codes. Only needed when using more than one shlink instance ([Multi instance considerations](#multi-instance-considerations)).
An example using all env vars could look like this:
```bash
@@ -128,10 +137,14 @@ docker run \
-e DB_PORT=3306 \
-e DISABLE_TRACK_PARAM="no-track" \
-e DELETE_SHORT_URL_THRESHOLD=30 \
-e LOCALE=es \
-e VALIDATE_URLS=false \
-e "NOT_FOUND_REDIRECT_TO=https://www.google.com" \
-e "INVALID_SHORT_URL_REDIRECT_TO=https://my-landing-page.com" \
-e "REGULAR_404_REDIRECT_TO=https://my-landing-page.com" \
-e "BASE_URL_REDIRECT_TO=https://my-landing-page.com" \
-e "REDIS_SERVERS=tcp://172.20.0.1:6379,tcp://172.20.0.2:6379" \
-e "BASE_PATH=/my-campaign" \
-e WEB_WORKER_NUM=64 \
-e TASK_WORKER_NUM=32 \
shlinkio/shlink
```
@@ -147,11 +160,15 @@ The whole configuration should have this format, but it can be split into multip
{
"disable_track_param": "my_param",
"delete_short_url_threshold": 30,
"locale": "es",
"short_domain_schema": "https",
"short_domain_host": "doma.in",
"validate_url": false,
"not_found_redirect_to": "https://my-landing-page.com",
"invalid_short_url_redirect_to": "https://my-landing-page.com",
"regular_404_redirect_to": "https://my-landing-page.com",
"base_url_redirect_to": "https://my-landing-page.com",
"base_path": "/my-campaign",
"web_worker_num": 64,
"task_worker_num": 32,
"redis_servers": [
"tcp://172.20.0.1:6379",
"tcp://172.20.0.2:6379"
@@ -163,12 +180,15 @@ The whole configuration should have this format, but it can be split into multip
"password": "123abc",
"host": "something.rds.amazonaws.com",
"port": "3306"
}
},
"not_found_redirect_to": "https://my-landing-page.com"
}
```
> This is internally parsed to how shlink expects the config. If you are using a version previous to 1.17.0, this parser is not present and you need to provide a config structure like the one [documented previously](https://github.com/shlinkio/shlink-docker-image/tree/v1.16.3#provide-config-via-volumes).
> The `not_found_redirect_to` option has been deprecated in v1.20. Use `invalid_short_url_redirect_to` instead (however, it will still work for backwards compatibility).
Once created just run shlink with the volume:
```bash
@@ -179,7 +199,13 @@ docker run --name shlink -p 8080:8080 -v ${PWD}/my/config/dir:/etc/shlink/config
These are some considerations to take into account when running multiple instances of shlink.
* The first time shlink is run, it generates a charset used to generate short codes, which is a shuffled base62 charset.
* Some operations performed by Shlink should never be run more than once at the same time (like creating the database for the first time, or downloading the GeoLite2 database). For this reason, Shlink uses a locking system.
However, these locks are locally scoped to each Shlink instance by default.
You can (and should) make the locks to be shared by all Shlink instances by using a redis server/cluster. Just define the `REDIS_SERVERS` env var with the list of servers.
* **Ignore this if using Shlink 1.20 or newer**. The first time shlink is run, it generates a charset used to generate short codes, which is a shuffled base62 charset.
If you are using several shlink instances, you will probably want all of them to use the same charset.
@@ -187,18 +213,12 @@ These are some considerations to take into account when running multiple instanc
If you don't do this, each shlink instance will use a different charset. However this shouldn't be a problem in practice, since the chances to get a collision will be very low.
* Some operations performed by Shlink should never be run more than once at the same time (like creating the database for the first time, or downloading the GeoLite2 database). For this reason, Shlink uses a locking system.
However, these locks are locally scoped to each Shlink instance by default.
You can (and should) make the locks to be shared by all Shlink instances by using a redis server/cluster. Just define the `REDIS_SERVERS` env var with the list of servers.
## Versions
Versions of this image match the shlink version it contains.
Versioning on this docker image works as follows:
For example, installing `shlinkio/shlink:1.15.0`, you will get an image containing shlink v1.15.0.
* `X.X.X`: when providing a specific version number, the image version will match the shlink version it contains. For example, installing `shlinkio/shlink:1.15.0`, you will get an image containing shlink v1.15.0.
* `stable`: always holds the latest stable tag. For example, if latest shlink version is 1.20.0, installing `shlinkio/shlink:stable`, you will get an image containing shlink v1.20.0
* `latest`: always holds the latest contents in master, and it's considered unstable and not suitable for production.
The `latest` docker tag always holds the latest contents in master, and it's considered unestable and not suitable for production.
> There are no official shlink images previous to v1.15.0.
> **Important**: The docker image was introduced with shlink v1.15.0, so there are no official images previous to that versions.

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink;
@@ -10,6 +11,7 @@ use function explode;
use function file_exists;
use function file_get_contents;
use function file_put_contents;
use function Functional\contains;
use function implode;
use function Shlinkio\Shlink\Common\env;
use function sprintf;
@@ -21,24 +23,24 @@ $helper = new class {
private const BASE62 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
private const DB_DRIVERS_MAP = [
'mysql' => 'pdo_mysql',
'maria' => 'pdo_mysql',
'postgres' => 'pdo_pgsql',
];
private const DB_PORTS_MAP = [
'mysql' => '3306',
'maria' => '3306',
'postgres' => '5432',
];
/** @var string */
private $charset;
/** @var string */
private $secretKey;
public function __construct()
{
[$this->charset, $this->secretKey] = $this->initShlinkKeys();
[, $this->secretKey] = $this->initShlinkSecretKey();
}
private function initShlinkKeys(): array
private function initShlinkSecretKey(): array
{
$keysFile = sprintf('%s/shlink.keys', sys_get_temp_dir());
if (file_exists($keysFile)) {
@@ -46,29 +48,19 @@ $helper = new class {
}
$keys = [
env('SHORTCODE_CHARS', $this->generateShortcodeChars()),
env('SECRET_KEY', $this->generateSecretKey()),
'', // This was the SHORTCODE_CHARS. Kept as empty string for BC
env('SECRET_KEY', $this->generateSecretKey()), // Deprecated
];
file_put_contents($keysFile, implode(',', $keys));
return $keys;
}
private function generateShortcodeChars(): string
{
return str_shuffle(self::BASE62);
}
private function generateSecretKey(): string
{
return substr(str_shuffle(self::BASE62), 0, 32);
}
public function getShortcodeChars(): string
{
return $this->charset;
}
public function getSecretKey(): string
{
return $this->secretKey;
@@ -84,8 +76,8 @@ $helper = new class {
];
}
$driverOptions = $driver !== 'mysql' ? [] : [
// PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
$driverOptions = ! contains(['maria', 'mysql'], $driver) ? [] : [
// 1002 -> PDO::MYSQL_ATTR_INIT_COMMAND
1002 => 'SET NAMES utf8',
];
return [
@@ -99,13 +91,12 @@ $helper = new class {
];
}
public function getNotFoundConfig(): array
public function getNotFoundRedirectsConfig(): array
{
$notFoundRedirectTo = env('NOT_FOUND_REDIRECT_TO');
return [
'enable_redirection' => $notFoundRedirectTo !== null,
'redirect_to' => $notFoundRedirectTo,
'invalid_short_url' => env('INVALID_SHORT_URL_REDIRECT_TO', env('NOT_FOUND_REDIRECT_TO')),
'regular_404' => env('REGULAR_404_REDIRECT_TO'),
'base_url' => env('BASE_URL_REDIRECT_TO'),
];
}
};
@@ -124,10 +115,6 @@ return [
'visits_threshold' => (int) env('DELETE_SHORT_URL_THRESHOLD', 15),
],
'translator' => [
'locale' => env('LOCALE', 'en'),
],
'entity_manager' => [
'connection' => $helper->getDbConfig(),
],
@@ -137,11 +124,11 @@ return [
'schema' => env('SHORT_DOMAIN_SCHEMA', 'http'),
'hostname' => env('SHORT_DOMAIN_HOST', ''),
],
'shortcode_chars' => $helper->getShortcodeChars(),
'validate_url' => (bool) env('VALIDATE_URLS', true),
'not_found_short_url' => $helper->getNotFoundConfig(),
],
'not_found_redirects' => $helper->getNotFoundRedirectsConfig(),
'logger' => [
'handlers' => [
'shlink_rotating_handler' => [
@@ -172,4 +159,17 @@ return [
'servers' => env('REDIS_SERVERS'),
],
'router' => [
'base_path' => env('BASE_PATH', ''),
],
'zend-expressive-swoole' => [
'swoole-http-server' => [
'options' => [
'worker_num' => (int) env('WEB_WORKER_NUM', 16),
'task_worker_num' => (int) env('TASK_WORKER_NUM', 16),
],
],
],
];

View File

@@ -216,6 +216,10 @@
"findIfExists": {
"description": "Will force existing matching URL to be returned if found, instead of creating a new one",
"type": "boolean"
},
"domain": {
"description": "The domain to which the short URL will be attached",
"type": "string"
}
}
}

View File

@@ -15,6 +15,15 @@
"schema": {
"type": "string"
}
},
{
"name": "domain",
"in": "query",
"description": "The domain in which the short code should be searched for. Will fall back to default domain if not found.",
"required": false,
"schema": {
"type": "string"
}
}
],
"security": [

11
migrations.php Normal file
View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
return [
'name' => 'ShlinkMigrations',
'migrations_namespace' => 'ShlinkMigrations',
'table_name' => 'migrations',
'migrations_directory' => 'data/migrations',
'custom_template' => 'data/migrations_template.txt',
];

View File

@@ -1,5 +0,0 @@
name: ShlinkMigrations
migrations_namespace: ShlinkMigrations
table_name: migrations
migrations_directory: data/migrations
custom_template: data/migrations_template.txt

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI;
@@ -57,7 +58,7 @@ return [
],
ConfigAbstractFactory::class => [
GeolocationDbUpdater::class => [DbUpdater::class, Reader::class, Locker::class],
GeolocationDbUpdater::class => [DbUpdater::class, Reader::class, 'Shlinkio\Shlink\LocalLockFactory'],
Command\ShortUrl\GenerateShortUrlCommand::class => [Service\UrlShortener::class, 'config.url_shortener.domain'],
Command\ShortUrl\ResolveUrlCommand::class => [Service\UrlShortener::class],

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Api;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Api;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Api;

View File

@@ -1,10 +1,10 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Config;
use Shlinkio\Shlink\CLI\Util\ExitCodes;
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -17,6 +17,7 @@ use function str_shuffle;
class GenerateCharsetCommand extends Command
{
public const NAME = 'config:generate-charset';
private const DEFAULT_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
protected function configure(): void
{
@@ -25,14 +26,14 @@ class GenerateCharsetCommand extends Command
->setDescription(sprintf(
'[DEPRECATED] Generates a character set sample just by shuffling the default one, "%s". '
. 'Then it can be set in the SHORTCODE_CHARS environment variable',
UrlShortenerOptions::DEFAULT_CHARS
self::DEFAULT_CHARS
))
->setHelp('<fg=red;options=bold>This command is deprecated. Better leave shlink generate the charset.</>');
}
protected function execute(InputInterface $input, OutputInterface $output): ?int
{
$charSet = str_shuffle(UrlShortenerOptions::DEFAULT_CHARS);
$charSet = str_shuffle(self::DEFAULT_CHARS);
(new SymfonyStyle($input, $output))->success(sprintf('Character set: "%s"', $charSet));
return ExitCodes::EXIT_SUCCESS;
}

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Config;

View File

@@ -1,9 +1,11 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Db;
use Shlinkio\Shlink\CLI\Command\Util\AbstractLockedCommand;
use Shlinkio\Shlink\CLI\Command\Util\LockedCommandConfig;
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Lock\Factory as Locker;
@@ -28,6 +30,11 @@ abstract class AbstractDatabaseCommand extends AbstractLockedCommand
protected function runPhpCommand(OutputInterface $output, array $command): void
{
array_unshift($command, $this->phpBinary);
$this->processHelper->run($output, $command, null, null, $output->getVerbosity());
$this->processHelper->mustRun($output, $command);
}
protected function getLockConfig(): LockedCommandConfig
{
return new LockedCommandConfig($this->getName(), true);
}
}

View File

@@ -1,10 +1,10 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Db;
use Doctrine\DBAL\Connection;
use Shlinkio\Shlink\CLI\Command\Util\LockedCommandConfig;
use Shlinkio\Shlink\CLI\Util\ExitCodes;
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Console\Input\InputInterface;
@@ -18,8 +18,8 @@ use function Functional\contains;
class CreateDatabaseCommand extends AbstractDatabaseCommand
{
public const NAME = 'db:create';
public const DOCTRINE_HELPER_SCRIPT = 'vendor/doctrine/orm/bin/doctrine.php';
public const DOCTRINE_HELPER_COMMAND = 'orm:schema-tool:create';
public const DOCTRINE_SCRIPT = 'vendor/doctrine/orm/bin/doctrine.php';
public const DOCTRINE_CREATE_SCHEMA_COMMAND = 'orm:schema-tool:create';
/** @var Connection */
private $regularConn;
@@ -60,7 +60,7 @@ class CreateDatabaseCommand extends AbstractDatabaseCommand
// Create database
$io->writeln('<fg=blue>Creating database tables...</>');
$this->runPhpCommand($output, [self::DOCTRINE_HELPER_SCRIPT, self::DOCTRINE_HELPER_COMMAND]);
$this->runPhpCommand($output, [self::DOCTRINE_SCRIPT, self::DOCTRINE_CREATE_SCHEMA_COMMAND]);
$io->success('Database properly created!');
return ExitCodes::EXIT_SUCCESS;
@@ -86,13 +86,8 @@ class CreateDatabaseCommand extends AbstractDatabaseCommand
private function schemaExists(): bool
{
// If at least one of the shlink tables exist, we will consider the database exists somehow.
// Any inconsistency will be taken care by the migrations
// Any inconsistency should be taken care by the migrations
$schemaManager = $this->regularConn->getSchemaManager();
return ! empty($schemaManager->listTableNames());
}
protected function getLockConfig(): LockedCommandConfig
{
return new LockedCommandConfig($this->getName(), true);
}
}

View File

@@ -1,9 +1,9 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Db;
use Shlinkio\Shlink\CLI\Command\Util\LockedCommandConfig;
use Shlinkio\Shlink\CLI\Util\ExitCodes;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -12,8 +12,8 @@ use Symfony\Component\Console\Style\SymfonyStyle;
class MigrateDatabaseCommand extends AbstractDatabaseCommand
{
public const NAME = 'db:migrate';
public const DOCTRINE_HELPER_SCRIPT = 'vendor/doctrine/migrations/bin/doctrine-migrations.php';
public const DOCTRINE_HELPER_COMMAND = 'migrations:migrate';
public const DOCTRINE_MIGRATIONS_SCRIPT = 'vendor/doctrine/migrations/bin/doctrine-migrations.php';
public const DOCTRINE_MIGRATE_COMMAND = 'migrations:migrate';
protected function configure(): void
{
@@ -27,14 +27,9 @@ class MigrateDatabaseCommand extends AbstractDatabaseCommand
$io = new SymfonyStyle($input, $output);
$io->writeln('<fg=blue>Migrating database...</>');
$this->runPhpCommand($output, [self::DOCTRINE_HELPER_SCRIPT, self::DOCTRINE_HELPER_COMMAND]);
$this->runPhpCommand($output, [self::DOCTRINE_MIGRATIONS_SCRIPT, self::DOCTRINE_MIGRATE_COMMAND]);
$io->success('Database properly migrated!');
return ExitCodes::EXIT_SUCCESS;
}
protected function getLockConfig(): LockedCommandConfig
{
return new LockedCommandConfig($this->getName(), true);
}
}

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;

View File

@@ -1,15 +1,14 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
use Cake\Chronos\Chronos;
use Shlinkio\Shlink\CLI\Util\ExitCodes;
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
use Shlinkio\Shlink\Core\Util\ShortUrlBuilderTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -26,8 +25,6 @@ use function sprintf;
class GenerateShortUrlCommand extends Command
{
use ShortUrlBuilderTrait;
public const NAME = 'short-url:generate';
private const ALIASES = ['shortcode:generate', 'short-code:generate'];
@@ -87,6 +84,12 @@ class GenerateShortUrlCommand extends Command
'f',
InputOption::VALUE_NONE,
'This will force existing matching URL to be returned if found, instead of creating a new one.'
)
->addOption(
'domain',
'd',
InputOption::VALUE_REQUIRED,
'The domain to which this short URL will be attached.'
);
}
@@ -98,7 +101,7 @@ class GenerateShortUrlCommand extends Command
return;
}
$longUrl = $io->ask('A long URL was not provided. Which URL do you want to be shortened?');
$longUrl = $io->ask('Which URL do you want to shorten?');
if (! empty($longUrl)) {
$input->setArgument('longUrl', $longUrl);
}
@@ -119,22 +122,22 @@ class GenerateShortUrlCommand extends Command
$maxVisits = $input->getOption('maxVisits');
try {
$shortCode = $this->urlShortener->urlToShortCode(
$shortUrl = $this->urlShortener->urlToShortCode(
new Uri($longUrl),
$tags,
ShortUrlMeta::createFromParams(
$this->getOptionalDate($input, 'validSince'),
$this->getOptionalDate($input, 'validUntil'),
$input->getOption('validSince'),
$input->getOption('validUntil'),
$customSlug,
$maxVisits !== null ? (int) $maxVisits : null,
$input->getOption('findIfExists')
$input->getOption('findIfExists'),
$input->getOption('domain')
)
)->getShortCode();
$shortUrl = $this->buildShortUrl($this->domainConfig, $shortCode);
);
$io->writeln([
sprintf('Processed long URL: <info>%s</info>', $longUrl),
sprintf('Generated short URL: <info>%s</info>', $shortUrl),
sprintf('Generated short URL: <info>%s</info>', $shortUrl->toString($this->domainConfig)),
]);
return ExitCodes::EXIT_SUCCESS;
} catch (InvalidUrlException $e) {
@@ -147,10 +150,4 @@ class GenerateShortUrlCommand extends Command
return ExitCodes::EXIT_FAILURE;
}
}
private function getOptionalDate(InputInterface $input, string $fieldName): ?Chronos
{
$since = $input->getOption($fieldName);
return $since !== null ? Chronos::parse($since) : null;
}
}

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
@@ -61,26 +62,26 @@ class ListShortUrlsCommand extends Command
->addOption(
'page',
'p',
InputOption::VALUE_OPTIONAL,
InputOption::VALUE_REQUIRED,
sprintf('The first page to list (%s items per page)', ShortUrlRepositoryAdapter::ITEMS_PER_PAGE),
'1'
)
->addOption(
'searchTerm',
's',
InputOption::VALUE_OPTIONAL,
InputOption::VALUE_REQUIRED,
'A query used to filter results by searching for it on the longUrl and shortCode fields'
)
->addOption(
'tags',
't',
InputOption::VALUE_OPTIONAL,
InputOption::VALUE_REQUIRED,
'A comma-separated list of tags to filter results'
)
->addOption(
'orderBy',
'o',
InputOption::VALUE_OPTIONAL,
InputOption::VALUE_REQUIRED,
'The field from which we want to order by. Pass ASC or DESC separated by a comma'
)
->addOption('showTags', null, InputOption::VALUE_NONE, 'Whether to display the tags or not');

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
@@ -10,6 +11,7 @@ use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -35,7 +37,8 @@ class ResolveUrlCommand extends Command
->setName(self::NAME)
->setAliases(self::ALIASES)
->setDescription('Returns the long URL behind a short code')
->addArgument('shortCode', InputArgument::REQUIRED, 'The short code to parse');
->addArgument('shortCode', InputArgument::REQUIRED, 'The short code to parse')
->addOption('domain', 'd', InputOption::VALUE_REQUIRED, 'The domain to which the short URL is attached.');
}
protected function interact(InputInterface $input, OutputInterface $output): void
@@ -56,9 +59,10 @@ class ResolveUrlCommand extends Command
{
$io = new SymfonyStyle($input, $output);
$shortCode = $input->getArgument('shortCode');
$domain = $input->getOption('domain');
try {
$url = $this->urlShortener->shortCodeToUrl($shortCode);
$url = $this->urlShortener->shortCodeToUrl($shortCode, $domain);
$output->writeln(sprintf('Long URL: <info>%s</info>', $url->getLongUrl()));
return ExitCodes::EXIT_SUCCESS;
} catch (InvalidShortCodeException $e) {

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Tag;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Tag;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Tag;

View File

@@ -1,10 +1,12 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Tag;
use Shlinkio\Shlink\CLI\Util\ExitCodes;
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
use Shlinkio\Shlink\Core\Exception\TagConflictException;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@@ -49,6 +51,11 @@ class RenameTagCommand extends Command
} catch (EntityDoesNotExistException $e) {
$io->error(sprintf('A tag with name "%s" was not found', $oldName));
return ExitCodes::EXIT_FAILURE;
} catch (TagConflictException $e) {
$io->error(
sprintf('A tag with name "%s" cannot be renamed to "%s" because it already exists', $oldName, $newName)
);
return ExitCodes::EXIT_FAILURE;
}
}
}

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Util;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Util;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Visit;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Visit;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Exception;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Exception;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Factory;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Util;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Util;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Util;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Util;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\CLI\Command\Api;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\CLI\Command\Api;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\CLI\Command\Api;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\CLI\Command\Config;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\CLI\Command\Db;
@@ -115,10 +116,10 @@ class CreateDatabaseCommandTest extends TestCase
$createDatabase = $this->schemaManager->createDatabase($shlinkDatabase)->will(function () {
});
$listTables = $this->schemaManager->listTableNames()->willReturn([]);
$runCommand = $this->processHelper->run(Argument::type(OutputInterface::class), [
$runCommand = $this->processHelper->mustRun(Argument::type(OutputInterface::class), [
'/usr/local/bin/php',
CreateDatabaseCommand::DOCTRINE_HELPER_SCRIPT,
CreateDatabaseCommand::DOCTRINE_HELPER_COMMAND,
CreateDatabaseCommand::DOCTRINE_SCRIPT,
CreateDatabaseCommand::DOCTRINE_CREATE_SCHEMA_COMMAND,
], Argument::cetera());
$this->commandTester->execute([]);

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