mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-06 23:33:13 +08:00
Compare commits
143 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8465094c1 | ||
|
|
16f7359ac6 | ||
|
|
f9f4817ee2 | ||
|
|
c7e49f223f | ||
|
|
6e79b4ba7b | ||
|
|
f78a7f12a9 | ||
|
|
b3664597b0 | ||
|
|
8cfb4f61ca | ||
|
|
b0dbb2dae4 | ||
|
|
7c6da4985d | ||
|
|
386b0dfb7b | ||
|
|
1437ff48ce | ||
|
|
63294f20ee | ||
|
|
d8acc3c247 | ||
|
|
52d8ffa212 | ||
|
|
98ad2816e8 | ||
|
|
9d890f4227 | ||
|
|
0932d04907 | ||
|
|
1f78b5c524 | ||
|
|
59f10619ba | ||
|
|
334710e92c | ||
|
|
75b8175824 | ||
|
|
8a74ef2a33 | ||
|
|
d05ac5ce9d | ||
|
|
3100fffa2b | ||
|
|
6bbacb1017 | ||
|
|
4403dc5df9 | ||
|
|
fdc637c23d | ||
|
|
b99d662417 | ||
|
|
eb9a964c66 | ||
|
|
e5ef8d7f8c | ||
|
|
28650aee2b | ||
|
|
a2294704e6 | ||
|
|
e5e1aa2ff4 | ||
|
|
2f5290b9d3 | ||
|
|
ef3c4aadf2 | ||
|
|
c9ce56eea5 | ||
|
|
4fee656f96 | ||
|
|
d2a04259f5 | ||
|
|
e504daa1ba | ||
|
|
8793a67ce9 | ||
|
|
b4ded374e9 | ||
|
|
91d350b12f | ||
|
|
b3e25f28fd | ||
|
|
aca89f9abe | ||
|
|
243075dd78 | ||
|
|
7130425896 | ||
|
|
fe9ab20cbb | ||
|
|
6935b2ebe2 | ||
|
|
3dcc510da1 | ||
|
|
2f26c82fa6 | ||
|
|
9ddb60a882 | ||
|
|
210b08b61f | ||
|
|
42fe4bd5ce | ||
|
|
1b2a0820e5 | ||
|
|
6cf0155417 | ||
|
|
9b8be3e5b8 | ||
|
|
a27b01b895 | ||
|
|
16dd1838aa | ||
|
|
f788d6872f | ||
|
|
d0df007812 | ||
|
|
f60c217fae | ||
|
|
d3fc7d543a | ||
|
|
4d0fc1da07 | ||
|
|
ee2233c6dd | ||
|
|
ea6e0d7c7f | ||
|
|
d9d599eab4 | ||
|
|
d1ba44e1b3 | ||
|
|
dff2ad3740 | ||
|
|
f7e63710e4 | ||
|
|
d3b5cd5c57 | ||
|
|
86ed83d25e | ||
|
|
f96d0fe30a | ||
|
|
be406bd676 | ||
|
|
044278752b | ||
|
|
343d2ab44a | ||
|
|
66992f644e | ||
|
|
cf245524dd | ||
|
|
ad520811a3 | ||
|
|
ee1e1d5688 | ||
|
|
8ef0e7c25b | ||
|
|
c3d555ef3c | ||
|
|
bf8e14708b | ||
|
|
6ea59b1e4d | ||
|
|
cf8b778711 | ||
|
|
1e79969c3b | ||
|
|
5fd34e03fc | ||
|
|
ce9d6642d4 | ||
|
|
ecebdbbfa8 | ||
|
|
6f7ce709ca | ||
|
|
84094a51a2 | ||
|
|
7ba9eb8e2c | ||
|
|
e8a0c5484c | ||
|
|
0521227127 | ||
|
|
fac9455a1e | ||
|
|
3243ade4fd | ||
|
|
da21eb4a5c | ||
|
|
5ec6d538db | ||
|
|
08228d9d98 | ||
|
|
7856d64299 | ||
|
|
057bbae729 | ||
|
|
09b161304c | ||
|
|
a60c45ca4d | ||
|
|
89ed84ce28 | ||
|
|
a6c547c4da | ||
|
|
3e2c5abaa4 | ||
|
|
c202b3e518 | ||
|
|
e15b67b5dc | ||
|
|
7ddc180487 | ||
|
|
f3fbfc3692 | ||
|
|
b289e3bac2 | ||
|
|
4d4aafa6db | ||
|
|
2705070063 | ||
|
|
5e3770c105 | ||
|
|
0f0213aa87 | ||
|
|
0e2ad0dbca | ||
|
|
d275316acd | ||
|
|
0a681f0efa | ||
|
|
b17f96043a | ||
|
|
6f9b727673 | ||
|
|
79427d08d7 | ||
|
|
2ec807ba70 | ||
|
|
ede4525332 | ||
|
|
4dffc9f0c1 | ||
|
|
5de845c258 | ||
|
|
745ff51150 | ||
|
|
88b9f9fc56 | ||
|
|
fdbe076bf2 | ||
|
|
0760550767 | ||
|
|
1b94083188 | ||
|
|
1993d01110 | ||
|
|
37fb7e76d9 | ||
|
|
cc3362837b | ||
|
|
2012cc453c | ||
|
|
ea80b6d48a | ||
|
|
db956a1f40 | ||
|
|
4f3995ea80 | ||
|
|
e024ba5d94 | ||
|
|
af0ff0f65b | ||
|
|
a9094dc0f6 | ||
|
|
aca90ef907 | ||
|
|
ddfccea901 | ||
|
|
6c6cfb4fc3 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -22,3 +22,4 @@ indocker export-ignore
|
|||||||
phpcs.xml export-ignore
|
phpcs.xml export-ignore
|
||||||
phpunit.xml.dist export-ignore
|
phpunit.xml.dist export-ignore
|
||||||
phpunit-func.xml export-ignore
|
phpunit-func.xml export-ignore
|
||||||
|
phpstan.neon
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ language: php
|
|||||||
|
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- /.*/
|
||||||
- develop
|
|
||||||
|
|
||||||
php:
|
php:
|
||||||
- 7
|
|
||||||
- 7.1
|
- 7.1
|
||||||
- 7.2
|
- 7.2
|
||||||
|
|
||||||
|
|||||||
91
CHANGELOG.md
91
CHANGELOG.md
@@ -1,8 +1,97 @@
|
|||||||
## CHANGELOG
|
## CHANGELOG
|
||||||
|
|
||||||
|
### 1.9.1
|
||||||
|
|
||||||
|
**Bugs:**
|
||||||
|
|
||||||
|
* [154: When filtering by searchTerm, sizes of every result page has an unexpected behavior](https://github.com/shlinkio/shlink/issues/154)
|
||||||
|
* [157: Background commands executed by installation process do not respect the used php binary](https://github.com/shlinkio/shlink/issues/157)
|
||||||
|
|
||||||
|
**Enhancements:**
|
||||||
|
|
||||||
|
* [155: Improve the pagination object returned in lists, including more meaningful properties](https://github.com/shlinkio/shlink/issues/155)
|
||||||
|
|
||||||
|
### 1.9.0
|
||||||
|
|
||||||
|
**Features**
|
||||||
|
|
||||||
|
* [147: Allow short URLs to be created on the fly with query param authentication](https://github.com/shlinkio/shlink/issues/147)
|
||||||
|
|
||||||
|
**Bugs:**
|
||||||
|
|
||||||
|
* [139: Make sure all core actions log exceptions](https://github.com/shlinkio/shlink/issues/139)
|
||||||
|
|
||||||
|
### 1.8.1
|
||||||
|
|
||||||
|
**Tasks**
|
||||||
|
|
||||||
|
* [141: Remove workaround used in PathVersionMiddleware](https://github.com/shlinkio/shlink/issues/141)
|
||||||
|
|
||||||
|
**Bugs:**
|
||||||
|
|
||||||
|
* [140: Installation failed. Warning thrown while trying to include doctrine script](https://github.com/shlinkio/shlink/issues/140)
|
||||||
|
|
||||||
|
### 1.8.0
|
||||||
|
|
||||||
|
**Features**
|
||||||
|
|
||||||
|
* [125: Implement a path which returns a 1px image instead of a redirection](https://github.com/shlinkio/shlink/issues/125)
|
||||||
|
|
||||||
|
**Enhancements:**
|
||||||
|
|
||||||
|
* [130: Update to Expressive 3](https://github.com/shlinkio/shlink/issues/130)
|
||||||
|
* [137: Update symfony packages to v4](https://github.com/shlinkio/shlink/issues/137)
|
||||||
|
|
||||||
|
**Tasks**
|
||||||
|
|
||||||
|
* [131: Drop support for PHP 7](https://github.com/shlinkio/shlink/issues/131)
|
||||||
|
* [132: Add infection to improve tests](https://github.com/shlinkio/shlink/issues/132)
|
||||||
|
|
||||||
|
### 1.7.2
|
||||||
|
|
||||||
|
**Bugs:**
|
||||||
|
|
||||||
|
* [135: Fix PathVersionMiddleware being ignored when using expressive 2.2](https://github.com/shlinkio/shlink/issues/135)
|
||||||
|
|
||||||
|
### 1.7.1
|
||||||
|
|
||||||
|
**Enhancements:**
|
||||||
|
|
||||||
|
* [128: Upgrade to expressive 2.2](https://github.com/shlinkio/shlink/issues/128)
|
||||||
|
|
||||||
|
**Bugs**
|
||||||
|
|
||||||
|
* [126: Expressive 2.2 causes failures by triggering E_USER_DEPRECATED errors](https://github.com/shlinkio/shlink/issues/126)
|
||||||
|
|
||||||
|
### 1.7.0
|
||||||
|
|
||||||
|
**Features**
|
||||||
|
|
||||||
|
* [88: Allow to disable tracking of the short URL by including a configurable query param](https://github.com/shlinkio/shlink/issues/88)
|
||||||
|
* [108: Allow to edit metadata in created shortcodes](https://github.com/shlinkio/shlink/issues/108)
|
||||||
|
|
||||||
|
**Enhancements:**
|
||||||
|
|
||||||
|
* [113: Update CLI commands to use SymfonyStyle](https://github.com/shlinkio/shlink/issues/113)
|
||||||
|
* [112: Configure cli commands lazy loading](https://github.com/shlinkio/shlink/issues/112)
|
||||||
|
|
||||||
|
**Tasks**
|
||||||
|
|
||||||
|
* [117: Make every module which throws exceptions have its own ExceptionInterface, and make them all extend Throwable](https://github.com/shlinkio/shlink/issues/117)
|
||||||
|
* [115: Add phpstan to build matrix on PHP >=7.1 envs](https://github.com/shlinkio/shlink/issues/115)
|
||||||
|
* [114: Replace vlucas/phpdotenv dev requirement by symfony/env](https://github.com/shlinkio/shlink/issues/114)
|
||||||
|
|
||||||
|
### 1.6.2
|
||||||
|
|
||||||
|
**Bugs**
|
||||||
|
|
||||||
|
* [109: Fix installation error due to typo in latest migration](https://github.com/shlinkio/shlink/issues/109)
|
||||||
|
|
||||||
### 1.6.1
|
### 1.6.1
|
||||||
|
|
||||||
* Added gitattributes file to avoid files not needed in production to be included in distribution
|
**Tasks**
|
||||||
|
|
||||||
|
* [110: Create gitattributes file to define files to be excluded from distributable package](https://github.com/shlinkio/shlink/issues/110)
|
||||||
|
|
||||||
### 1.6.0
|
### 1.6.0
|
||||||
|
|
||||||
|
|||||||
2
bin/cli
2
bin/cli
@@ -1,5 +1,7 @@
|
|||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Interop\Container\ContainerInterface;
|
use Interop\Container\ContainerInterface;
|
||||||
use Symfony\Component\Console\Application as CliApp;
|
use Symfony\Component\Console\Application as CliApp;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Shlinkio\Shlink\CLI\Factory\InstallApplicationFactory;
|
use Shlinkio\Shlink\CLI\Factory\InstallApplicationFactory;
|
||||||
use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizerPlugin;
|
use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizer;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||||
@@ -17,12 +17,11 @@ $container = new ServiceManager([
|
|||||||
'factories' => [
|
'factories' => [
|
||||||
Application::class => InstallApplicationFactory::class,
|
Application::class => InstallApplicationFactory::class,
|
||||||
Filesystem::class => InvokableFactory::class,
|
Filesystem::class => InvokableFactory::class,
|
||||||
QuestionHelper::class => InvokableFactory::class,
|
|
||||||
],
|
],
|
||||||
'services' => [
|
'services' => [
|
||||||
'config' => [
|
'config' => [
|
||||||
ConfigAbstractFactory::class => [
|
ConfigAbstractFactory::class => [
|
||||||
DatabaseConfigCustomizerPlugin::class => [QuestionHelper::class, Filesystem::class]
|
DatabaseConfigCustomizer::class => [Filesystem::class]
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Shlinkio\Shlink\CLI\Factory\InstallApplicationFactory;
|
use Shlinkio\Shlink\CLI\Factory\InstallApplicationFactory;
|
||||||
use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizerPlugin;
|
use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizer;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||||
@@ -17,12 +17,11 @@ $container = new ServiceManager([
|
|||||||
'factories' => [
|
'factories' => [
|
||||||
Application::class => InstallApplicationFactory::class,
|
Application::class => InstallApplicationFactory::class,
|
||||||
Filesystem::class => InvokableFactory::class,
|
Filesystem::class => InvokableFactory::class,
|
||||||
QuestionHelper::class => InvokableFactory::class,
|
|
||||||
],
|
],
|
||||||
'services' => [
|
'services' => [
|
||||||
'config' => [
|
'config' => [
|
||||||
ConfigAbstractFactory::class => [
|
ConfigAbstractFactory::class => [
|
||||||
DatabaseConfigCustomizerPlugin::class => [QuestionHelper::class, Filesystem::class]
|
DatabaseConfigCustomizer::class => [Filesystem::class]
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
4
build.sh
4
build.sh
@@ -33,8 +33,12 @@ rm composer.*
|
|||||||
rm LICENSE
|
rm LICENSE
|
||||||
rm indocker
|
rm indocker
|
||||||
rm docker-compose.yml
|
rm docker-compose.yml
|
||||||
|
rm docker-compose.override.yml
|
||||||
|
rm docker-compose.override.yml.dist
|
||||||
|
rm func_tests_bootstrap.php
|
||||||
rm php*
|
rm php*
|
||||||
rm README.md
|
rm README.md
|
||||||
|
rm infection.json
|
||||||
rm -rf build
|
rm -rf build
|
||||||
rm -ff data/database.sqlite
|
rm -ff data/database.sqlite
|
||||||
rm -rf data/infra
|
rm -rf data/infra
|
||||||
|
|||||||
@@ -12,48 +12,51 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.0",
|
"php": "^7.1",
|
||||||
"acelaya/ze-content-based-error-handler": "^2.0",
|
"acelaya/ze-content-based-error-handler": "^2.2",
|
||||||
"cocur/slugify": "^3.0",
|
"cocur/slugify": "^3.0",
|
||||||
"doctrine/annotations": "^1.4 <1.5",
|
"doctrine/annotations": "^1.4",
|
||||||
"doctrine/cache": "^1.6 <1.7",
|
"doctrine/cache": "^1.6",
|
||||||
"doctrine/collections": "^1.4 <1.5",
|
"doctrine/collections": "^1.4",
|
||||||
"doctrine/common": "^2.7 <2.8",
|
"doctrine/common": "^2.7",
|
||||||
"doctrine/dbal": "^2.5 <2.6",
|
"doctrine/dbal": "^2.5",
|
||||||
"doctrine/migrations": "^1.4",
|
"doctrine/migrations": "^1.4",
|
||||||
"doctrine/orm": "^2.5 <2.6",
|
"doctrine/orm": "^2.5",
|
||||||
"endroid/qrcode": "^1.7",
|
"endroid/qr-code": "^1.7",
|
||||||
"firebase/php-jwt": "^4.0",
|
"firebase/php-jwt": "^4.0",
|
||||||
"guzzlehttp/guzzle": "^6.2",
|
"guzzlehttp/guzzle": "^6.2",
|
||||||
"http-interop/http-middleware": "^0.4.1",
|
|
||||||
"mikehaertl/phpwkhtmltopdf": "^2.2",
|
"mikehaertl/phpwkhtmltopdf": "^2.2",
|
||||||
"monolog/monolog": "^1.21",
|
"monolog/monolog": "^1.21",
|
||||||
"roave/security-advisories": "dev-master",
|
"roave/security-advisories": "dev-master",
|
||||||
"symfony/console": "^3.0",
|
"symfony/console": "^4.0",
|
||||||
"symfony/filesystem": "^3.0",
|
"symfony/filesystem": "^4.0",
|
||||||
"symfony/process": "^3.0",
|
"symfony/process": "^4.0",
|
||||||
"theorchard/monolog-cascade": "^0.4",
|
"theorchard/monolog-cascade": "^0.4",
|
||||||
"zendframework/zend-config": "^3.0",
|
"zendframework/zend-config": "^3.0",
|
||||||
"zendframework/zend-config-aggregator": "^1.0",
|
"zendframework/zend-config-aggregator": "^1.0",
|
||||||
"zendframework/zend-expressive": "^2.0",
|
"zendframework/zend-diactoros": "^1.7",
|
||||||
"zendframework/zend-expressive-fastroute": "^2.0",
|
"zendframework/zend-expressive": "^3.0",
|
||||||
"zendframework/zend-expressive-helpers": "^4.2",
|
"zendframework/zend-expressive-fastroute": "^3.0",
|
||||||
"zendframework/zend-expressive-platesrenderer": "^1.3",
|
"zendframework/zend-expressive-helpers": "^5.0",
|
||||||
|
"zendframework/zend-expressive-platesrenderer": "^2.0",
|
||||||
"zendframework/zend-i18n": "^2.7",
|
"zendframework/zend-i18n": "^2.7",
|
||||||
|
"zendframework/zend-inputfilter": "^2.8",
|
||||||
"zendframework/zend-paginator": "^2.6",
|
"zendframework/zend-paginator": "^2.6",
|
||||||
"zendframework/zend-servicemanager": "^3.0",
|
"zendframework/zend-servicemanager": "^3.2",
|
||||||
"zendframework/zend-stdlib": "^3.0"
|
"zendframework/zend-stdlib": "^3.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"filp/whoops": "^2.0",
|
"filp/whoops": "^2.0",
|
||||||
"phpunit/dbunit": "^3.0",
|
"infection/infection": "^0.8.1",
|
||||||
"phpunit/phpcov": "^4.0",
|
"phpstan/phpstan": "0.9",
|
||||||
"phpunit/phpunit": "^6.0",
|
"phpunit/phpcov": "^5.0",
|
||||||
|
"phpunit/phpunit": "^7.0",
|
||||||
"slevomat/coding-standard": "^4.0",
|
"slevomat/coding-standard": "^4.0",
|
||||||
"squizlabs/php_codesniffer": "^3.1",
|
"squizlabs/php_codesniffer": "^3.1 <3.2",
|
||||||
"symfony/var-dumper": "^3.0",
|
"symfony/dotenv": "^4.0",
|
||||||
"vlucas/phpdotenv": "^2.2",
|
"symfony/var-dumper": "^4.0",
|
||||||
"zendframework/zend-expressive-tooling": "^0.4"
|
"zendframework/zend-component-installer": "^2.1",
|
||||||
|
"zendframework/zend-expressive-tooling": "^1.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
@@ -83,8 +86,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"check": [
|
"check": [
|
||||||
"@cs",
|
"@cs",
|
||||||
|
"@stan",
|
||||||
"@test",
|
"@test",
|
||||||
"@func-test"
|
"@func-test",
|
||||||
|
"@infect"
|
||||||
],
|
],
|
||||||
"cs": "phpcs",
|
"cs": "phpcs",
|
||||||
"cs-fix": "phpcbf",
|
"cs-fix": "phpcbf",
|
||||||
@@ -96,13 +101,17 @@
|
|||||||
"@test",
|
"@test",
|
||||||
"@func-test",
|
"@func-test",
|
||||||
"phpcov merge build --html build/html"
|
"phpcov merge build --html build/html"
|
||||||
]
|
],
|
||||||
|
"stan": "phpstan analyse module/*/src/ --level=6 -c phpstan.neon",
|
||||||
|
"infect": "infection --threads=4 --min-msi=65 --only-covered --log-verbosity=2",
|
||||||
|
"infect-show": "infection --threads=4 --min-msi=65 --only-covered --log-verbosity=2 --show-mutations",
|
||||||
|
"expressive": "expressive"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"process-timeout": 0,
|
"process-timeout": 0,
|
||||||
"sort-packages": true,
|
"sort-packages": true,
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "7.0"
|
"php": "7.1.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Shlinkio\Shlink\Common;
|
use function Shlinkio\Shlink\Common\env;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'app_options' => [
|
'app_options' => [
|
||||||
'name' => 'Shlink',
|
'name' => 'Shlink',
|
||||||
'version' => '1.2.0',
|
'version' => '1.7.0',
|
||||||
'secret_key' => Common\env('SECRET_KEY'),
|
'secret_key' => env('SECRET_KEY'),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -5,26 +5,24 @@ use Shlinkio\Shlink\Common\Factory\EmptyResponseImplicitOptionsMiddlewareFactory
|
|||||||
use Zend\Expressive;
|
use Zend\Expressive;
|
||||||
use Zend\Expressive\Container;
|
use Zend\Expressive\Container;
|
||||||
use Zend\Expressive\Helper;
|
use Zend\Expressive\Helper;
|
||||||
use Zend\Expressive\Middleware;
|
use Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware;
|
||||||
use Zend\Expressive\Plates;
|
|
||||||
use Zend\Expressive\Router;
|
|
||||||
use Zend\Expressive\Template;
|
|
||||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||||
use Zend\Stratigility\Middleware\ErrorHandler;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'dependencies' => [
|
'dependencies' => [
|
||||||
'factories' => [
|
'factories' => [
|
||||||
Expressive\Application::class => Container\ApplicationFactory::class,
|
ImplicitOptionsMiddleware::class => EmptyResponseImplicitOptionsMiddlewareFactory::class,
|
||||||
Template\TemplateRendererInterface::class => Plates\PlatesRendererFactory::class,
|
|
||||||
Router\RouterInterface::class => Router\FastRouteRouterFactory::class,
|
|
||||||
ErrorHandler::class => Container\ErrorHandlerFactory::class,
|
|
||||||
Middleware\ImplicitOptionsMiddleware::class => EmptyResponseImplicitOptionsMiddlewareFactory::class,
|
|
||||||
|
|
||||||
Helper\UrlHelper::class => Helper\UrlHelperFactory::class,
|
Helper\UrlHelper::class => Helper\UrlHelperFactory::class,
|
||||||
Helper\ServerUrlHelper::class => InvokableFactory::class,
|
Helper\ServerUrlHelper::class => InvokableFactory::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'delegators' => [
|
||||||
|
Expressive\Application::class => [
|
||||||
|
Container\ApplicationConfigInjectionDelegator::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Shlinkio\Shlink\Common\Middleware\LocaleMiddleware;
|
use Shlinkio\Shlink\Common\Middleware\LocaleMiddleware;
|
||||||
|
use Shlinkio\Shlink\Core\Response\NotFoundHandler;
|
||||||
use Shlinkio\Shlink\Rest\Middleware\BodyParserMiddleware;
|
use Shlinkio\Shlink\Rest\Middleware\BodyParserMiddleware;
|
||||||
use Shlinkio\Shlink\Rest\Middleware\CheckAuthenticationMiddleware;
|
use Shlinkio\Shlink\Rest\Middleware\CheckAuthenticationMiddleware;
|
||||||
use Shlinkio\Shlink\Rest\Middleware\CrossDomainMiddleware;
|
use Shlinkio\Shlink\Rest\Middleware\CrossDomainMiddleware;
|
||||||
@@ -15,6 +16,7 @@ return [
|
|||||||
'pre-routing' => [
|
'pre-routing' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
ErrorHandler::class,
|
ErrorHandler::class,
|
||||||
|
Expressive\Helper\ContentLengthMiddleware::class,
|
||||||
LocaleMiddleware::class,
|
LocaleMiddleware::class,
|
||||||
],
|
],
|
||||||
'priority' => 11,
|
'priority' => 11,
|
||||||
@@ -29,7 +31,7 @@ return [
|
|||||||
|
|
||||||
'routing' => [
|
'routing' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
Expressive\Application::ROUTING_MIDDLEWARE,
|
Expressive\Router\Middleware\RouteMiddleware::class,
|
||||||
],
|
],
|
||||||
'priority' => 10,
|
'priority' => 10,
|
||||||
],
|
],
|
||||||
@@ -38,7 +40,7 @@ return [
|
|||||||
'path' => '/rest',
|
'path' => '/rest',
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
CrossDomainMiddleware::class,
|
CrossDomainMiddleware::class,
|
||||||
Expressive\Middleware\ImplicitOptionsMiddleware::class,
|
Expressive\Router\Middleware\ImplicitOptionsMiddleware::class,
|
||||||
BodyParserMiddleware::class,
|
BodyParserMiddleware::class,
|
||||||
CheckAuthenticationMiddleware::class,
|
CheckAuthenticationMiddleware::class,
|
||||||
],
|
],
|
||||||
@@ -47,7 +49,8 @@ return [
|
|||||||
|
|
||||||
'post-routing' => [
|
'post-routing' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
Expressive\Application::DISPATCH_MIDDLEWARE,
|
Expressive\Router\Middleware\DispatchMiddleware::class,
|
||||||
|
NotFoundHandler::class,
|
||||||
],
|
],
|
||||||
'priority' => 1,
|
'priority' => 1,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use Shlinkio\Shlink\Common;
|
|||||||
use Shlinkio\Shlink\Core;
|
use Shlinkio\Shlink\Core;
|
||||||
use Shlinkio\Shlink\Rest;
|
use Shlinkio\Shlink\Rest;
|
||||||
use Zend\ConfigAggregator;
|
use Zend\ConfigAggregator;
|
||||||
|
use Zend\Expressive;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration files are loaded in a specific order. First ``global.php``, then ``*.global.php``.
|
* Configuration files are loaded in a specific order. First ``global.php``, then ``*.global.php``.
|
||||||
@@ -18,6 +19,11 @@ use Zend\ConfigAggregator;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
return (new ConfigAggregator\ConfigAggregator([
|
return (new ConfigAggregator\ConfigAggregator([
|
||||||
|
Expressive\ConfigProvider::class,
|
||||||
|
Expressive\Router\ConfigProvider::class,
|
||||||
|
Expressive\Router\FastRouteRouter\ConfigProvider::class,
|
||||||
|
Expressive\Plates\ConfigProvider::class,
|
||||||
|
Expressive\Helper\ConfigProvider::class,
|
||||||
ExpressiveErrorHandler\ConfigProvider::class,
|
ExpressiveErrorHandler\ConfigProvider::class,
|
||||||
Common\ConfigProvider::class,
|
Common\ConfigProvider::class,
|
||||||
Core\ConfigProvider::class,
|
Core\ConfigProvider::class,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Dotenv\Dotenv;
|
use Symfony\Component\Dotenv\Dotenv;
|
||||||
use Zend\ServiceManager\ServiceManager;
|
use Zend\ServiceManager\ServiceManager;
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
chdir(dirname(__DIR__));
|
||||||
@@ -12,8 +12,8 @@ require 'vendor/autoload.php';
|
|||||||
if (class_exists(Dotenv::class)) {
|
if (class_exists(Dotenv::class)) {
|
||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL);
|
||||||
ini_set('display_errors', '1');
|
ini_set('display_errors', '1');
|
||||||
$dotenv = new Dotenv(__DIR__ . '/..');
|
$dotenv = new Dotenv();
|
||||||
$dotenv->load();
|
$dotenv->load(__DIR__ . '/../.env');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build container
|
// Build container
|
||||||
|
|||||||
0
data/infra/database/.gitignore
vendored
Normal file → Executable file
0
data/infra/database/.gitignore
vendored
Normal file → Executable file
0
data/infra/nginx/.gitignore
vendored
Normal file → Executable file
0
data/infra/nginx/.gitignore
vendored
Normal file → Executable file
@@ -20,7 +20,7 @@ class Version20171021093246 extends AbstractMigration
|
|||||||
public function up(Schema $schema)
|
public function up(Schema $schema)
|
||||||
{
|
{
|
||||||
$shortUrls = $schema->getTable('short_urls');
|
$shortUrls = $schema->getTable('short_urls');
|
||||||
if ($shortUrls->hasColumn('value_since')) {
|
if ($shortUrls->hasColumn('valid_since')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ class Version20171021093246 extends AbstractMigration
|
|||||||
public function down(Schema $schema)
|
public function down(Schema $schema)
|
||||||
{
|
{
|
||||||
$shortUrls = $schema->getTable('short_urls');
|
$shortUrls = $schema->getTable('short_urls');
|
||||||
if (! $shortUrls->hasColumn('value_since')) {
|
if (! $shortUrls->hasColumn('valid_since')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,23 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"currentPage": {
|
"currentPage": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "The number of current page being displayed."
|
"description": "The number of current page."
|
||||||
},
|
},
|
||||||
"pagesCount": {
|
"pagesCount": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "The total number of pages that can be displayed."
|
"description": "The total number of pages that can be obtained."
|
||||||
|
},
|
||||||
|
"itemsPerPage": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The number of items for every page."
|
||||||
|
},
|
||||||
|
"itemsInCurrentPage": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The number of items in current page (could be smaller than itemsPerPage)."
|
||||||
|
},
|
||||||
|
"totalItems": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The total number of items among all pages."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"description": "The authorization token with Bearer type",
|
|
||||||
"required": true,
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
@@ -5,24 +5,39 @@
|
|||||||
],
|
],
|
||||||
"summary": "Perform authentication",
|
"summary": "Perform authentication",
|
||||||
"description": "Performs an authentication",
|
"description": "Performs an authentication",
|
||||||
"parameters": [
|
"requestBody": {
|
||||||
{
|
"description": "Request body.",
|
||||||
"name": "apiKey",
|
"required": true,
|
||||||
"in": "formData",
|
"content": {
|
||||||
"description": "The API key to authenticate with",
|
"application/json": {
|
||||||
"required": true,
|
"schema": {
|
||||||
"type": "string"
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"apiKey"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"apiKey": {
|
||||||
|
"description": "The API key to authenticate with",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "The authentication worked.",
|
"description": "The authentication worked.",
|
||||||
"schema": {
|
"content": {
|
||||||
"type": "object",
|
"application/json": {
|
||||||
"properties": {
|
"schema": {
|
||||||
"token": {
|
"type": "object",
|
||||||
"type": "string",
|
"properties": {
|
||||||
"description": "The authentication token that needs to be sent in the Authorization header"
|
"token": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The authentication token that needs to be sent in the Authorization header"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -34,20 +49,32 @@
|
|||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "An API key was not provided.",
|
"description": "An API key was not provided.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"401": {
|
"401": {
|
||||||
"description": "The API key is incorrect, is disabled or has expired.",
|
"description": "The API key is incorrect, is disabled or has expired.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Unexpected error.",
|
"description": "Unexpected error.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,59 +11,73 @@
|
|||||||
"in": "query",
|
"in": "query",
|
||||||
"description": "The page to be displayed. Defaults to 1",
|
"description": "The page to be displayed. Defaults to 1",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "integer"
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "searchTerm",
|
"name": "searchTerm",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"description": "A query used to filter results by searching for it on the longUrl and shortCode fields. (Since v1.3.0)",
|
"description": "A query used to filter results by searching for it on the longUrl and shortCode fields. (Since v1.3.0)",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "string"
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "tags",
|
"name": "tags",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"description": "A list of tags used to filter the resultset. Only short URLs tagged with at least one of the provided tags will be returned. (Since v1.3.0)",
|
"description": "A list of tags used to filter the resultset. Only short URLs tagged with at least one of the provided tags will be returned. (Since v1.3.0)",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "array",
|
"schema": {
|
||||||
"items": {
|
"type": "array",
|
||||||
"type": "string"
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "orderBy",
|
"name": "orderBy",
|
||||||
"in": "query",
|
"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. (Since v1.3.0)",
|
||||||
"enum": [
|
|
||||||
"originalUrl",
|
|
||||||
"shortCode",
|
|
||||||
"dateCreated",
|
|
||||||
"visits"
|
|
||||||
],
|
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "string"
|
"schema": {
|
||||||
},
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"originalUrl",
|
||||||
|
"shortCode",
|
||||||
|
"dateCreated",
|
||||||
|
"visits"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
{
|
{
|
||||||
"$ref": "../parameters/Authorization.json"
|
"Bearer": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "The list of short URLs",
|
"description": "The list of short URLs",
|
||||||
"schema": {
|
"content": {
|
||||||
"type": "object",
|
"application/json": {
|
||||||
"properties": {
|
"schema": {
|
||||||
"shortUrls": {
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"shortUrls": {
|
||||||
"type": "array",
|
"type": "object",
|
||||||
"items": {
|
"properties": {
|
||||||
"$ref": "../definitions/ShortUrl.json"
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "../definitions/ShortUrl.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pagination": {
|
||||||
|
"$ref": "../definitions/Pagination.json"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"pagination": {
|
|
||||||
"$ref": "../definitions/Pagination.json"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,7 +116,10 @@
|
|||||||
],
|
],
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"currentPage": 5,
|
"currentPage": 5,
|
||||||
"pagesCount": 12
|
"pagesCount": 12,
|
||||||
|
"itemsPerPage": 10,
|
||||||
|
"itemsInCurrentPage": 10,
|
||||||
|
"totalItems": 115
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,71 +127,114 @@
|
|||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Unexpected error.",
|
"description": "Unexpected error.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"ShortCodes"
|
"ShortCodes"
|
||||||
],
|
],
|
||||||
"summary": "Create short URL",
|
"summary": "Create short URL",
|
||||||
"description": "Creates a new short code",
|
"description": "Creates a new short code",
|
||||||
"parameters": [
|
"security": [
|
||||||
{
|
{
|
||||||
"name": "longUrl",
|
"Bearer": []
|
||||||
"in": "formData",
|
|
||||||
"description": "The URL to parse",
|
|
||||||
"required": true,
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tags",
|
|
||||||
"in": "formData",
|
|
||||||
"description": "The URL to parse",
|
|
||||||
"required": false,
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "../parameters/Authorization.json"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "Request body.",
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"longUrl"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"longUrl": {
|
||||||
|
"description": "The URL to parse",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"description": "The URL to parse",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"validSince": {
|
||||||
|
"description": "The date (in ISO-8601 format) from which this short code will be valid",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"validUntil": {
|
||||||
|
"description": "The date (in ISO-8601 format) until which this short code will be valid",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"customSlug": {
|
||||||
|
"description": "A unique custom slug to be used instead of the generated short code",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxVisits": {
|
||||||
|
"description": "The maximum number of allowed visits for this short code",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "The result of parsing the long URL",
|
"description": "The result of parsing the long URL",
|
||||||
"schema": {
|
"content": {
|
||||||
"type": "object",
|
"application/json": {
|
||||||
"properties": {
|
"schema": {
|
||||||
"longUrl": {
|
"type": "object",
|
||||||
"type": "string",
|
"properties": {
|
||||||
"description": "The original long URL that has been parsed"
|
"longUrl": {
|
||||||
},
|
"type": "string",
|
||||||
"shortUrl": {
|
"description": "The original long URL that has been parsed"
|
||||||
"type": "string",
|
},
|
||||||
"description": "The generated short URL"
|
"shortUrl": {
|
||||||
},
|
"type": "string",
|
||||||
"shortCode": {
|
"description": "The generated short URL"
|
||||||
"type": "string",
|
},
|
||||||
"description": "the short code that is being used in the short URL"
|
"shortCode": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "the short code that is being used in the short URL"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "The long URL was not provided or is invalid.",
|
"description": "The long URL was not provided or is invalid.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Unexpected error.",
|
"description": "Unexpected error.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
125
docs/swagger/paths/v1_short-codes_shorten.json
Normal file
125
docs/swagger/paths/v1_short-codes_shorten.json
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
{
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"ShortCodes"
|
||||||
|
],
|
||||||
|
"summary": "Create a short URL",
|
||||||
|
"description": "Creates a short URL in a single API call. Useful for third party integrations",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "apiKey",
|
||||||
|
"in": "query",
|
||||||
|
"description": "The API key used to authenticate the request",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "longUrl",
|
||||||
|
"in": "query",
|
||||||
|
"description": "The URL to be shortened",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "format",
|
||||||
|
"in": "query",
|
||||||
|
"description": "The format in which you want the response to be returned. You can also use the \"Accept\" header instead of this",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"txt",
|
||||||
|
"json"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "The list of short URLs",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"longUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The original long URL that has been shortened"
|
||||||
|
},
|
||||||
|
"shortUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The generated short URL"
|
||||||
|
},
|
||||||
|
"shortCode": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "the short code that is being used in the short URL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"text/plain": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"application/json": {
|
||||||
|
"longUrl": "https://github.com/shlinkio/shlink",
|
||||||
|
"shortUrl": "https://dom.ain/abc123",
|
||||||
|
"shortCode": "abc123"
|
||||||
|
},
|
||||||
|
"text/plain": "https://dom.ain/abc123"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "The long URL was not provided or is invalid.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"text/plain": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"application/json": {
|
||||||
|
"error": "INVALID_URL",
|
||||||
|
"message": "Provided URL foo is invalid. Try with a different one."
|
||||||
|
},
|
||||||
|
"text/plain": "INVALID_URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Unexpected error.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"text/plain": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"application/json": {
|
||||||
|
"error": "UNKNOWN_ERROR",
|
||||||
|
"message": "Unexpected error occurred"
|
||||||
|
},
|
||||||
|
"text/plain": "UNKNOWN_ERROR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,23 +9,31 @@
|
|||||||
{
|
{
|
||||||
"name": "shortCode",
|
"name": "shortCode",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"type": "string",
|
|
||||||
"description": "The short code to resolve.",
|
"description": "The short code to resolve.",
|
||||||
"required": true
|
"required": true,
|
||||||
},
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
{
|
{
|
||||||
"$ref": "../parameters/Authorization.json"
|
"Bearer": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "The long URL behind a short code.",
|
"description": "The long URL behind a short code.",
|
||||||
"schema": {
|
"content": {
|
||||||
"type": "object",
|
"application/json": {
|
||||||
"properties": {
|
"schema": {
|
||||||
"longUrl": {
|
"type": "object",
|
||||||
"type": "string",
|
"properties": {
|
||||||
"description": "The original long URL behind the short code."
|
"longUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The original long URL behind the short code."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -37,20 +45,116 @@
|
|||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "Provided shortCode does not match the character set currently used by the app to generate short codes.",
|
"description": "Provided shortCode does not match the character set currently used by the app to generate short codes.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "No URL was found for provided short code.",
|
"description": "No URL was found for provided short code.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Unexpected error.",
|
"description": "Unexpected error.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"put": {
|
||||||
|
"tags": [
|
||||||
|
"ShortCodes"
|
||||||
|
],
|
||||||
|
"summary": "Edit short code",
|
||||||
|
"description": "Update certain meta arguments from an existing short URL.",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "shortCode",
|
||||||
|
"in": "path",
|
||||||
|
"description": "The short code to edit.",
|
||||||
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "../definitions/Error.json"
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "Request body.",
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"validSince": {
|
||||||
|
"description": "The date (in ISO-8601 format) from which this short code will be valid",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"validUntil": {
|
||||||
|
"description": "The date (in ISO-8601 format) until which this short code will be valid",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxVisits": {
|
||||||
|
"description": "The maximum number of allowed visits for this short code",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "The short code has been properly updated."
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Provided meta arguments are invalid.",
|
||||||
|
"content": {
|
||||||
|
"application/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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Unexpected error.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,34 +10,55 @@
|
|||||||
{
|
{
|
||||||
"name": "shortCode",
|
"name": "shortCode",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"type": "string",
|
|
||||||
"description": "The shortCode in which we want to edit tags.",
|
"description": "The shortCode in which we want to edit tags.",
|
||||||
"required": true
|
"required": true,
|
||||||
},
|
"schema": {
|
||||||
{
|
|
||||||
"name": "tags",
|
|
||||||
"in": "formData",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
}
|
||||||
"description": "The list of tags to set to the short URL.",
|
}
|
||||||
"required": true
|
],
|
||||||
},
|
"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": [
|
||||||
{
|
{
|
||||||
"$ref": "../parameters/Authorization.json"
|
"Bearer": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "List of tags.",
|
"description": "List of tags.",
|
||||||
"schema": {
|
"content": {
|
||||||
"type": "object",
|
"application/json": {
|
||||||
"properties": {
|
"schema": {
|
||||||
"tags": {
|
"type": "object",
|
||||||
"type": "array",
|
"properties": {
|
||||||
"items": {
|
"tags": {
|
||||||
"type": "string"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,20 +74,32 @@
|
|||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "The request body does not contain a \"tags\" param with array type.",
|
"description": "The request body does not contain a \"tags\" param with array type.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "No short URL was found for provided short code.",
|
"description": "No short URL was found for provided short code.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Unexpected error.",
|
"description": "Unexpected error.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,27 +10,35 @@
|
|||||||
{
|
{
|
||||||
"name": "shortCode",
|
"name": "shortCode",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"type": "string",
|
|
||||||
"description": "The shortCode from which we want to get the visits.",
|
"description": "The shortCode from which we want to get the visits.",
|
||||||
"required": true
|
"required": true,
|
||||||
},
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
{
|
{
|
||||||
"$ref": "../parameters/Authorization.json"
|
"Bearer": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "List of visits.",
|
"description": "List of visits.",
|
||||||
"schema": {
|
"content": {
|
||||||
"type": "object",
|
"application/json": {
|
||||||
"properties": {
|
"schema": {
|
||||||
"visits": {
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"visits": {
|
||||||
"type": "array",
|
"type": "object",
|
||||||
"items": {
|
"properties": {
|
||||||
"$ref": "../definitions/Visit.json"
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "../definitions/Visit.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,14 +74,22 @@
|
|||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "The short code does not belong to any short URL.",
|
"description": "The short code does not belong to any short URL.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Unexpected error.",
|
"description": "Unexpected error.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,24 +5,28 @@
|
|||||||
],
|
],
|
||||||
"summary": "List existing 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, ordered by name",
|
||||||
"parameters": [
|
"security": [
|
||||||
{
|
{
|
||||||
"$ref": "../parameters/Authorization.json"
|
"Bearer": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "The list of tags",
|
"description": "The list of tags",
|
||||||
"schema": {
|
"content": {
|
||||||
"type": "object",
|
"application/json": {
|
||||||
"properties": {
|
"schema": {
|
||||||
"tags": {
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"tags": {
|
||||||
"type": "array",
|
"type": "object",
|
||||||
"items": {
|
"properties": {
|
||||||
"type": "string"
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,8 +48,12 @@
|
|||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Unexpected error.",
|
"description": "Unexpected error.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,31 +65,51 @@
|
|||||||
],
|
],
|
||||||
"summary": "Create tags",
|
"summary": "Create tags",
|
||||||
"description": "Provided a list of tags, creates all that do not yet exist",
|
"description": "Provided a list of tags, creates all that do not yet exist",
|
||||||
"parameters": [
|
"security": [
|
||||||
{
|
{
|
||||||
"$ref": "../parameters/Authorization.json"
|
"Bearer": []
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tags[]",
|
|
||||||
"in": "formData",
|
|
||||||
"description": "The list of tag names to create",
|
|
||||||
"required": true,
|
|
||||||
"type": "array"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "Request body.",
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"tags"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"tags": {
|
||||||
|
"description": "The list of tag names to create",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "The list of tags",
|
"description": "The list of tags",
|
||||||
"schema": {
|
"content": {
|
||||||
"type": "object",
|
"application/json": {
|
||||||
"properties": {
|
"schema": {
|
||||||
"tags": {
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"tags": {
|
||||||
"type": "array",
|
"type": "object",
|
||||||
"items": {
|
"properties": {
|
||||||
"type": "string"
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,8 +131,12 @@
|
|||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Unexpected error.",
|
"description": "Unexpected error.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,45 +148,68 @@
|
|||||||
],
|
],
|
||||||
"summary": "Rename tag",
|
"summary": "Rename tag",
|
||||||
"description": "Renames one existing tag",
|
"description": "Renames one existing tag",
|
||||||
"parameters": [
|
"security": [
|
||||||
{
|
{
|
||||||
"$ref": "../parameters/Authorization.json"
|
"Bearer": []
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "oldName",
|
|
||||||
"in": "formData",
|
|
||||||
"description": "Current name of the tag",
|
|
||||||
"required": true,
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "newName",
|
|
||||||
"in": "formData",
|
|
||||||
"description": "New name of the tag",
|
|
||||||
"required": true,
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "Request body.",
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"oldName",
|
||||||
|
"newName"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"oldName": {
|
||||||
|
"description": "Current name of the tag",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"newName": {
|
||||||
|
"description": "New name of the tag",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "The tag has been properly renamed"
|
"description": "The tag has been properly renamed"
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "You have not provided either the oldName or the newName params.",
|
"description": "You have not provided either the oldName or the newName params.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "There's no tag found with the name provided in oldName param.",
|
"description": "There's no tag found with the name provided in oldName param.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Unexpected error.",
|
"description": "Unexpected error.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,15 +222,22 @@
|
|||||||
"summary": "Delete tags",
|
"summary": "Delete tags",
|
||||||
"description": "Deletes provided list of tags",
|
"description": "Deletes provided list of tags",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
|
||||||
"$ref": "../parameters/Authorization.json"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "tags[]",
|
"name": "tags[]",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"description": "The names of the tags to delete",
|
"description": "The names of the tags to delete",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "array"
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -184,8 +246,12 @@
|
|||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Unexpected error.",
|
"description": "Unexpected error.",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "../definitions/Error.json"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,37 @@
|
|||||||
{
|
{
|
||||||
"swagger": "2.0",
|
"openapi": "3.0.0",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Shlink",
|
"title": "Shlink",
|
||||||
"description": "Shlink, the self-hosted URL shortener",
|
"description": "Shlink, the self-hosted URL shortener",
|
||||||
"version": "1.0"
|
"version": "1.0"
|
||||||
},
|
},
|
||||||
"schemes": [
|
|
||||||
"http",
|
"servers": [
|
||||||
"https"
|
{
|
||||||
],
|
"url": "{schema}://{server}/rest",
|
||||||
"basePath": "/rest",
|
"variables": {
|
||||||
"produces": [
|
"schema": {
|
||||||
"application/json"
|
"default": "https",
|
||||||
],
|
"enum": ["https", "http"]
|
||||||
"consumes": [
|
},
|
||||||
"application/x-www-form-urlencoded",
|
"server": {
|
||||||
"application/json"
|
"default": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"components": {
|
||||||
|
"securitySchemes": {
|
||||||
|
"Bearer": {
|
||||||
|
"description": "The JWT identifying a previously logged API key",
|
||||||
|
"type": "http",
|
||||||
|
"scheme": "bearer",
|
||||||
|
"bearerFormat": "JWT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"/v1/authenticate": {
|
"/v1/authenticate": {
|
||||||
"$ref": "paths/v1_authenticate.json"
|
"$ref": "paths/v1_authenticate.json"
|
||||||
@@ -26,6 +40,9 @@
|
|||||||
"/v1/short-codes": {
|
"/v1/short-codes": {
|
||||||
"$ref": "paths/v1_short-codes.json"
|
"$ref": "paths/v1_short-codes.json"
|
||||||
},
|
},
|
||||||
|
"/v1/short-codes/shorten": {
|
||||||
|
"$ref": "paths/v1_short-codes_shorten.json"
|
||||||
|
},
|
||||||
"/v1/short-codes/{shortCode}": {
|
"/v1/short-codes/{shortCode}": {
|
||||||
"$ref": "paths/v1_short-codes_{shortCode}.json"
|
"$ref": "paths/v1_short-codes_{shortCode}.json"
|
||||||
},
|
},
|
||||||
|
|||||||
18
infection.json
Normal file
18
infection.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"source": {
|
||||||
|
"directories": [
|
||||||
|
"module/*/src"
|
||||||
|
],
|
||||||
|
"excludes": []
|
||||||
|
},
|
||||||
|
"timeout": 10,
|
||||||
|
"logs": {
|
||||||
|
"text": "build/infection/infection-log.txt",
|
||||||
|
"summary": "build/infection/summary-log.txt",
|
||||||
|
"debug": "build/infection/debug-log.txt"
|
||||||
|
},
|
||||||
|
"tmpDir": "build/infection/temp",
|
||||||
|
"phpUnit": {
|
||||||
|
"configDir": "."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,21 +9,25 @@ return [
|
|||||||
'cli' => [
|
'cli' => [
|
||||||
'locale' => Common\env('CLI_LOCALE', 'en'),
|
'locale' => Common\env('CLI_LOCALE', 'en'),
|
||||||
'commands' => [
|
'commands' => [
|
||||||
Command\Shortcode\GenerateShortcodeCommand::class,
|
Command\Shortcode\GenerateShortcodeCommand::NAME => Command\Shortcode\GenerateShortcodeCommand::class,
|
||||||
Command\Shortcode\ResolveUrlCommand::class,
|
Command\Shortcode\ResolveUrlCommand::NAME => Command\Shortcode\ResolveUrlCommand::class,
|
||||||
Command\Shortcode\ListShortcodesCommand::class,
|
Command\Shortcode\ListShortcodesCommand::NAME => Command\Shortcode\ListShortcodesCommand::class,
|
||||||
Command\Shortcode\GetVisitsCommand::class,
|
Command\Shortcode\GetVisitsCommand::NAME => Command\Shortcode\GetVisitsCommand::class,
|
||||||
Command\Shortcode\GeneratePreviewCommand::class,
|
Command\Shortcode\GeneratePreviewCommand::NAME => Command\Shortcode\GeneratePreviewCommand::class,
|
||||||
Command\Visit\ProcessVisitsCommand::class,
|
|
||||||
Command\Config\GenerateCharsetCommand::class,
|
Command\Visit\ProcessVisitsCommand::NAME => Command\Visit\ProcessVisitsCommand::class,
|
||||||
Command\Config\GenerateSecretCommand::class,
|
|
||||||
Command\Api\GenerateKeyCommand::class,
|
Command\Config\GenerateCharsetCommand::NAME => Command\Config\GenerateCharsetCommand::class,
|
||||||
Command\Api\DisableKeyCommand::class,
|
Command\Config\GenerateSecretCommand::NAME => Command\Config\GenerateSecretCommand::class,
|
||||||
Command\Api\ListKeysCommand::class,
|
|
||||||
Command\Tag\ListTagsCommand::class,
|
Command\Api\GenerateKeyCommand::NAME => Command\Api\GenerateKeyCommand::class,
|
||||||
Command\Tag\CreateTagCommand::class,
|
Command\Api\DisableKeyCommand::NAME => Command\Api\DisableKeyCommand::class,
|
||||||
Command\Tag\RenameTagCommand::class,
|
Command\Api\ListKeysCommand::NAME => Command\Api\ListKeysCommand::class,
|
||||||
Command\Tag\DeleteTagsCommand::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,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -1,15 +1,15 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Shlink 1.0\n"
|
"Project-Id-Version: Shlink 1.0\n"
|
||||||
"POT-Creation-Date: 2017-10-21 20:17+0200\n"
|
"POT-Creation-Date: 2018-01-21 09:36+0100\n"
|
||||||
"PO-Revision-Date: 2017-10-21 20:19+0200\n"
|
"PO-Revision-Date: 2018-01-21 09:39+0100\n"
|
||||||
"Last-Translator: Alejandro Celaya <alejandro@alejandrocelaya.com>\n"
|
"Last-Translator: Alejandro Celaya <alejandro@alejandrocelaya.com>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"Language: es_ES\n"
|
"Language: es_ES\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Generator: Poedit 2.0.1\n"
|
"X-Generator: Poedit 2.0.4\n"
|
||||||
"X-Poedit-Basepath: ..\n"
|
"X-Poedit-Basepath: ..\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
"X-Poedit-SourceCharset: UTF-8\n"
|
"X-Poedit-SourceCharset: UTF-8\n"
|
||||||
@@ -24,8 +24,8 @@ msgid "The API key to disable"
|
|||||||
msgstr "La clave de API a deshabilitar"
|
msgstr "La clave de API a deshabilitar"
|
||||||
|
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "API key %s properly disabled"
|
msgid "API key \"%s\" properly disabled"
|
||||||
msgstr "Clave de API %s deshabilitada correctamente"
|
msgstr "Clave de API \"%s\" deshabilitada correctamente"
|
||||||
|
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "API key \"%s\" does not exist."
|
msgid "API key \"%s\" does not exist."
|
||||||
@@ -39,8 +39,9 @@ msgstr ""
|
|||||||
"La fecha en la que la clave de API debe expirar. Utiliza cualquier valor "
|
"La fecha en la que la clave de API debe expirar. Utiliza cualquier valor "
|
||||||
"válido en PHP."
|
"válido en PHP."
|
||||||
|
|
||||||
msgid "Generated API key"
|
#, php-format
|
||||||
msgstr "Generada clave de API"
|
msgid "Generated API key: \"%s\""
|
||||||
|
msgstr "Generada clave de API. \"%s\""
|
||||||
|
|
||||||
msgid "Lists all the available API keys."
|
msgid "Lists all the available API keys."
|
||||||
msgstr "Lista todas las claves de API disponibles."
|
msgstr "Lista todas las claves de API disponibles."
|
||||||
@@ -51,12 +52,12 @@ msgstr "Define si sólo las claves de API habilitadas deben ser devueltas."
|
|||||||
msgid "Key"
|
msgid "Key"
|
||||||
msgstr "Clave"
|
msgstr "Clave"
|
||||||
|
|
||||||
msgid "Expiration date"
|
|
||||||
msgstr "Fecha de caducidad"
|
|
||||||
|
|
||||||
msgid "Is enabled"
|
msgid "Is enabled"
|
||||||
msgstr "Está habilitada"
|
msgstr "Está habilitada"
|
||||||
|
|
||||||
|
msgid "Expiration date"
|
||||||
|
msgstr "Fecha de caducidad"
|
||||||
|
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Generates a character set sample just by shuffling the default one, \"%s\". "
|
"Generates a character set sample just by shuffling the default one, \"%s\". "
|
||||||
@@ -65,8 +66,9 @@ msgstr ""
|
|||||||
"Genera un grupo de caracteres simplemente mexclando el grupo por defecto \"%s"
|
"Genera un grupo de caracteres simplemente mexclando el grupo por defecto \"%s"
|
||||||
"\". Después puede ser utilizado en la variable de entrono SHORTCODE_CHARS"
|
"\". Después puede ser utilizado en la variable de entrono SHORTCODE_CHARS"
|
||||||
|
|
||||||
msgid "Character set:"
|
#, php-format
|
||||||
msgstr "Grupo de caracteres:"
|
msgid "Character set: \"%s\""
|
||||||
|
msgstr "Grupo de caracteres: \"%s\""
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Generates a random secret string that can be used for JWT token encryption"
|
"Generates a random secret string that can be used for JWT token encryption"
|
||||||
@@ -74,8 +76,9 @@ msgstr ""
|
|||||||
"Genera una cadena de caracteres aleatoria que puede ser usada para cifrar "
|
"Genera una cadena de caracteres aleatoria que puede ser usada para cifrar "
|
||||||
"tokens JWT"
|
"tokens JWT"
|
||||||
|
|
||||||
msgid "Secret key:"
|
#, php-format
|
||||||
msgstr "Clave secreta:"
|
msgid "Secret key: \"%s\""
|
||||||
|
msgstr "Clave secreta: \"%s\""
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Processes and generates the previews for every URL, improving performance "
|
"Processes and generates the previews for every URL, improving performance "
|
||||||
@@ -125,17 +128,22 @@ msgid "If provided, this slug will be used instead of generating a short code"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Si se proporciona, este slug será usado en vez de generar un código corto"
|
"Si se proporciona, este slug será usado en vez de generar un código corto"
|
||||||
|
|
||||||
msgid "A long URL was not provided. Which URL do you want to shorten?:"
|
msgid "This will limit the number of visits for this short URL."
|
||||||
|
msgstr "Esto limitará el número de visitas a esta URL acortada."
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "A long URL was not provided. Which URL do you want to shorten?:"
|
||||||
|
msgid "A long URL was not provided. Which URL do you want to be shortened?"
|
||||||
msgstr "No se ha proporcionado una URL larga. ¿Qué URL deseas acortar?"
|
msgstr "No se ha proporcionado una URL larga. ¿Qué URL deseas acortar?"
|
||||||
|
|
||||||
msgid "A URL was not provided!"
|
msgid "A URL was not provided!"
|
||||||
msgstr "¡No se ha proporcionado una URL!"
|
msgstr "¡No se ha proporcionado una URL!"
|
||||||
|
|
||||||
msgid "Processed URL:"
|
msgid "Processed long URL:"
|
||||||
msgstr "URL procesada:"
|
msgstr "URL larga procesada:"
|
||||||
|
|
||||||
msgid "Generated URL:"
|
msgid "Generated short URL:"
|
||||||
msgstr "URL generada:"
|
msgstr "URL corta generada:"
|
||||||
|
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Provided URL \"%s\" is invalid. Try with a different one."
|
msgid "Provided URL \"%s\" is invalid. Try with a different one."
|
||||||
@@ -166,8 +174,8 @@ msgid "Allows to filter visits, returning only those newer than end date"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Permite filtrar las visitas, devolviendo sólo aquellas más nuevas que endDate"
|
"Permite filtrar las visitas, devolviendo sólo aquellas más nuevas que endDate"
|
||||||
|
|
||||||
msgid "A short code was not provided. Which short code do you want to use?:"
|
msgid "A short code was not provided. Which short code do you want to use?"
|
||||||
msgstr "No se prporcionó un código corto. ¿Qué código corto deseas usar?:"
|
msgstr "No se proporcionó un código corto. ¿Qué código corto deseas usar?"
|
||||||
|
|
||||||
msgid "Referer"
|
msgid "Referer"
|
||||||
msgstr "Origen"
|
msgstr "Origen"
|
||||||
@@ -222,8 +230,8 @@ msgstr "Número de visitas"
|
|||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Etiquetas"
|
msgstr "Etiquetas"
|
||||||
|
|
||||||
msgid "You have reached last page"
|
msgid "Short codes properly listed"
|
||||||
msgstr "Has alcanzado la última página"
|
msgstr "Códigos cortos correctamente listados"
|
||||||
|
|
||||||
msgid "Continue with page"
|
msgid "Continue with page"
|
||||||
msgstr "Continuar con la página"
|
msgstr "Continuar con la página"
|
||||||
@@ -234,13 +242,9 @@ msgstr "Devuelve la URL larga detrás de un código corto"
|
|||||||
msgid "The short code to parse"
|
msgid "The short code to parse"
|
||||||
msgstr "El código corto a convertir"
|
msgstr "El código corto a convertir"
|
||||||
|
|
||||||
msgid "A short code was not provided. Which short code do you want to parse?:"
|
msgid "A short code was not provided. Which short code do you want to parse?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"No se proporcionó un código corto. ¿Qué código corto quieres convertir?:"
|
"No se proporcionó un código corto. ¿Qué código corto quieres convertir?"
|
||||||
|
|
||||||
#, php-format
|
|
||||||
msgid "No URL found for short code \"%s\""
|
|
||||||
msgstr "No se ha encontrado ninguna URL para el código corto \"%s\""
|
|
||||||
|
|
||||||
msgid "Long URL:"
|
msgid "Long URL:"
|
||||||
msgstr "URL larga:"
|
msgstr "URL larga:"
|
||||||
@@ -262,8 +266,8 @@ msgstr "El nombre de las etiquetas a crear"
|
|||||||
msgid "You have to provide at least one tag name"
|
msgid "You have to provide at least one tag name"
|
||||||
msgstr "Debes proporcionar al menos un nombre de etiqueta"
|
msgstr "Debes proporcionar al menos un nombre de etiqueta"
|
||||||
|
|
||||||
msgid "Created tags"
|
msgid "Tags properly created"
|
||||||
msgstr "Etiquetas creadas"
|
msgstr "Etiquetas correctamente creadas"
|
||||||
|
|
||||||
msgid "Deletes one or more tags."
|
msgid "Deletes one or more tags."
|
||||||
msgstr "Elimina una o más etiquetas."
|
msgstr "Elimina una o más etiquetas."
|
||||||
@@ -271,8 +275,8 @@ msgstr "Elimina una o más etiquetas."
|
|||||||
msgid "The name of the tags to delete"
|
msgid "The name of the tags to delete"
|
||||||
msgstr "El nombre de las etiquetas a eliminar"
|
msgstr "El nombre de las etiquetas a eliminar"
|
||||||
|
|
||||||
msgid "Deleted tags"
|
msgid "Tags properly deleted"
|
||||||
msgstr "Etiquetas eliminadas"
|
msgstr "Etiquetas correctamente eliminadas"
|
||||||
|
|
||||||
msgid "Lists existing tags."
|
msgid "Lists existing tags."
|
||||||
msgstr "Lista las etiquetas existentes."
|
msgstr "Lista las etiquetas existentes."
|
||||||
@@ -315,3 +319,15 @@ msgstr "Dirección localizada en \"%s\""
|
|||||||
|
|
||||||
msgid "Finished processing all IPs"
|
msgid "Finished processing all IPs"
|
||||||
msgstr "Finalizado el procesado de todas las IPs"
|
msgstr "Finalizado el procesado de todas las IPs"
|
||||||
|
|
||||||
|
#~ msgid "You have reached last page"
|
||||||
|
#~ msgstr "Has alcanzado la última página"
|
||||||
|
|
||||||
|
#~ msgid "No URL found for short code \"%s\""
|
||||||
|
#~ msgstr "No se ha encontrado ninguna URL para el código corto \"%s\""
|
||||||
|
|
||||||
|
#~ msgid "Created tags"
|
||||||
|
#~ msgstr "Etiquetas creadas"
|
||||||
|
|
||||||
|
#~ msgid "Deleted tags"
|
||||||
|
#~ msgstr "Etiquetas eliminadas"
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ use Symfony\Component\Console\Command\Command;
|
|||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class DisableKeyCommand extends Command
|
class DisableKeyCommand extends Command
|
||||||
{
|
{
|
||||||
|
const NAME = 'api-key:disable';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var ApiKeyServiceInterface
|
* @var ApiKeyServiceInterface
|
||||||
*/
|
*/
|
||||||
@@ -30,7 +33,7 @@ class DisableKeyCommand extends Command
|
|||||||
|
|
||||||
public function configure()
|
public function configure()
|
||||||
{
|
{
|
||||||
$this->setName('api-key:disable')
|
$this->setName(self::NAME)
|
||||||
->setDescription($this->translator->translate('Disables an API key.'))
|
->setDescription($this->translator->translate('Disables an API key.'))
|
||||||
->addArgument('apiKey', InputArgument::REQUIRED, $this->translator->translate('The API key to disable'));
|
->addArgument('apiKey', InputArgument::REQUIRED, $this->translator->translate('The API key to disable'));
|
||||||
}
|
}
|
||||||
@@ -38,18 +41,13 @@ class DisableKeyCommand extends Command
|
|||||||
public function execute(InputInterface $input, OutputInterface $output)
|
public function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
$apiKey = $input->getArgument('apiKey');
|
$apiKey = $input->getArgument('apiKey');
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->apiKeyService->disable($apiKey);
|
$this->apiKeyService->disable($apiKey);
|
||||||
$output->writeln(sprintf(
|
$io->success(sprintf($this->translator->translate('API key "%s" properly disabled'), $apiKey));
|
||||||
$this->translator->translate('API key %s properly disabled'),
|
|
||||||
'<info>' . $apiKey . '</info>'
|
|
||||||
));
|
|
||||||
} catch (\InvalidArgumentException $e) {
|
} catch (\InvalidArgumentException $e) {
|
||||||
$output->writeln(sprintf(
|
$io->error(sprintf($this->translator->translate('API key "%s" does not exist.'), $apiKey));
|
||||||
'<error>' . $this->translator->translate('API key "%s" does not exist.') . '</error>',
|
|
||||||
$apiKey
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ use Symfony\Component\Console\Command\Command;
|
|||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class GenerateKeyCommand extends Command
|
class GenerateKeyCommand extends Command
|
||||||
{
|
{
|
||||||
|
const NAME = 'api-key:generate';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var ApiKeyServiceInterface
|
* @var ApiKeyServiceInterface
|
||||||
*/
|
*/
|
||||||
@@ -30,7 +33,7 @@ class GenerateKeyCommand extends Command
|
|||||||
|
|
||||||
public function configure()
|
public function configure()
|
||||||
{
|
{
|
||||||
$this->setName('api-key:generate')
|
$this->setName(self::NAME)
|
||||||
->setDescription($this->translator->translate('Generates a new valid API key.'))
|
->setDescription($this->translator->translate('Generates a new valid API key.'))
|
||||||
->addOption(
|
->addOption(
|
||||||
'expirationDate',
|
'expirationDate',
|
||||||
@@ -44,6 +47,9 @@ class GenerateKeyCommand extends Command
|
|||||||
{
|
{
|
||||||
$expirationDate = $input->getOption('expirationDate');
|
$expirationDate = $input->getOption('expirationDate');
|
||||||
$apiKey = $this->apiKeyService->create(isset($expirationDate) ? new \DateTime($expirationDate) : null);
|
$apiKey = $this->apiKeyService->create(isset($expirationDate) ? new \DateTime($expirationDate) : null);
|
||||||
$output->writeln($this->translator->translate('Generated API key') . sprintf(': <info>%s</info>', $apiKey));
|
|
||||||
|
(new SymfonyStyle($input, $output))->success(
|
||||||
|
sprintf($this->translator->translate('Generated API key: "%s"'), $apiKey)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,16 @@ namespace Shlinkio\Shlink\CLI\Command\Api;
|
|||||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Helper\Table;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class ListKeysCommand extends Command
|
class ListKeysCommand extends Command
|
||||||
{
|
{
|
||||||
|
const NAME = 'api-key:list';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var ApiKeyServiceInterface
|
* @var ApiKeyServiceInterface
|
||||||
*/
|
*/
|
||||||
@@ -32,7 +34,7 @@ class ListKeysCommand extends Command
|
|||||||
|
|
||||||
public function configure()
|
public function configure()
|
||||||
{
|
{
|
||||||
$this->setName('api-key:list')
|
$this->setName(self::NAME)
|
||||||
->setDescription($this->translator->translate('Lists all the available API keys.'))
|
->setDescription($this->translator->translate('Lists all the available API keys.'))
|
||||||
->addOption(
|
->addOption(
|
||||||
'enabledOnly',
|
'enabledOnly',
|
||||||
@@ -44,78 +46,75 @@ class ListKeysCommand extends Command
|
|||||||
|
|
||||||
public function execute(InputInterface $input, OutputInterface $output)
|
public function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
$enabledOnly = $input->getOption('enabledOnly');
|
$enabledOnly = $input->getOption('enabledOnly');
|
||||||
$list = $this->apiKeyService->listKeys($enabledOnly);
|
$list = $this->apiKeyService->listKeys($enabledOnly);
|
||||||
|
$rows = [];
|
||||||
$table = new Table($output);
|
|
||||||
if ($enabledOnly) {
|
|
||||||
$table->setHeaders([
|
|
||||||
$this->translator->translate('Key'),
|
|
||||||
$this->translator->translate('Expiration date'),
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
$table->setHeaders([
|
|
||||||
$this->translator->translate('Key'),
|
|
||||||
$this->translator->translate('Is enabled'),
|
|
||||||
$this->translator->translate('Expiration date'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var ApiKey $row */
|
/** @var ApiKey $row */
|
||||||
foreach ($list as $row) {
|
foreach ($list as $row) {
|
||||||
$key = $row->getKey();
|
$key = $row->getKey();
|
||||||
$expiration = $row->getExpirationDate();
|
$expiration = $row->getExpirationDate();
|
||||||
$rowData = [];
|
$formatMethod = $this->determineFormatMethod($row);
|
||||||
$formatMethod = ! $row->isEnabled()
|
|
||||||
? 'getErrorString'
|
|
||||||
: ($row->isExpired() ? 'getWarningString' : 'getSuccessString');
|
|
||||||
|
|
||||||
if ($enabledOnly) {
|
// Set columns for this row
|
||||||
$rowData[] = $this->{$formatMethod}($key);
|
$rowData = [$formatMethod($key)];
|
||||||
} else {
|
if (! $enabledOnly) {
|
||||||
$rowData[] = $this->{$formatMethod}($key);
|
$rowData[] = $formatMethod($this->getEnabledSymbol($row));
|
||||||
$rowData[] = $this->{$formatMethod}($this->getEnabledSymbol($row));
|
|
||||||
}
|
}
|
||||||
|
$rowData[] = $expiration !== null ? $expiration->format(\DateTime::ATOM) : '-';
|
||||||
|
|
||||||
$rowData[] = isset($expiration) ? $expiration->format(\DateTime::ATOM) : '-';
|
$rows[] = $rowData;
|
||||||
$table->addRow($rowData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$table->render();
|
$io->table(array_filter([
|
||||||
|
$this->translator->translate('Key'),
|
||||||
|
! $enabledOnly ? $this->translator->translate('Is enabled') : null,
|
||||||
|
$this->translator->translate('Expiration date'),
|
||||||
|
]), $rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function determineFormatMethod(ApiKey $apiKey): callable
|
||||||
|
{
|
||||||
|
if (! $apiKey->isEnabled()) {
|
||||||
|
return [$this, 'getErrorString'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $apiKey->isExpired() ? [$this, 'getWarningString'] : [$this, 'getSuccessString'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $string
|
* @param string $value
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function getErrorString($string)
|
private function getErrorString(string $value): string
|
||||||
{
|
{
|
||||||
return sprintf('<fg=red>%s</>', $string);
|
return sprintf('<fg=red>%s</>', $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $string
|
* @param string $value
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function getSuccessString($string)
|
private function getSuccessString(string $value): string
|
||||||
{
|
{
|
||||||
return sprintf('<info>%s</info>', $string);
|
return sprintf('<info>%s</info>', $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $string
|
* @param string $value
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function getWarningString($string)
|
private function getWarningString(string $value): string
|
||||||
{
|
{
|
||||||
return sprintf('<comment>%s</comment>', $string);
|
return sprintf('<comment>%s</comment>', $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ApiKey $apiKey
|
* @param ApiKey $apiKey
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function getEnabledSymbol(ApiKey $apiKey)
|
private function getEnabledSymbol(ApiKey $apiKey): string
|
||||||
{
|
{
|
||||||
return ! $apiKey->isEnabled() || $apiKey->isExpired() ? '---' : '+++';
|
return ! $apiKey->isEnabled() || $apiKey->isExpired() ? '---' : '+++';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ use Shlinkio\Shlink\Core\Service\UrlShortener;
|
|||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class GenerateCharsetCommand extends Command
|
class GenerateCharsetCommand extends Command
|
||||||
{
|
{
|
||||||
|
const NAME = 'config:generate-charset';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TranslatorInterface
|
* @var TranslatorInterface
|
||||||
*/
|
*/
|
||||||
@@ -24,7 +27,7 @@ class GenerateCharsetCommand extends Command
|
|||||||
|
|
||||||
public function configure()
|
public function configure()
|
||||||
{
|
{
|
||||||
$this->setName('config:generate-charset')
|
$this->setName(self::NAME)
|
||||||
->setDescription(sprintf($this->translator->translate(
|
->setDescription(sprintf($this->translator->translate(
|
||||||
'Generates a character set sample just by shuffling the default one, "%s". '
|
'Generates a character set sample just by shuffling the default one, "%s". '
|
||||||
. 'Then it can be set in the SHORTCODE_CHARS environment variable'
|
. 'Then it can be set in the SHORTCODE_CHARS environment variable'
|
||||||
@@ -34,6 +37,8 @@ class GenerateCharsetCommand extends Command
|
|||||||
public function execute(InputInterface $input, OutputInterface $output)
|
public function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
$charSet = str_shuffle(UrlShortener::DEFAULT_CHARS);
|
$charSet = str_shuffle(UrlShortener::DEFAULT_CHARS);
|
||||||
$output->writeln($this->translator->translate('Character set:') . sprintf(' <info>%s</info>', $charSet));
|
(new SymfonyStyle($input, $output))->success(
|
||||||
|
\sprintf($this->translator->translate('Character set: "%s"'), $charSet)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
|||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class GenerateSecretCommand extends Command
|
class GenerateSecretCommand extends Command
|
||||||
{
|
{
|
||||||
use StringUtilsTrait;
|
use StringUtilsTrait;
|
||||||
|
|
||||||
|
const NAME = 'config:generate-secret';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TranslatorInterface
|
* @var TranslatorInterface
|
||||||
*/
|
*/
|
||||||
@@ -26,7 +29,7 @@ class GenerateSecretCommand extends Command
|
|||||||
|
|
||||||
public function configure()
|
public function configure()
|
||||||
{
|
{
|
||||||
$this->setName('config:generate-secret')
|
$this->setName(self::NAME)
|
||||||
->setDescription($this->translator->translate(
|
->setDescription($this->translator->translate(
|
||||||
'Generates a random secret string that can be used for JWT token encryption'
|
'Generates a random secret string that can be used for JWT token encryption'
|
||||||
));
|
));
|
||||||
@@ -35,6 +38,8 @@ class GenerateSecretCommand extends Command
|
|||||||
public function execute(InputInterface $input, OutputInterface $output)
|
public function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
$secret = $this->generateRandomString(32);
|
$secret = $this->generateRandomString(32);
|
||||||
$output->writeln($this->translator->translate('Secret key:') . sprintf(' <info>%s</info>', $secret));
|
(new SymfonyStyle($input, $output))->success(
|
||||||
|
sprintf($this->translator->translate('Secret key: "%s"'), $secret)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,22 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Install;
|
namespace Shlinkio\Shlink\CLI\Command\Install;
|
||||||
|
|
||||||
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerPluginManagerInterface;
|
use Psr\Container\ContainerExceptionInterface;
|
||||||
|
use Psr\Container\NotFoundExceptionInterface;
|
||||||
|
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerManagerInterface;
|
||||||
use Shlinkio\Shlink\CLI\Install\Plugin;
|
use Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||||
use Symfony\Component\Console\Exception\LogicException;
|
use Symfony\Component\Console\Exception\LogicException;
|
||||||
use Symfony\Component\Console\Exception\RuntimeException;
|
use Symfony\Component\Console\Exception\RuntimeException;
|
||||||
use Symfony\Component\Console\Helper\ProcessHelper;
|
use Symfony\Component\Console\Helper\ProcessHelper;
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\Console\Question\Question;
|
|
||||||
use Symfony\Component\Filesystem\Exception\IOException;
|
use Symfony\Component\Filesystem\Exception\IOException;
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use Symfony\Component\Process\PhpExecutableFinder;
|
||||||
use Zend\Config\Writer\WriterInterface;
|
use Zend\Config\Writer\WriterInterface;
|
||||||
|
|
||||||
class InstallCommand extends Command
|
class InstallCommand extends Command
|
||||||
@@ -24,17 +26,9 @@ class InstallCommand extends Command
|
|||||||
const GENERATED_CONFIG_PATH = 'config/params/generated_config.php';
|
const GENERATED_CONFIG_PATH = 'config/params/generated_config.php';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var InputInterface
|
* @var SymfonyStyle
|
||||||
*/
|
*/
|
||||||
private $input;
|
private $io;
|
||||||
/**
|
|
||||||
* @var OutputInterface
|
|
||||||
*/
|
|
||||||
private $output;
|
|
||||||
/**
|
|
||||||
* @var QuestionHelper
|
|
||||||
*/
|
|
||||||
private $questionHelper;
|
|
||||||
/**
|
/**
|
||||||
* @var ProcessHelper
|
* @var ProcessHelper
|
||||||
*/
|
*/
|
||||||
@@ -48,65 +42,81 @@ class InstallCommand extends Command
|
|||||||
*/
|
*/
|
||||||
private $filesystem;
|
private $filesystem;
|
||||||
/**
|
/**
|
||||||
* @var ConfigCustomizerPluginManagerInterface
|
* @var ConfigCustomizerManagerInterface
|
||||||
*/
|
*/
|
||||||
private $configCustomizers;
|
private $configCustomizers;
|
||||||
/**
|
/**
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
private $isUpdate;
|
private $isUpdate;
|
||||||
|
/**
|
||||||
|
* @var PhpExecutableFinder
|
||||||
|
*/
|
||||||
|
private $phpFinder;
|
||||||
|
/**
|
||||||
|
* @var string|bool
|
||||||
|
*/
|
||||||
|
private $phpBinary;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InstallCommand constructor.
|
* InstallCommand constructor.
|
||||||
* @param WriterInterface $configWriter
|
* @param WriterInterface $configWriter
|
||||||
* @param Filesystem $filesystem
|
* @param Filesystem $filesystem
|
||||||
|
* @param ConfigCustomizerManagerInterface $configCustomizers
|
||||||
* @param bool $isUpdate
|
* @param bool $isUpdate
|
||||||
|
* @param PhpExecutableFinder|null $phpFinder
|
||||||
* @throws LogicException
|
* @throws LogicException
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
WriterInterface $configWriter,
|
WriterInterface $configWriter,
|
||||||
Filesystem $filesystem,
|
Filesystem $filesystem,
|
||||||
ConfigCustomizerPluginManagerInterface $configCustomizers,
|
ConfigCustomizerManagerInterface $configCustomizers,
|
||||||
$isUpdate = false
|
bool $isUpdate = false,
|
||||||
|
PhpExecutableFinder $phpFinder = null
|
||||||
) {
|
) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
$this->configWriter = $configWriter;
|
$this->configWriter = $configWriter;
|
||||||
$this->isUpdate = $isUpdate;
|
$this->isUpdate = $isUpdate;
|
||||||
$this->filesystem = $filesystem;
|
$this->filesystem = $filesystem;
|
||||||
$this->configCustomizers = $configCustomizers;
|
$this->configCustomizers = $configCustomizers;
|
||||||
|
$this->phpFinder = $phpFinder ?: new PhpExecutableFinder();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configure()
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this
|
$this
|
||||||
->setName('shlink:install')
|
->setName('shlink:install')
|
||||||
->setDescription('Installs or updates Shlink');
|
->setDescription('Installs or updates Shlink');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(InputInterface $input, OutputInterface $output)
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @return void
|
||||||
|
* @throws ContainerExceptionInterface
|
||||||
|
* @throws NotFoundExceptionInterface
|
||||||
|
*/
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): void
|
||||||
{
|
{
|
||||||
$this->input = $input;
|
$this->io = new SymfonyStyle($input, $output);
|
||||||
$this->output = $output;
|
|
||||||
$this->questionHelper = $this->getHelper('question');
|
|
||||||
$this->processHelper = $this->getHelper('process');
|
|
||||||
|
|
||||||
$output->writeln([
|
$this->io->writeln([
|
||||||
'<info>Welcome to Shlink!!</info>',
|
'<info>Welcome to Shlink!!</info>',
|
||||||
'This will guide you through the installation process.',
|
'This will guide you through the installation process.',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Check if a cached config file exists and drop it if so
|
// Check if a cached config file exists and drop it if so
|
||||||
if ($this->filesystem->exists('data/cache/app_config.php')) {
|
if ($this->filesystem->exists('data/cache/app_config.php')) {
|
||||||
$output->write('Deleting old cached config...');
|
$this->io->write('Deleting old cached config...');
|
||||||
try {
|
try {
|
||||||
$this->filesystem->remove('data/cache/app_config.php');
|
$this->filesystem->remove('data/cache/app_config.php');
|
||||||
$output->writeln(' <info>Success</info>');
|
$this->io->writeln(' <info>Success</info>');
|
||||||
} catch (IOException $e) {
|
} catch (IOException $e) {
|
||||||
$output->writeln(
|
$this->io->error(
|
||||||
' <error>Failed!</error> You will have to manually delete the data/cache/app_config.php file to get'
|
'Failed! You will have to manually delete the data/cache/app_config.php file to'
|
||||||
. ' new config applied.'
|
. ' get new config applied.'
|
||||||
);
|
);
|
||||||
if ($output->isVerbose()) {
|
if ($this->io->isVerbose()) {
|
||||||
$this->getApplication()->renderException($e, $output);
|
$this->getApplication()->renderException($e, $output);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -118,56 +128,65 @@ class InstallCommand extends Command
|
|||||||
|
|
||||||
// Ask for custom config params
|
// Ask for custom config params
|
||||||
foreach ([
|
foreach ([
|
||||||
Plugin\DatabaseConfigCustomizerPlugin::class,
|
Plugin\DatabaseConfigCustomizer::class,
|
||||||
Plugin\UrlShortenerConfigCustomizerPlugin::class,
|
Plugin\UrlShortenerConfigCustomizer::class,
|
||||||
Plugin\LanguageConfigCustomizerPlugin::class,
|
Plugin\LanguageConfigCustomizer::class,
|
||||||
Plugin\ApplicationConfigCustomizerPlugin::class,
|
Plugin\ApplicationConfigCustomizer::class,
|
||||||
] as $pluginName) {
|
] as $pluginName) {
|
||||||
/** @var Plugin\ConfigCustomizerPluginInterface $configCustomizer */
|
/** @var Plugin\ConfigCustomizerInterface $configCustomizer */
|
||||||
$configCustomizer = $this->configCustomizers->get($pluginName);
|
$configCustomizer = $this->configCustomizers->get($pluginName);
|
||||||
$configCustomizer->process($input, $output, $config);
|
$configCustomizer->process($this->io, $config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate config params files
|
// Generate config params files
|
||||||
$this->configWriter->toFile(self::GENERATED_CONFIG_PATH, $config->getArrayCopy(), false);
|
$this->configWriter->toFile(self::GENERATED_CONFIG_PATH, $config->getArrayCopy(), false);
|
||||||
$output->writeln(['<info>Custom configuration properly generated!</info>', '']);
|
$this->io->writeln(['<info>Custom configuration properly generated!</info>', '']);
|
||||||
|
|
||||||
// If current command is not update, generate database
|
// If current command is not update, generate database
|
||||||
if (! $this->isUpdate) {
|
if (! $this->isUpdate) {
|
||||||
$this->output->writeln('Initializing database...');
|
$this->io->write('Initializing database...');
|
||||||
if (! $this->runCommand(
|
if (! $this->runPhpCommand(
|
||||||
'php vendor/bin/doctrine.php orm:schema-tool:create',
|
'vendor/doctrine/orm/bin/doctrine.php orm:schema-tool:create',
|
||||||
'Error generating database.'
|
'Error generating database.',
|
||||||
|
$output
|
||||||
)) {
|
)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run database migrations
|
// Run database migrations
|
||||||
$output->writeln('Updating database...');
|
$this->io->write('Updating database...');
|
||||||
if (! $this->runCommand('php vendor/bin/doctrine-migrations migrations:migrate', 'Error updating database.')) {
|
if (! $this->runPhpCommand(
|
||||||
|
'vendor/doctrine/migrations/bin/doctrine-migrations.php migrations:migrate',
|
||||||
|
'Error updating database.',
|
||||||
|
$output
|
||||||
|
)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate proxies
|
// Generate proxies
|
||||||
$output->writeln('Generating proxies...');
|
$this->io->write('Generating proxies...');
|
||||||
if (! $this->runCommand('php vendor/bin/doctrine.php orm:generate-proxies', 'Error generating proxies.')) {
|
if (! $this->runPhpCommand(
|
||||||
|
'vendor/doctrine/orm/bin/doctrine.php orm:generate-proxies',
|
||||||
|
'Error generating proxies.',
|
||||||
|
$output
|
||||||
|
)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->io->success('Installation complete!');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return CustomizableAppConfig
|
* @return CustomizableAppConfig
|
||||||
* @throws RuntimeException
|
* @throws RuntimeException
|
||||||
*/
|
*/
|
||||||
private function importConfig()
|
private function importConfig(): CustomizableAppConfig
|
||||||
{
|
{
|
||||||
$config = new CustomizableAppConfig();
|
$config = new CustomizableAppConfig();
|
||||||
|
|
||||||
// Ask the user if he/she wants to import an older configuration
|
// Ask the user if he/she wants to import an older configuration
|
||||||
$importConfig = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
|
$importConfig = $this->io->confirm('Do you want to import configuration from previous installation?');
|
||||||
'<question>Do you want to import previous configuration? (Y/n):</question> '
|
|
||||||
));
|
|
||||||
if (! $importConfig) {
|
if (! $importConfig) {
|
||||||
return $config;
|
return $config;
|
||||||
}
|
}
|
||||||
@@ -175,17 +194,16 @@ class InstallCommand extends Command
|
|||||||
// Ask the user for the older shlink path
|
// Ask the user for the older shlink path
|
||||||
$keepAsking = true;
|
$keepAsking = true;
|
||||||
do {
|
do {
|
||||||
$config->setImportedInstallationPath($this->ask(
|
$config->setImportedInstallationPath($this->io->ask(
|
||||||
'Previous shlink installation path from which to import config'
|
'Previous shlink installation path from which to import config'
|
||||||
));
|
));
|
||||||
$configFile = $config->getImportedInstallationPath() . '/' . self::GENERATED_CONFIG_PATH;
|
$configFile = $config->getImportedInstallationPath() . '/' . self::GENERATED_CONFIG_PATH;
|
||||||
$configExists = $this->filesystem->exists($configFile);
|
$configExists = $this->filesystem->exists($configFile);
|
||||||
|
|
||||||
if (! $configExists) {
|
if (! $configExists) {
|
||||||
$keepAsking = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
|
$keepAsking = $this->io->confirm(
|
||||||
'Provided path does not seem to be a valid shlink root path. '
|
'Provided path does not seem to be a valid shlink root path. Do you want to try another path?'
|
||||||
. '<question>Do you want to try another path? (Y/n):</question> '
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
} while (! $configExists && $keepAsking);
|
} while (! $configExists && $keepAsking);
|
||||||
|
|
||||||
@@ -199,51 +217,35 @@ class InstallCommand extends Command
|
|||||||
return $config;
|
return $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $text
|
|
||||||
* @param string|null $default
|
|
||||||
* @param bool $allowEmpty
|
|
||||||
* @return string
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
private function ask($text, $default = null, $allowEmpty = false)
|
|
||||||
{
|
|
||||||
if ($default !== null) {
|
|
||||||
$text .= ' (defaults to ' . $default . ')';
|
|
||||||
}
|
|
||||||
do {
|
|
||||||
$value = $this->questionHelper->ask($this->input, $this->output, new Question(
|
|
||||||
'<question>' . $text . ':</question> ',
|
|
||||||
$default
|
|
||||||
));
|
|
||||||
if (empty($value) && ! $allowEmpty) {
|
|
||||||
$this->output->writeln('<error>Value can\'t be empty</error>');
|
|
||||||
}
|
|
||||||
} while (empty($value) && $default === null && ! $allowEmpty);
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $command
|
* @param string $command
|
||||||
* @param string $errorMessage
|
* @param string $errorMessage
|
||||||
|
* @param OutputInterface $output
|
||||||
* @return bool
|
* @return bool
|
||||||
|
* @throws LogicException
|
||||||
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
private function runCommand($command, $errorMessage)
|
private function runPhpCommand($command, $errorMessage, OutputInterface $output): bool
|
||||||
{
|
{
|
||||||
$process = $this->processHelper->run($this->output, $command);
|
if ($this->processHelper === null) {
|
||||||
|
$this->processHelper = $this->getHelper('process');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->phpBinary === null) {
|
||||||
|
$this->phpBinary = $this->phpFinder->find(false) ?: 'php';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->io->writeln('Running "' . sprintf('%s %s', $this->phpBinary, $command) . '"');
|
||||||
|
$process = $this->processHelper->run($output, sprintf('%s %s', $this->phpBinary, $command));
|
||||||
if ($process->isSuccessful()) {
|
if ($process->isSuccessful()) {
|
||||||
$this->output->writeln(' <info>Success!</info>');
|
$this->io->writeln(' <info>Success!</info>');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->output->isVerbose()) {
|
if (! $this->io->isVerbose()) {
|
||||||
return false;
|
$this->io->error($errorMessage . ' Run this command with -vvv to see specific error info.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->output->writeln(
|
|
||||||
' <error>' . $errorMessage . '</error> Run this command with -vvv to see specific error info.'
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,13 @@ use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
|||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class GeneratePreviewCommand extends Command
|
class GeneratePreviewCommand extends Command
|
||||||
{
|
{
|
||||||
|
const NAME = 'shortcode:process-previews';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var PreviewGeneratorInterface
|
* @var PreviewGeneratorInterface
|
||||||
*/
|
*/
|
||||||
@@ -39,7 +42,7 @@ class GeneratePreviewCommand extends Command
|
|||||||
|
|
||||||
public function configure()
|
public function configure()
|
||||||
{
|
{
|
||||||
$this->setName('shortcode:process-previews')
|
$this->setName(self::NAME)
|
||||||
->setDescription(
|
->setDescription(
|
||||||
$this->translator->translate(
|
$this->translator->translate(
|
||||||
'Processes and generates the previews for every URL, improving performance for later web requests.'
|
'Processes and generates the previews for every URL, improving performance for later web requests.'
|
||||||
@@ -59,22 +62,20 @@ class GeneratePreviewCommand extends Command
|
|||||||
}
|
}
|
||||||
} while ($page <= $shortUrls->count());
|
} while ($page <= $shortUrls->count());
|
||||||
|
|
||||||
$output->writeln('<info>' . $this->translator->translate('Finished processing all URLs') . '</info>');
|
(new SymfonyStyle($input, $output))->success($this->translator->translate('Finished processing all URLs'));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function processUrl($url, OutputInterface $output)
|
protected function processUrl($url, OutputInterface $output)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$output->write(sprintf($this->translator->translate('Processing URL %s...'), $url));
|
$output->write(\sprintf($this->translator->translate('Processing URL %s...'), $url));
|
||||||
$this->previewGenerator->generatePreview($url);
|
$this->previewGenerator->generatePreview($url);
|
||||||
$output->writeln($this->translator->translate(' <info>Success!</info>'));
|
$output->writeln($this->translator->translate(' <info>Success!</info>'));
|
||||||
} catch (PreviewGenerationException $e) {
|
} catch (PreviewGenerationException $e) {
|
||||||
$messages = [' <error>' . $this->translator->translate('Error') . '</error>'];
|
$output->writeln(' <error>' . $this->translator->translate('Error') . '</error>');
|
||||||
if ($output->isVerbose()) {
|
if ($output->isVerbose()) {
|
||||||
$messages[] = '<error>' . $e->__toString() . '</error>';
|
$this->getApplication()->renderException($e, $output);
|
||||||
}
|
}
|
||||||
|
|
||||||
$output->writeln($messages);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,18 @@ use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
|||||||
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Question\Question;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Zend\Diactoros\Uri;
|
use Zend\Diactoros\Uri;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class GenerateShortcodeCommand extends Command
|
class GenerateShortcodeCommand extends Command
|
||||||
{
|
{
|
||||||
|
const NAME = 'shortcode:generate';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var UrlShortenerInterface
|
* @var UrlShortenerInterface
|
||||||
*/
|
*/
|
||||||
@@ -44,7 +45,7 @@ class GenerateShortcodeCommand extends Command
|
|||||||
|
|
||||||
public function configure()
|
public function configure()
|
||||||
{
|
{
|
||||||
$this->setName('shortcode:generate')
|
$this->setName(self::NAME)
|
||||||
->setDescription(
|
->setDescription(
|
||||||
$this->translator->translate('Generates a short code for provided URL and returns the short URL')
|
$this->translator->translate('Generates a short code for provided URL and returns the short URL')
|
||||||
)
|
)
|
||||||
@@ -73,19 +74,15 @@ class GenerateShortcodeCommand extends Command
|
|||||||
|
|
||||||
public function interact(InputInterface $input, OutputInterface $output)
|
public function interact(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
$longUrl = $input->getArgument('longUrl');
|
$longUrl = $input->getArgument('longUrl');
|
||||||
if (! empty($longUrl)) {
|
if (! empty($longUrl)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var QuestionHelper $helper */
|
$longUrl = $io->ask(
|
||||||
$helper = $this->getHelper('question');
|
$this->translator->translate('A long URL was not provided. Which URL do you want to be shortened?')
|
||||||
$question = new Question(sprintf(
|
);
|
||||||
'<question>%s</question> ',
|
|
||||||
$this->translator->translate('A long URL was not provided. Which URL do you want to shorten?:')
|
|
||||||
));
|
|
||||||
|
|
||||||
$longUrl = $helper->ask($input, $output, $question);
|
|
||||||
if (! empty($longUrl)) {
|
if (! empty($longUrl)) {
|
||||||
$input->setArgument('longUrl', $longUrl);
|
$input->setArgument('longUrl', $longUrl);
|
||||||
}
|
}
|
||||||
@@ -93,23 +90,24 @@ class GenerateShortcodeCommand extends Command
|
|||||||
|
|
||||||
public function execute(InputInterface $input, OutputInterface $output)
|
public function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
$longUrl = $input->getArgument('longUrl');
|
$longUrl = $input->getArgument('longUrl');
|
||||||
|
if (empty($longUrl)) {
|
||||||
|
$io->error($this->translator->translate('A URL was not provided!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$tags = $input->getOption('tags');
|
$tags = $input->getOption('tags');
|
||||||
$processedTags = [];
|
$processedTags = [];
|
||||||
foreach ($tags as $key => $tag) {
|
foreach ($tags as $key => $tag) {
|
||||||
$explodedTags = explode(',', $tag);
|
$explodedTags = \explode(',', $tag);
|
||||||
$processedTags = array_merge($processedTags, $explodedTags);
|
$processedTags = \array_merge($processedTags, $explodedTags);
|
||||||
}
|
}
|
||||||
$tags = $processedTags;
|
$tags = $processedTags;
|
||||||
$customSlug = $input->getOption('customSlug');
|
$customSlug = $input->getOption('customSlug');
|
||||||
$maxVisits = $input->getOption('maxVisits');
|
$maxVisits = $input->getOption('maxVisits');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (! isset($longUrl)) {
|
|
||||||
$output->writeln(sprintf('<error>%s</error>', $this->translator->translate('A URL was not provided!')));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$shortCode = $this->urlShortener->urlToShortCode(
|
$shortCode = $this->urlShortener->urlToShortCode(
|
||||||
new Uri($longUrl),
|
new Uri($longUrl),
|
||||||
$tags,
|
$tags,
|
||||||
@@ -122,22 +120,20 @@ class GenerateShortcodeCommand extends Command
|
|||||||
->withScheme($this->domainConfig['schema'])
|
->withScheme($this->domainConfig['schema'])
|
||||||
->withHost($this->domainConfig['hostname']);
|
->withHost($this->domainConfig['hostname']);
|
||||||
|
|
||||||
$output->writeln([
|
$io->writeln([
|
||||||
sprintf('%s <info>%s</info>', $this->translator->translate('Processed URL:'), $longUrl),
|
\sprintf('%s <info>%s</info>', $this->translator->translate('Processed long URL:'), $longUrl),
|
||||||
sprintf('%s <info>%s</info>', $this->translator->translate('Generated URL:'), $shortUrl),
|
\sprintf('%s <info>%s</info>', $this->translator->translate('Generated short URL:'), $shortUrl),
|
||||||
]);
|
]);
|
||||||
} catch (InvalidUrlException $e) {
|
} catch (InvalidUrlException $e) {
|
||||||
$output->writeln(sprintf(
|
$io->error(\sprintf(
|
||||||
'<error>' . $this->translator->translate(
|
$this->translator->translate('Provided URL "%s" is invalid. Try with a different one.'),
|
||||||
'Provided URL "%s" is invalid. Try with a different one.'
|
|
||||||
) . '</error>',
|
|
||||||
$longUrl
|
$longUrl
|
||||||
));
|
));
|
||||||
} catch (NonUniqueSlugException $e) {
|
} catch (NonUniqueSlugException $e) {
|
||||||
$output->writeln(sprintf(
|
$io->error(\sprintf(
|
||||||
'<error>' . $this->translator->translate(
|
$this->translator->translate(
|
||||||
'Provided slug "%s" is already in use by another URL. Try with a different one.'
|
'Provided slug "%s" is already in use by another URL. Try with a different one.'
|
||||||
) . '</error>',
|
),
|
||||||
$customSlug
|
$customSlug
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
|||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
|
||||||
use Symfony\Component\Console\Helper\Table;
|
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Question\Question;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class GetVisitsCommand extends Command
|
class GetVisitsCommand extends Command
|
||||||
{
|
{
|
||||||
|
const NAME = 'shortcode:visits';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var VisitsTrackerInterface
|
* @var VisitsTrackerInterface
|
||||||
*/
|
*/
|
||||||
@@ -30,12 +30,12 @@ class GetVisitsCommand extends Command
|
|||||||
{
|
{
|
||||||
$this->visitsTracker = $visitsTracker;
|
$this->visitsTracker = $visitsTracker;
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
parent::__construct(null);
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configure()
|
public function configure()
|
||||||
{
|
{
|
||||||
$this->setName('shortcode:visits')
|
$this->setName(self::NAME)
|
||||||
->setDescription(
|
->setDescription(
|
||||||
$this->translator->translate('Returns the detailed visits information for provided short code')
|
$this->translator->translate('Returns the detailed visits information for provided short code')
|
||||||
)
|
)
|
||||||
@@ -65,14 +65,10 @@ class GetVisitsCommand extends Command
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var QuestionHelper $helper */
|
$io = new SymfonyStyle($input, $output);
|
||||||
$helper = $this->getHelper('question');
|
$shortCode = $io->ask(
|
||||||
$question = new Question(sprintf(
|
$this->translator->translate('A short code was not provided. Which short code do you want to use?')
|
||||||
'<question>%s</question> ',
|
);
|
||||||
$this->translator->translate('A short code was not provided. Which short code do you want to use?:')
|
|
||||||
));
|
|
||||||
|
|
||||||
$shortCode = $helper->ask($input, $output, $question);
|
|
||||||
if (! empty($shortCode)) {
|
if (! empty($shortCode)) {
|
||||||
$input->setArgument('shortCode', $shortCode);
|
$input->setArgument('shortCode', $shortCode);
|
||||||
}
|
}
|
||||||
@@ -80,33 +76,32 @@ class GetVisitsCommand extends Command
|
|||||||
|
|
||||||
public function execute(InputInterface $input, OutputInterface $output)
|
public function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
$shortCode = $input->getArgument('shortCode');
|
$shortCode = $input->getArgument('shortCode');
|
||||||
$startDate = $this->getDateOption($input, 'startDate');
|
$startDate = $this->getDateOption($input, 'startDate');
|
||||||
$endDate = $this->getDateOption($input, 'endDate');
|
$endDate = $this->getDateOption($input, 'endDate');
|
||||||
|
|
||||||
$visits = $this->visitsTracker->info($shortCode, new DateRange($startDate, $endDate));
|
$visits = $this->visitsTracker->info($shortCode, new DateRange($startDate, $endDate));
|
||||||
$table = new Table($output);
|
$rows = [];
|
||||||
$table->setHeaders([
|
|
||||||
$this->translator->translate('Referer'),
|
|
||||||
$this->translator->translate('Date'),
|
|
||||||
$this->translator->translate('Remote Address'),
|
|
||||||
$this->translator->translate('User agent'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
foreach ($visits as $row) {
|
foreach ($visits as $row) {
|
||||||
$rowData = $row->jsonSerialize();
|
$rowData = $row->jsonSerialize();
|
||||||
// Unset location info
|
// Unset location info
|
||||||
unset($rowData['visitLocation']);
|
unset($rowData['visitLocation']);
|
||||||
|
|
||||||
$table->addRow(array_values($rowData));
|
$rows[] = \array_values($rowData);
|
||||||
}
|
}
|
||||||
$table->render();
|
$io->table([
|
||||||
|
$this->translator->translate('Referer'),
|
||||||
|
$this->translator->translate('Date'),
|
||||||
|
$this->translator->translate('Remote Address'),
|
||||||
|
$this->translator->translate('User agent'),
|
||||||
|
], $rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getDateOption(InputInterface $input, $key)
|
protected function getDateOption(InputInterface $input, $key)
|
||||||
{
|
{
|
||||||
$value = $input->getOption($key);
|
$value = $input->getOption($key);
|
||||||
if (isset($value)) {
|
if (! empty($value)) {
|
||||||
$value = new \DateTime($value);
|
$value = new \DateTime($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,18 +7,18 @@ use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter;
|
|||||||
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
|
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
|
||||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
|
||||||
use Symfony\Component\Console\Helper\Table;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class ListShortcodesCommand extends Command
|
class ListShortcodesCommand extends Command
|
||||||
{
|
{
|
||||||
use PaginatorUtilsTrait;
|
use PaginatorUtilsTrait;
|
||||||
|
|
||||||
|
const NAME = 'shortcode:list';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var ShortUrlServiceInterface
|
* @var ShortUrlServiceInterface
|
||||||
*/
|
*/
|
||||||
@@ -32,12 +32,12 @@ class ListShortcodesCommand extends Command
|
|||||||
{
|
{
|
||||||
$this->shortUrlService = $shortUrlService;
|
$this->shortUrlService = $shortUrlService;
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
parent::__construct(null);
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configure()
|
public function configure()
|
||||||
{
|
{
|
||||||
$this->setName('shortcode:list')
|
$this->setName(self::NAME)
|
||||||
->setDescription($this->translator->translate('List all short URLs'))
|
->setDescription($this->translator->translate('List all short URLs'))
|
||||||
->addOption(
|
->addOption(
|
||||||
'page',
|
'page',
|
||||||
@@ -81,19 +81,16 @@ class ListShortcodesCommand extends Command
|
|||||||
|
|
||||||
public function execute(InputInterface $input, OutputInterface $output)
|
public function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
$page = (int) $input->getOption('page');
|
$page = (int) $input->getOption('page');
|
||||||
$searchTerm = $input->getOption('searchTerm');
|
$searchTerm = $input->getOption('searchTerm');
|
||||||
$tags = $input->getOption('tags');
|
$tags = $input->getOption('tags');
|
||||||
$tags = ! empty($tags) ? explode(',', $tags) : [];
|
$tags = ! empty($tags) ? \explode(',', $tags) : [];
|
||||||
$showTags = $input->getOption('showTags');
|
$showTags = $input->getOption('showTags');
|
||||||
|
|
||||||
/** @var QuestionHelper $helper */
|
|
||||||
$helper = $this->getHelper('question');
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
$result = $this->shortUrlService->listShortUrls($page, $searchTerm, $tags, $this->processOrderBy($input));
|
$result = $this->shortUrlService->listShortUrls($page, $searchTerm, $tags, $this->processOrderBy($input));
|
||||||
$page++;
|
$page++;
|
||||||
$table = new Table($output);
|
|
||||||
|
|
||||||
$headers = [
|
$headers = [
|
||||||
$this->translator->translate('Short code'),
|
$this->translator->translate('Short code'),
|
||||||
@@ -104,8 +101,8 @@ class ListShortcodesCommand extends Command
|
|||||||
if ($showTags) {
|
if ($showTags) {
|
||||||
$headers[] = $this->translator->translate('Tags');
|
$headers[] = $this->translator->translate('Tags');
|
||||||
}
|
}
|
||||||
$table->setHeaders($headers);
|
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
foreach ($result as $row) {
|
foreach ($result as $row) {
|
||||||
$shortUrl = $row->jsonSerialize();
|
$shortUrl = $row->jsonSerialize();
|
||||||
if ($showTags) {
|
if ($showTags) {
|
||||||
@@ -118,27 +115,23 @@ class ListShortcodesCommand extends Command
|
|||||||
unset($shortUrl['tags']);
|
unset($shortUrl['tags']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$table->addRow(array_values($shortUrl));
|
$rows[] = \array_values($shortUrl);
|
||||||
}
|
}
|
||||||
$table->render();
|
$io->table($headers, $rows);
|
||||||
|
|
||||||
if ($this->isLastPage($result)) {
|
if ($this->isLastPage($result)) {
|
||||||
$continue = false;
|
$continue = false;
|
||||||
$output->writeln(
|
$io->success($this->translator->translate('Short codes properly listed'));
|
||||||
sprintf('<info>%s</info>', $this->translator->translate('You have reached last page'))
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
$continue = $helper->ask($input, $output, new ConfirmationQuestion(
|
$continue = $io->confirm(
|
||||||
sprintf('<question>' . $this->translator->translate(
|
\sprintf($this->translator->translate('Continue with page') . ' <options=bold>%s</>?', $page),
|
||||||
'Continue with page'
|
|
||||||
) . ' <bg=cyan;options=bold>%s</>? (y/N)</question> ', $page),
|
|
||||||
false
|
false
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
} while ($continue);
|
} while ($continue);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function processOrderBy(InputInterface $input)
|
private function processOrderBy(InputInterface $input)
|
||||||
{
|
{
|
||||||
$orderBy = $input->getOption('orderBy');
|
$orderBy = $input->getOption('orderBy');
|
||||||
if (empty($orderBy)) {
|
if (empty($orderBy)) {
|
||||||
|
|||||||
@@ -7,15 +7,16 @@ use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
|||||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Question\Question;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class ResolveUrlCommand extends Command
|
class ResolveUrlCommand extends Command
|
||||||
{
|
{
|
||||||
|
const NAME = 'shortcode:parse';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var UrlShortenerInterface
|
* @var UrlShortenerInterface
|
||||||
*/
|
*/
|
||||||
@@ -34,7 +35,7 @@ class ResolveUrlCommand extends Command
|
|||||||
|
|
||||||
public function configure()
|
public function configure()
|
||||||
{
|
{
|
||||||
$this->setName('shortcode:parse')
|
$this->setName(self::NAME)
|
||||||
->setDescription($this->translator->translate('Returns the long URL behind a short code'))
|
->setDescription($this->translator->translate('Returns the long URL behind a short code'))
|
||||||
->addArgument(
|
->addArgument(
|
||||||
'shortCode',
|
'shortCode',
|
||||||
@@ -50,14 +51,10 @@ class ResolveUrlCommand extends Command
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var QuestionHelper $helper */
|
$io = new SymfonyStyle($input, $output);
|
||||||
$helper = $this->getHelper('question');
|
$shortCode = $io->ask(
|
||||||
$question = new Question(sprintf(
|
$this->translator->translate('A short code was not provided. Which short code do you want to parse?')
|
||||||
'<question>%s</question> ',
|
);
|
||||||
$this->translator->translate('A short code was not provided. Which short code do you want to parse?:')
|
|
||||||
));
|
|
||||||
|
|
||||||
$shortCode = $helper->ask($input, $output, $question);
|
|
||||||
if (! empty($shortCode)) {
|
if (! empty($shortCode)) {
|
||||||
$input->setArgument('shortCode', $shortCode);
|
$input->setArgument('shortCode', $shortCode);
|
||||||
}
|
}
|
||||||
@@ -65,27 +62,20 @@ class ResolveUrlCommand extends Command
|
|||||||
|
|
||||||
public function execute(InputInterface $input, OutputInterface $output)
|
public function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
$shortCode = $input->getArgument('shortCode');
|
$shortCode = $input->getArgument('shortCode');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$longUrl = $this->urlShortener->shortCodeToUrl($shortCode);
|
$longUrl = $this->urlShortener->shortCodeToUrl($shortCode);
|
||||||
if (! isset($longUrl)) {
|
$output->writeln(\sprintf('%s <info>%s</info>', $this->translator->translate('Long URL:'), $longUrl));
|
||||||
$output->writeln(sprintf(
|
|
||||||
'<error>' . $this->translator->translate('No URL found for short code "%s"') . '</error>',
|
|
||||||
$shortCode
|
|
||||||
));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$output->writeln(sprintf('%s <info>%s</info>', $this->translator->translate('Long URL:'), $longUrl));
|
|
||||||
} catch (InvalidShortCodeException $e) {
|
} catch (InvalidShortCodeException $e) {
|
||||||
$output->writeln(sprintf('<error>' . $this->translator->translate(
|
$io->error(
|
||||||
'Provided short code "%s" has an invalid format.'
|
\sprintf($this->translator->translate('Provided short code "%s" has an invalid format.'), $shortCode)
|
||||||
) . '</error>', $shortCode));
|
);
|
||||||
} catch (EntityDoesNotExistException $e) {
|
} catch (EntityDoesNotExistException $e) {
|
||||||
$output->writeln(sprintf('<error>' . $this->translator->translate(
|
$io->error(
|
||||||
'Provided short code "%s" could not be found.'
|
\sprintf($this->translator->translate('Provided short code "%s" could not be found.'), $shortCode)
|
||||||
) . '</error>', $shortCode));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ use Symfony\Component\Console\Command\Command;
|
|||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class CreateTagCommand extends Command
|
class CreateTagCommand extends Command
|
||||||
{
|
{
|
||||||
|
const NAME = 'tag:create';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TagServiceInterface
|
* @var TagServiceInterface
|
||||||
*/
|
*/
|
||||||
@@ -31,7 +34,7 @@ class CreateTagCommand extends Command
|
|||||||
protected function configure()
|
protected function configure()
|
||||||
{
|
{
|
||||||
$this
|
$this
|
||||||
->setName('tag:create')
|
->setName(self::NAME)
|
||||||
->setDescription($this->translator->translate('Creates one or more tags.'))
|
->setDescription($this->translator->translate('Creates one or more tags.'))
|
||||||
->addOption(
|
->addOption(
|
||||||
'name',
|
'name',
|
||||||
@@ -43,19 +46,15 @@ class CreateTagCommand extends Command
|
|||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output)
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
$tagNames = $input->getOption('name');
|
$tagNames = $input->getOption('name');
|
||||||
|
|
||||||
if (empty($tagNames)) {
|
if (empty($tagNames)) {
|
||||||
$output->writeln(sprintf(
|
$io->warning($this->translator->translate('You have to provide at least one tag name'));
|
||||||
'<comment>%s</comment>',
|
|
||||||
$this->translator->translate('You have to provide at least one tag name')
|
|
||||||
));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->tagService->createTags($tagNames);
|
$this->tagService->createTags($tagNames);
|
||||||
$output->writeln($this->translator->translate('Created tags') . sprintf(': ["<info>%s</info>"]', implode(
|
$io->success($this->translator->translate('Tags properly created'));
|
||||||
'</info>", "<info>',
|
|
||||||
$tagNames
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ use Symfony\Component\Console\Command\Command;
|
|||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class DeleteTagsCommand extends Command
|
class DeleteTagsCommand extends Command
|
||||||
{
|
{
|
||||||
|
const NAME = 'tag:delete';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TagServiceInterface
|
* @var TagServiceInterface
|
||||||
*/
|
*/
|
||||||
@@ -31,7 +34,7 @@ class DeleteTagsCommand extends Command
|
|||||||
protected function configure()
|
protected function configure()
|
||||||
{
|
{
|
||||||
$this
|
$this
|
||||||
->setName('tag:delete')
|
->setName(self::NAME)
|
||||||
->setDescription($this->translator->translate('Deletes one or more tags.'))
|
->setDescription($this->translator->translate('Deletes one or more tags.'))
|
||||||
->addOption(
|
->addOption(
|
||||||
'name',
|
'name',
|
||||||
@@ -43,19 +46,15 @@ class DeleteTagsCommand extends Command
|
|||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output)
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
$tagNames = $input->getOption('name');
|
$tagNames = $input->getOption('name');
|
||||||
|
|
||||||
if (empty($tagNames)) {
|
if (empty($tagNames)) {
|
||||||
$output->writeln(sprintf(
|
$io->warning($this->translator->translate('You have to provide at least one tag name'));
|
||||||
'<comment>%s</comment>',
|
|
||||||
$this->translator->translate('You have to provide at least one tag name')
|
|
||||||
));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->tagService->deleteTags($tagNames);
|
$this->tagService->deleteTags($tagNames);
|
||||||
$output->writeln($this->translator->translate('Deleted tags') . sprintf(': ["<info>%s</info>"]', implode(
|
$io->success($this->translator->translate('Tags properly deleted'));
|
||||||
'</info>", "<info>',
|
|
||||||
$tagNames
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ namespace Shlinkio\Shlink\CLI\Command\Tag;
|
|||||||
use Shlinkio\Shlink\Core\Entity\Tag;
|
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||||
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
|
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Helper\Table;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class ListTagsCommand extends Command
|
class ListTagsCommand extends Command
|
||||||
{
|
{
|
||||||
|
const NAME = 'tag:list';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TagServiceInterface
|
* @var TagServiceInterface
|
||||||
*/
|
*/
|
||||||
@@ -32,17 +34,14 @@ class ListTagsCommand extends Command
|
|||||||
protected function configure()
|
protected function configure()
|
||||||
{
|
{
|
||||||
$this
|
$this
|
||||||
->setName('tag:list')
|
->setName(self::NAME)
|
||||||
->setDescription($this->translator->translate('Lists existing tags.'));
|
->setDescription($this->translator->translate('Lists existing tags.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output)
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
$table = new Table($output);
|
$io = new SymfonyStyle($input, $output);
|
||||||
$table->setHeaders([$this->translator->translate('Name')])
|
$io->table([$this->translator->translate('Name')], $this->getTagsRows());
|
||||||
->setRows($this->getTagsRows());
|
|
||||||
|
|
||||||
$table->render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getTagsRows()
|
private function getTagsRows()
|
||||||
@@ -52,7 +51,7 @@ class ListTagsCommand extends Command
|
|||||||
return [[$this->translator->translate('No tags yet')]];
|
return [[$this->translator->translate('No tags yet')]];
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_map(function (Tag $tag) {
|
return \array_map(function (Tag $tag) {
|
||||||
return [$tag->getName()];
|
return [$tag->getName()];
|
||||||
}, $tags);
|
}, $tags);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,13 @@ use Symfony\Component\Console\Command\Command;
|
|||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class RenameTagCommand extends Command
|
class RenameTagCommand extends Command
|
||||||
{
|
{
|
||||||
|
const NAME = 'tag:rename';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TagServiceInterface
|
* @var TagServiceInterface
|
||||||
*/
|
*/
|
||||||
@@ -32,7 +35,7 @@ class RenameTagCommand extends Command
|
|||||||
protected function configure()
|
protected function configure()
|
||||||
{
|
{
|
||||||
$this
|
$this
|
||||||
->setName('tag:rename')
|
->setName(self::NAME)
|
||||||
->setDescription($this->translator->translate('Renames one existing tag.'))
|
->setDescription($this->translator->translate('Renames one existing tag.'))
|
||||||
->addArgument('oldName', InputArgument::REQUIRED, $this->translator->translate('Current name of the tag.'))
|
->addArgument('oldName', InputArgument::REQUIRED, $this->translator->translate('Current name of the tag.'))
|
||||||
->addArgument('newName', InputArgument::REQUIRED, $this->translator->translate('New name of the tag.'));
|
->addArgument('newName', InputArgument::REQUIRED, $this->translator->translate('New name of the tag.'));
|
||||||
@@ -40,16 +43,15 @@ class RenameTagCommand extends Command
|
|||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output)
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
$oldName = $input->getArgument('oldName');
|
$oldName = $input->getArgument('oldName');
|
||||||
$newName = $input->getArgument('newName');
|
$newName = $input->getArgument('newName');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->tagService->renameTag($oldName, $newName);
|
$this->tagService->renameTag($oldName, $newName);
|
||||||
$output->writeln(sprintf('<info>%s</info>', $this->translator->translate('Tag properly renamed.')));
|
$io->success($this->translator->translate('Tag properly renamed.'));
|
||||||
} catch (EntityDoesNotExistException $e) {
|
} catch (EntityDoesNotExistException $e) {
|
||||||
$output->writeln('<error>' . sprintf($this->translator->translate(
|
$io->error(\sprintf($this->translator->translate('A tag with name "%s" was not found'), $oldName));
|
||||||
'A tag with name "%s" was not found'
|
|
||||||
), $oldName) . '</error>');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ use Shlinkio\Shlink\Core\Service\VisitServiceInterface;
|
|||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class ProcessVisitsCommand extends Command
|
class ProcessVisitsCommand extends Command
|
||||||
{
|
{
|
||||||
const LOCALHOST = '127.0.0.1';
|
const LOCALHOST = '127.0.0.1';
|
||||||
|
const NAME = 'visit:process';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var VisitServiceInterface
|
* @var VisitServiceInterface
|
||||||
@@ -42,7 +44,7 @@ class ProcessVisitsCommand extends Command
|
|||||||
|
|
||||||
public function configure()
|
public function configure()
|
||||||
{
|
{
|
||||||
$this->setName('visit:process')
|
$this->setName(self::NAME)
|
||||||
->setDescription(
|
->setDescription(
|
||||||
$this->translator->translate('Processes visits where location is not set yet')
|
$this->translator->translate('Processes visits where location is not set yet')
|
||||||
);
|
);
|
||||||
@@ -50,13 +52,14 @@ class ProcessVisitsCommand extends Command
|
|||||||
|
|
||||||
public function execute(InputInterface $input, OutputInterface $output)
|
public function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
$visits = $this->visitService->getUnlocatedVisits();
|
$visits = $this->visitService->getUnlocatedVisits();
|
||||||
|
|
||||||
foreach ($visits as $visit) {
|
foreach ($visits as $visit) {
|
||||||
$ipAddr = $visit->getRemoteAddr();
|
$ipAddr = $visit->getRemoteAddr();
|
||||||
$output->write(sprintf('%s <info>%s</info>', $this->translator->translate('Processing IP'), $ipAddr));
|
$io->write(sprintf('%s <info>%s</info>', $this->translator->translate('Processing IP'), $ipAddr));
|
||||||
if ($ipAddr === self::LOCALHOST) {
|
if ($ipAddr === self::LOCALHOST) {
|
||||||
$output->writeln(
|
$io->writeln(
|
||||||
sprintf(' (<comment>%s</comment>)', $this->translator->translate('Ignored localhost address'))
|
sprintf(' (<comment>%s</comment>)', $this->translator->translate('Ignored localhost address'))
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
@@ -64,11 +67,13 @@ class ProcessVisitsCommand extends Command
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$result = $this->ipLocationResolver->resolveIpLocation($ipAddr);
|
$result = $this->ipLocationResolver->resolveIpLocation($ipAddr);
|
||||||
|
|
||||||
$location = new VisitLocation();
|
$location = new VisitLocation();
|
||||||
$location->exchangeArray($result);
|
$location->exchangeArray($result);
|
||||||
$visit->setVisitLocation($location);
|
$visit->setVisitLocation($location);
|
||||||
$this->visitService->saveVisit($visit);
|
$this->visitService->saveVisit($visit);
|
||||||
$output->writeln(sprintf(
|
|
||||||
|
$io->writeln(sprintf(
|
||||||
' (' . $this->translator->translate('Address located at "%s"') . ')',
|
' (' . $this->translator->translate('Address located at "%s"') . ')',
|
||||||
$location->getCityName()
|
$location->getCityName()
|
||||||
));
|
));
|
||||||
@@ -77,6 +82,6 @@ class ProcessVisitsCommand extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$output->writeln($this->translator->translate('Finished processing all IPs'));
|
$io->success($this->translator->translate('Finished processing all IPs'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,11 @@ namespace Shlinkio\Shlink\CLI\Factory;
|
|||||||
|
|
||||||
use Interop\Container\ContainerInterface;
|
use Interop\Container\ContainerInterface;
|
||||||
use Interop\Container\Exception\ContainerException;
|
use Interop\Container\Exception\ContainerException;
|
||||||
|
use Psr\Container\ContainerExceptionInterface;
|
||||||
|
use Psr\Container\NotFoundExceptionInterface;
|
||||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||||
use Symfony\Component\Console\Application as CliApp;
|
use Symfony\Component\Console\Application as CliApp;
|
||||||
|
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
|
||||||
use Zend\I18n\Translator\Translator;
|
use Zend\I18n\Translator\Translator;
|
||||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||||
@@ -20,28 +23,23 @@ class ApplicationFactory implements FactoryInterface
|
|||||||
* @param ContainerInterface $container
|
* @param ContainerInterface $container
|
||||||
* @param string $requestedName
|
* @param string $requestedName
|
||||||
* @param null|array $options
|
* @param null|array $options
|
||||||
* @return object
|
* @return CliApp
|
||||||
|
* @throws NotFoundExceptionInterface
|
||||||
|
* @throws ContainerExceptionInterface
|
||||||
* @throws ServiceNotFoundException if unable to resolve the service.
|
* @throws ServiceNotFoundException if unable to resolve the service.
|
||||||
* @throws ServiceNotCreatedException if an exception is raised when
|
* @throws ServiceNotCreatedException if an exception is raised when creating a service.
|
||||||
* creating a service.
|
|
||||||
* @throws ContainerException if any other error occurs
|
* @throws ContainerException if any other error occurs
|
||||||
*/
|
*/
|
||||||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
public function __invoke(ContainerInterface $container, $requestedName, array $options = null): CliApp
|
||||||
{
|
{
|
||||||
$config = $container->get('config')['cli'];
|
$config = $container->get('config')['cli'];
|
||||||
$appOptions = $container->get(AppOptions::class);
|
$appOptions = $container->get(AppOptions::class);
|
||||||
$translator = $container->get(Translator::class);
|
$translator = $container->get(Translator::class);
|
||||||
$translator->setLocale($config['locale']);
|
$translator->setLocale($config['locale']);
|
||||||
|
|
||||||
$commands = isset($config['commands']) ? $config['commands'] : [];
|
$commands = $config['commands'] ?? [];
|
||||||
$app = new CliApp($appOptions->getName(), $appOptions->getVersion());
|
$app = new CliApp($appOptions->getName(), $appOptions->getVersion());
|
||||||
foreach ($commands as $command) {
|
$app->setCommandLoader(new ContainerCommandLoader($container, $commands));
|
||||||
if (! $container->has($command)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$app->add($container->get($command));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $app;
|
return $app;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ namespace Shlinkio\Shlink\CLI\Factory;
|
|||||||
use Interop\Container\ContainerInterface;
|
use Interop\Container\ContainerInterface;
|
||||||
use Interop\Container\Exception\ContainerException;
|
use Interop\Container\Exception\ContainerException;
|
||||||
use Shlinkio\Shlink\CLI\Command\Install\InstallCommand;
|
use Shlinkio\Shlink\CLI\Command\Install\InstallCommand;
|
||||||
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerPluginManager;
|
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerManager;
|
||||||
use Shlinkio\Shlink\CLI\Install\Plugin;
|
use Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
use Shlinkio\Shlink\CLI\Install\Plugin\Factory\DefaultConfigCustomizerPluginFactory;
|
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
use Symfony\Component\Console\Exception\LogicException;
|
use Symfony\Component\Console\Exception\LogicException;
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
@@ -17,6 +16,7 @@ use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
|||||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||||
|
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||||
|
|
||||||
class InstallApplicationFactory implements FactoryInterface
|
class InstallApplicationFactory implements FactoryInterface
|
||||||
{
|
{
|
||||||
@@ -41,11 +41,11 @@ class InstallApplicationFactory implements FactoryInterface
|
|||||||
$command = new InstallCommand(
|
$command = new InstallCommand(
|
||||||
new PhpArray(),
|
new PhpArray(),
|
||||||
$container->get(Filesystem::class),
|
$container->get(Filesystem::class),
|
||||||
new ConfigCustomizerPluginManager($container, ['factories' => [
|
new ConfigCustomizerManager($container, ['factories' => [
|
||||||
Plugin\DatabaseConfigCustomizerPlugin::class => ConfigAbstractFactory::class,
|
Plugin\DatabaseConfigCustomizer::class => ConfigAbstractFactory::class,
|
||||||
Plugin\UrlShortenerConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class,
|
Plugin\UrlShortenerConfigCustomizer::class => InvokableFactory::class,
|
||||||
Plugin\LanguageConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class,
|
Plugin\LanguageConfigCustomizer::class => InvokableFactory::class,
|
||||||
Plugin\ApplicationConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class,
|
Plugin\ApplicationConfigCustomizer::class => InvokableFactory::class,
|
||||||
]]),
|
]]),
|
||||||
$isUpdate
|
$isUpdate
|
||||||
);
|
);
|
||||||
|
|||||||
12
module/CLI/src/Install/ConfigCustomizerManager.php
Normal file
12
module/CLI/src/Install/ConfigCustomizerManager.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerInterface;
|
||||||
|
use Zend\ServiceManager\AbstractPluginManager;
|
||||||
|
|
||||||
|
class ConfigCustomizerManager extends AbstractPluginManager implements ConfigCustomizerManagerInterface
|
||||||
|
{
|
||||||
|
protected $instanceOf = ConfigCustomizerInterface::class;
|
||||||
|
}
|
||||||
@@ -5,6 +5,6 @@ namespace Shlinkio\Shlink\CLI\Install;
|
|||||||
|
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
interface ConfigCustomizerPluginManagerInterface extends ContainerInterface
|
interface ConfigCustomizerManagerInterface extends ContainerInterface
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Install;
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerPluginInterface;
|
|
||||||
use Zend\ServiceManager\AbstractPluginManager;
|
|
||||||
|
|
||||||
class ConfigCustomizerPluginManager extends AbstractPluginManager implements ConfigCustomizerPluginManagerInterface
|
|
||||||
{
|
|
||||||
protected $instanceOf = ConfigCustomizerPluginInterface::class;
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
|
||||||
|
|
||||||
use Symfony\Component\Console\Exception\RuntimeException;
|
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Question\Question;
|
|
||||||
|
|
||||||
abstract class AbstractConfigCustomizerPlugin implements ConfigCustomizerPluginInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var QuestionHelper
|
|
||||||
*/
|
|
||||||
protected $questionHelper;
|
|
||||||
|
|
||||||
public function __construct(QuestionHelper $questionHelper)
|
|
||||||
{
|
|
||||||
$this->questionHelper = $questionHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param InputInterface $input
|
|
||||||
* @param OutputInterface $output
|
|
||||||
* @param string $text
|
|
||||||
* @param string|null $default
|
|
||||||
* @param bool $allowEmpty
|
|
||||||
* @return string
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
protected function ask(InputInterface $input, OutputInterface $output, $text, $default = null, $allowEmpty = false)
|
|
||||||
{
|
|
||||||
if ($default !== null) {
|
|
||||||
$text .= ' (defaults to ' . $default . ')';
|
|
||||||
}
|
|
||||||
do {
|
|
||||||
$value = $this->questionHelper->ask($input, $output, new Question(
|
|
||||||
'<question>' . $text . ':</question> ',
|
|
||||||
$default
|
|
||||||
));
|
|
||||||
if (empty($value) && ! $allowEmpty) {
|
|
||||||
$output->writeln('<error>Value can\'t be empty</error>');
|
|
||||||
}
|
|
||||||
} while (empty($value) && $default === null && ! $allowEmpty);
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param OutputInterface $output
|
|
||||||
* @param string $text
|
|
||||||
*/
|
|
||||||
protected function printTitle(OutputInterface $output, $text)
|
|
||||||
{
|
|
||||||
$text = trim($text);
|
|
||||||
$length = strlen($text) + 4;
|
|
||||||
$header = str_repeat('*', $length);
|
|
||||||
|
|
||||||
$output->writeln([
|
|
||||||
'',
|
|
||||||
'<info>' . $header . '</info>',
|
|
||||||
'<info>* ' . strtoupper($text) . ' *</info>',
|
|
||||||
'<info>' . $header . '</info>',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
|
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
class ApplicationConfigCustomizer implements ConfigCustomizerInterface
|
||||||
|
{
|
||||||
|
use StringUtilsTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SymfonyStyle $io
|
||||||
|
* @param CustomizableAppConfig $appConfig
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig)
|
||||||
|
{
|
||||||
|
$io->title('APPLICATION');
|
||||||
|
|
||||||
|
if ($appConfig->hasApp() && $io->confirm('Do you want to keep imported application config?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = function ($value) {
|
||||||
|
return $value;
|
||||||
|
};
|
||||||
|
$appConfig->setApp([
|
||||||
|
'SECRET' => $io->ask(
|
||||||
|
'Define a secret string that will be used to sign API tokens (leave empty to autogenerate one)',
|
||||||
|
null,
|
||||||
|
$validator
|
||||||
|
) ?: $this->generateRandomString(32),
|
||||||
|
'DISABLE_TRACK_PARAM' => $io->ask(
|
||||||
|
'Provide a parameter name that you will be able to use to disable tracking on specific request to '
|
||||||
|
. 'short URLs (leave empty and this feature won\'t be enabled)',
|
||||||
|
null,
|
||||||
|
$validator
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
|
||||||
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
|
||||||
|
|
||||||
class ApplicationConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
|
|
||||||
{
|
|
||||||
use StringUtilsTrait;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param InputInterface $input
|
|
||||||
* @param OutputInterface $output
|
|
||||||
* @param CustomizableAppConfig $appConfig
|
|
||||||
* @return void
|
|
||||||
* @throws \Symfony\Component\Console\Exception\RuntimeException
|
|
||||||
*/
|
|
||||||
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
|
|
||||||
{
|
|
||||||
$this->printTitle($output, 'APPLICATION');
|
|
||||||
|
|
||||||
if ($appConfig->hasApp() && $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
|
|
||||||
'<question>Do you want to keep imported application config? (Y/n):</question> '
|
|
||||||
))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$appConfig->setApp([
|
|
||||||
'SECRET' => $this->ask(
|
|
||||||
$input,
|
|
||||||
$output,
|
|
||||||
'Define a secret string that will be used to sign API tokens (leave empty to autogenerate one)',
|
|
||||||
null,
|
|
||||||
true
|
|
||||||
) ?: $this->generateRandomString(32),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
17
module/CLI/src/Install/Plugin/ConfigCustomizerInterface.php
Normal file
17
module/CLI/src/Install/Plugin/ConfigCustomizerInterface.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
interface ConfigCustomizerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param SymfonyStyle $io
|
||||||
|
* @param CustomizableAppConfig $appConfig
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig);
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
|
|
||||||
interface ConfigCustomizerPluginInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param InputInterface $input
|
|
||||||
* @param OutputInterface $output
|
|
||||||
* @param CustomizableAppConfig $appConfig
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig);
|
|
||||||
}
|
|
||||||
78
module/CLI/src/Install/Plugin/DatabaseConfigCustomizer.php
Normal file
78
module/CLI/src/Install/Plugin/DatabaseConfigCustomizer.php
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Symfony\Component\Filesystem\Exception\IOException;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
|
class DatabaseConfigCustomizer implements ConfigCustomizerInterface
|
||||||
|
{
|
||||||
|
const DATABASE_DRIVERS = [
|
||||||
|
'MySQL' => 'pdo_mysql',
|
||||||
|
'PostgreSQL' => 'pdo_pgsql',
|
||||||
|
'SQLite' => 'pdo_sqlite',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Filesystem
|
||||||
|
*/
|
||||||
|
private $filesystem;
|
||||||
|
|
||||||
|
public function __construct(Filesystem $filesystem)
|
||||||
|
{
|
||||||
|
$this->filesystem = $filesystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SymfonyStyle $io
|
||||||
|
* @param CustomizableAppConfig $appConfig
|
||||||
|
* @return void
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig)
|
||||||
|
{
|
||||||
|
$io->title('DATABASE');
|
||||||
|
|
||||||
|
if ($appConfig->hasDatabase() && $io->confirm('Do you want to keep imported database config?')) {
|
||||||
|
// If the user selected to keep DB config and is configured to use sqlite, copy DB file
|
||||||
|
if ($appConfig->getDatabase()['DRIVER'] === self::DATABASE_DRIVERS['SQLite']) {
|
||||||
|
try {
|
||||||
|
$this->filesystem->copy(
|
||||||
|
$appConfig->getImportedInstallationPath() . '/' . CustomizableAppConfig::SQLITE_DB_PATH,
|
||||||
|
CustomizableAppConfig::SQLITE_DB_PATH
|
||||||
|
);
|
||||||
|
} catch (IOException $e) {
|
||||||
|
$io->error('It wasn\'t possible to import the SQLite database');
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select database type
|
||||||
|
$params = [];
|
||||||
|
$databases = \array_keys(self::DATABASE_DRIVERS);
|
||||||
|
$dbType = $io->choice('Select database type', $databases, $databases[0]);
|
||||||
|
$params['DRIVER'] = self::DATABASE_DRIVERS[$dbType];
|
||||||
|
|
||||||
|
// Ask for connection params if database is not SQLite
|
||||||
|
if ($params['DRIVER'] !== self::DATABASE_DRIVERS['SQLite']) {
|
||||||
|
$params['NAME'] = $io->ask('Database name', 'shlink');
|
||||||
|
$params['USER'] = $io->ask('Database username');
|
||||||
|
$params['PASSWORD'] = $io->ask('Database password');
|
||||||
|
$params['HOST'] = $io->ask('Database host', 'localhost');
|
||||||
|
$params['PORT'] = $io->ask('Database port', $this->getDefaultDbPort($params['DRIVER']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$appConfig->setDatabase($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDefaultDbPort(string $driver): string
|
||||||
|
{
|
||||||
|
return $driver === 'pdo_mysql' ? '3306' : '5432';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
|
||||||
|
|
||||||
use Acelaya\ZsmAnnotatedServices\Annotation as DI;
|
|
||||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
|
||||||
use Symfony\Component\Console\Exception\RuntimeException;
|
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
|
||||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
|
||||||
use Symfony\Component\Filesystem\Exception\IOException;
|
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
|
||||||
|
|
||||||
class DatabaseConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
|
|
||||||
{
|
|
||||||
const DATABASE_DRIVERS = [
|
|
||||||
'MySQL' => 'pdo_mysql',
|
|
||||||
'PostgreSQL' => 'pdo_pgsql',
|
|
||||||
'SQLite' => 'pdo_sqlite',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Filesystem
|
|
||||||
*/
|
|
||||||
private $filesystem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DatabaseConfigCustomizerPlugin constructor.
|
|
||||||
* @param QuestionHelper $questionHelper
|
|
||||||
* @param Filesystem $filesystem
|
|
||||||
*
|
|
||||||
* @DI\Inject({QuestionHelper::class, Filesystem::class})
|
|
||||||
*/
|
|
||||||
public function __construct(QuestionHelper $questionHelper, Filesystem $filesystem)
|
|
||||||
{
|
|
||||||
parent::__construct($questionHelper);
|
|
||||||
$this->filesystem = $filesystem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param InputInterface $input
|
|
||||||
* @param OutputInterface $output
|
|
||||||
* @param CustomizableAppConfig $appConfig
|
|
||||||
* @return void
|
|
||||||
* @throws IOException
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
|
|
||||||
{
|
|
||||||
$this->printTitle($output, 'DATABASE');
|
|
||||||
|
|
||||||
if ($appConfig->hasDatabase() && $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
|
|
||||||
'<question>Do you want to keep imported database config? (Y/n):</question> '
|
|
||||||
))) {
|
|
||||||
// If the user selected to keep DB config and is configured to use sqlite, copy DB file
|
|
||||||
if ($appConfig->getDatabase()['DRIVER'] === self::DATABASE_DRIVERS['SQLite']) {
|
|
||||||
try {
|
|
||||||
$this->filesystem->copy(
|
|
||||||
$appConfig->getImportedInstallationPath() . '/' . CustomizableAppConfig::SQLITE_DB_PATH,
|
|
||||||
CustomizableAppConfig::SQLITE_DB_PATH
|
|
||||||
);
|
|
||||||
} catch (IOException $e) {
|
|
||||||
$output->writeln('<error>It wasn\'t possible to import the SQLite database</error>');
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select database type
|
|
||||||
$params = [];
|
|
||||||
$databases = array_keys(self::DATABASE_DRIVERS);
|
|
||||||
$dbType = $this->questionHelper->ask($input, $output, new ChoiceQuestion(
|
|
||||||
'<question>Select database type (defaults to ' . $databases[0] . '):</question>',
|
|
||||||
$databases,
|
|
||||||
0
|
|
||||||
));
|
|
||||||
$params['DRIVER'] = self::DATABASE_DRIVERS[$dbType];
|
|
||||||
|
|
||||||
// Ask for connection params if database is not SQLite
|
|
||||||
if ($params['DRIVER'] !== self::DATABASE_DRIVERS['SQLite']) {
|
|
||||||
$params['NAME'] = $this->ask($input, $output, 'Database name', 'shlink');
|
|
||||||
$params['USER'] = $this->ask($input, $output, 'Database username');
|
|
||||||
$params['PASSWORD'] = $this->ask($input, $output, 'Database password');
|
|
||||||
$params['HOST'] = $this->ask($input, $output, 'Database host', 'localhost');
|
|
||||||
$params['PORT'] = $this->ask($input, $output, 'Database port', $this->getDefaultDbPort($params['DRIVER']));
|
|
||||||
}
|
|
||||||
|
|
||||||
$appConfig->setDatabase($params);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getDefaultDbPort($driver)
|
|
||||||
{
|
|
||||||
return $driver === 'pdo_mysql' ? '3306' : '5432';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Install\Plugin\Factory;
|
|
||||||
|
|
||||||
use Interop\Container\ContainerInterface;
|
|
||||||
use Interop\Container\Exception\ContainerException;
|
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
|
||||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
|
||||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
|
||||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
|
||||||
|
|
||||||
class DefaultConfigCustomizerPluginFactory implements FactoryInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Create an object
|
|
||||||
*
|
|
||||||
* @param ContainerInterface $container
|
|
||||||
* @param string $requestedName
|
|
||||||
* @param null|array $options
|
|
||||||
* @return object
|
|
||||||
* @throws ServiceNotFoundException if unable to resolve the service.
|
|
||||||
* @throws ServiceNotCreatedException if an exception is raised when
|
|
||||||
* creating a service.
|
|
||||||
* @throws ContainerException if any other error occurs
|
|
||||||
*/
|
|
||||||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
|
||||||
{
|
|
||||||
return new $requestedName($container->get(QuestionHelper::class));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
36
module/CLI/src/Install/Plugin/LanguageConfigCustomizer.php
Normal file
36
module/CLI/src/Install/Plugin/LanguageConfigCustomizer.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
class LanguageConfigCustomizer implements ConfigCustomizerInterface
|
||||||
|
{
|
||||||
|
const SUPPORTED_LANGUAGES = ['en', 'es'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SymfonyStyle $io
|
||||||
|
* @param CustomizableAppConfig $appConfig
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig)
|
||||||
|
{
|
||||||
|
$io->title('LANGUAGE');
|
||||||
|
|
||||||
|
if ($appConfig->hasLanguage() && $io->confirm('Do you want to keep imported language?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$appConfig->setLanguage([
|
||||||
|
'DEFAULT' => $this->chooseLanguage('Select default language for the application in general', $io),
|
||||||
|
'CLI' => $this->chooseLanguage('Select default language for CLI executions', $io),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function chooseLanguage(string $message, SymfonyStyle $io): string
|
||||||
|
{
|
||||||
|
return $io->choice($message, self::SUPPORTED_LANGUAGES, self::SUPPORTED_LANGUAGES[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
|
||||||
use Symfony\Component\Console\Exception\RuntimeException;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
|
||||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
|
||||||
|
|
||||||
class LanguageConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
|
|
||||||
{
|
|
||||||
const SUPPORTED_LANGUAGES = ['en', 'es'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param InputInterface $input
|
|
||||||
* @param OutputInterface $output
|
|
||||||
* @param CustomizableAppConfig $appConfig
|
|
||||||
* @return void
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
|
|
||||||
{
|
|
||||||
$this->printTitle($output, 'LANGUAGE');
|
|
||||||
|
|
||||||
if ($appConfig->hasLanguage() && $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
|
|
||||||
'<question>Do you want to keep imported language? (Y/n):</question> '
|
|
||||||
))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$appConfig->setLanguage([
|
|
||||||
'DEFAULT' => $this->questionHelper->ask($input, $output, new ChoiceQuestion(
|
|
||||||
'<question>Select default language for the application in general (defaults to '
|
|
||||||
. self::SUPPORTED_LANGUAGES[0] . '):</question>',
|
|
||||||
self::SUPPORTED_LANGUAGES,
|
|
||||||
0
|
|
||||||
)),
|
|
||||||
'CLI' => $this->questionHelper->ask($input, $output, new ChoiceQuestion(
|
|
||||||
'<question>Select default language for CLI executions (defaults to '
|
|
||||||
. self::SUPPORTED_LANGUAGES[0] . '):</question>',
|
|
||||||
self::SUPPORTED_LANGUAGES,
|
|
||||||
0
|
|
||||||
)),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
|
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
class UrlShortenerConfigCustomizer implements ConfigCustomizerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param SymfonyStyle $io
|
||||||
|
* @param CustomizableAppConfig $appConfig
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig)
|
||||||
|
{
|
||||||
|
$io->title('URL SHORTENER');
|
||||||
|
|
||||||
|
if ($appConfig->hasUrlShortener() && $io->confirm('Do you want to keep imported URL shortener config?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask for URL shortener params
|
||||||
|
$appConfig->setUrlShortener([
|
||||||
|
'SCHEMA' => $io->choice(
|
||||||
|
'Select schema for generated short URLs',
|
||||||
|
['http', 'https'],
|
||||||
|
'http'
|
||||||
|
),
|
||||||
|
'HOSTNAME' => $io->ask('Hostname for generated URLs'),
|
||||||
|
'CHARS' => $io->ask(
|
||||||
|
'Character set for generated short codes (leave empty to autogenerate one)',
|
||||||
|
null,
|
||||||
|
function ($value) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
) ?: \str_shuffle(UrlShortener::DEFAULT_CHARS),
|
||||||
|
'VALIDATE_URL' => $io->confirm('Do you want to validate long urls by 200 HTTP status code on response'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
|
||||||
use Symfony\Component\Console\Exception\RuntimeException;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
|
||||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
|
||||||
|
|
||||||
class UrlShortenerConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param InputInterface $input
|
|
||||||
* @param OutputInterface $output
|
|
||||||
* @param CustomizableAppConfig $appConfig
|
|
||||||
* @return void
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
|
|
||||||
{
|
|
||||||
$this->printTitle($output, 'URL SHORTENER');
|
|
||||||
|
|
||||||
if ($appConfig->hasUrlShortener() && $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
|
|
||||||
'<question>Do you want to keep imported URL shortener config? (Y/n):</question> '
|
|
||||||
))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask for URL shortener params
|
|
||||||
$appConfig->setUrlShortener([
|
|
||||||
'SCHEMA' => $this->questionHelper->ask($input, $output, new ChoiceQuestion(
|
|
||||||
'<question>Select schema for generated short URLs (defaults to http):</question>',
|
|
||||||
['http', 'https'],
|
|
||||||
0
|
|
||||||
)),
|
|
||||||
'HOSTNAME' => $this->ask($input, $output, 'Hostname for generated URLs'),
|
|
||||||
'CHARS' => $this->ask(
|
|
||||||
$input,
|
|
||||||
$output,
|
|
||||||
'Character set for generated short codes (leave empty to autogenerate one)',
|
|
||||||
null,
|
|
||||||
true
|
|
||||||
) ?: str_shuffle(UrlShortener::DEFAULT_CHARS),
|
|
||||||
'VALIDATE_URL' => $this->questionHelper->ask(
|
|
||||||
$input,
|
|
||||||
$output,
|
|
||||||
new ConfirmationQuestion(
|
|
||||||
'<question>Do you want to validate long urls by 200 HTTP status code on response (Y/n):</question>'
|
|
||||||
)
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -225,6 +225,7 @@ final class CustomizableAppConfig implements ArraySerializableInterface
|
|||||||
$config = [
|
$config = [
|
||||||
'app_options' => [
|
'app_options' => [
|
||||||
'secret_key' => $this->app['SECRET'],
|
'secret_key' => $this->app['SECRET'],
|
||||||
|
'disable_track_param' => $this->app['DISABLE_TRACK_PARAM'] ?? null,
|
||||||
],
|
],
|
||||||
'entity_manager' => [
|
'entity_manager' => [
|
||||||
'connection' => [
|
'connection' => [
|
||||||
|
|||||||
@@ -59,6 +59,6 @@ class DisableKeyCommandTest extends TestCase
|
|||||||
'apiKey' => $apiKey,
|
'apiKey' => $apiKey,
|
||||||
]);
|
]);
|
||||||
$output = $this->commandTester->getDisplay();
|
$output = $this->commandTester->getDisplay();
|
||||||
$this->assertEquals('API key "abcd1234" does not exist.' . PHP_EOL, $output);
|
$this->assertContains('API key "abcd1234" does not exist.', $output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace ShlinkioTest\Shlink\CLI\Command\Config;
|
|||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Shlinkio\Shlink\CLI\Command\Config\GenerateCharsetCommand;
|
use Shlinkio\Shlink\CLI\Command\Config\GenerateCharsetCommand;
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
use Symfony\Component\Console\Tester\CommandTester;
|
use Symfony\Component\Console\Tester\CommandTester;
|
||||||
use Zend\I18n\Translator\Translator;
|
use Zend\I18n\Translator\Translator;
|
||||||
@@ -32,7 +31,6 @@ class GenerateCharsetCommandTest extends TestCase
|
|||||||
public function charactersAreGeneratedFromDefault()
|
public function charactersAreGeneratedFromDefault()
|
||||||
{
|
{
|
||||||
$prefix = 'Character set: ';
|
$prefix = 'Character set: ';
|
||||||
$prefixLength = strlen($prefix);
|
|
||||||
|
|
||||||
$this->commandTester->execute([
|
$this->commandTester->execute([
|
||||||
'command' => 'config:generate-charset',
|
'command' => 'config:generate-charset',
|
||||||
@@ -40,13 +38,7 @@ class GenerateCharsetCommandTest extends TestCase
|
|||||||
$output = $this->commandTester->getDisplay();
|
$output = $this->commandTester->getDisplay();
|
||||||
|
|
||||||
// Both default character set and the new one should have the same length
|
// Both default character set and the new one should have the same length
|
||||||
$this->assertEquals($prefixLength + strlen(UrlShortener::DEFAULT_CHARS) + 1, strlen($output));
|
$this->assertContains($prefix, $output);
|
||||||
|
|
||||||
// Both default character set and the new one should have the same characters
|
|
||||||
$charset = substr($output, $prefixLength, strlen(UrlShortener::DEFAULT_CHARS));
|
|
||||||
$orderedDefault = $this->orderStringLetters(UrlShortener::DEFAULT_CHARS);
|
|
||||||
$orderedCharset = $this->orderStringLetters($charset);
|
|
||||||
$this->assertEquals($orderedDefault, $orderedCharset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function orderStringLetters($string)
|
protected function orderStringLetters($string)
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ use Prophecy\Argument;
|
|||||||
use Prophecy\Prophecy\MethodProphecy;
|
use Prophecy\Prophecy\MethodProphecy;
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
use Shlinkio\Shlink\CLI\Command\Install\InstallCommand;
|
use Shlinkio\Shlink\CLI\Command\Install\InstallCommand;
|
||||||
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerPluginManagerInterface;
|
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerManagerInterface;
|
||||||
use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerPluginInterface;
|
use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerInterface;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
use Symfony\Component\Console\Helper\ProcessHelper;
|
use Symfony\Component\Console\Helper\ProcessHelper;
|
||||||
use Symfony\Component\Console\Tester\CommandTester;
|
use Symfony\Component\Console\Tester\CommandTester;
|
||||||
use Symfony\Component\Filesystem\Exception\IOException;
|
use Symfony\Component\Filesystem\Exception\IOException;
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use Symfony\Component\Process\PhpExecutableFinder;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
use Zend\Config\Writer\WriterInterface;
|
use Zend\Config\Writer\WriterInterface;
|
||||||
|
|
||||||
@@ -51,10 +52,13 @@ class InstallCommandTest extends TestCase
|
|||||||
|
|
||||||
$this->configWriter = $this->prophesize(WriterInterface::class);
|
$this->configWriter = $this->prophesize(WriterInterface::class);
|
||||||
|
|
||||||
$configCustomizer = $this->prophesize(ConfigCustomizerPluginInterface::class);
|
$configCustomizer = $this->prophesize(ConfigCustomizerInterface::class);
|
||||||
$configCustomizers = $this->prophesize(ConfigCustomizerPluginManagerInterface::class);
|
$configCustomizers = $this->prophesize(ConfigCustomizerManagerInterface::class);
|
||||||
$configCustomizers->get(Argument::cetera())->willReturn($configCustomizer->reveal());
|
$configCustomizers->get(Argument::cetera())->willReturn($configCustomizer->reveal());
|
||||||
|
|
||||||
|
$finder = $this->prophesize(PhpExecutableFinder::class);
|
||||||
|
$finder->find(false)->willReturn('php');
|
||||||
|
|
||||||
$app = new Application();
|
$app = new Application();
|
||||||
$helperSet = $app->getHelperSet();
|
$helperSet = $app->getHelperSet();
|
||||||
$helperSet->set($processHelper->reveal());
|
$helperSet->set($processHelper->reveal());
|
||||||
@@ -62,7 +66,9 @@ class InstallCommandTest extends TestCase
|
|||||||
$this->command = new InstallCommand(
|
$this->command = new InstallCommand(
|
||||||
$this->configWriter->reveal(),
|
$this->configWriter->reveal(),
|
||||||
$this->filesystem->reveal(),
|
$this->filesystem->reveal(),
|
||||||
$configCustomizers->reveal()
|
$configCustomizers->reveal(),
|
||||||
|
false,
|
||||||
|
$finder->reveal()
|
||||||
);
|
);
|
||||||
$app->add($this->command);
|
$app->add($this->command);
|
||||||
|
|
||||||
|
|||||||
@@ -65,8 +65,9 @@ class GenerateShortcodeCommandTest extends TestCase
|
|||||||
'longUrl' => 'http://domain.com/invalid',
|
'longUrl' => 'http://domain.com/invalid',
|
||||||
]);
|
]);
|
||||||
$output = $this->commandTester->getDisplay();
|
$output = $this->commandTester->getDisplay();
|
||||||
$this->assertTrue(
|
$this->assertContains(
|
||||||
strpos($output, 'Provided URL "http://domain.com/invalid" is invalid. Try with a different one.') === 0
|
'Provided URL "http://domain.com/invalid" is invalid.',
|
||||||
|
$output
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use Shlinkio\Shlink\CLI\Command\Shortcode\ListShortcodesCommand;
|
|||||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
|
||||||
use Symfony\Component\Console\Tester\CommandTester;
|
use Symfony\Component\Console\Tester\CommandTester;
|
||||||
use Zend\I18n\Translator\Translator;
|
use Zend\I18n\Translator\Translator;
|
||||||
use Zend\Paginator\Adapter\ArrayAdapter;
|
use Zend\Paginator\Adapter\ArrayAdapter;
|
||||||
@@ -22,10 +21,6 @@ class ListShortcodesCommandTest extends TestCase
|
|||||||
* @var CommandTester
|
* @var CommandTester
|
||||||
*/
|
*/
|
||||||
protected $commandTester;
|
protected $commandTester;
|
||||||
/**
|
|
||||||
* @var QuestionHelper
|
|
||||||
*/
|
|
||||||
protected $questionHelper;
|
|
||||||
/**
|
/**
|
||||||
* @var ObjectProphecy
|
* @var ObjectProphecy
|
||||||
*/
|
*/
|
||||||
@@ -37,8 +32,6 @@ class ListShortcodesCommandTest extends TestCase
|
|||||||
$app = new Application();
|
$app = new Application();
|
||||||
$command = new ListShortcodesCommand($this->shortUrlService->reveal(), Translator::factory([]));
|
$command = new ListShortcodesCommand($this->shortUrlService->reveal(), Translator::factory([]));
|
||||||
$app->add($command);
|
$app->add($command);
|
||||||
|
|
||||||
$this->questionHelper = $command->getHelper('question');
|
|
||||||
$this->commandTester = new CommandTester($command);
|
$this->commandTester = new CommandTester($command);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,10 +40,10 @@ class ListShortcodesCommandTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function noInputCallsListJustOnce()
|
public function noInputCallsListJustOnce()
|
||||||
{
|
{
|
||||||
$this->questionHelper->setInputStream($this->getInputStream('\n'));
|
|
||||||
$this->shortUrlService->listShortUrls(1, null, [], null)->willReturn(new Paginator(new ArrayAdapter()))
|
$this->shortUrlService->listShortUrls(1, null, [], null)->willReturn(new Paginator(new ArrayAdapter()))
|
||||||
->shouldBeCalledTimes(1);
|
->shouldBeCalledTimes(1);
|
||||||
|
|
||||||
|
$this->commandTester->setInputs(['n']);
|
||||||
$this->commandTester->execute(['command' => 'shortcode:list']);
|
$this->commandTester->execute(['command' => 'shortcode:list']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,22 +54,15 @@ class ListShortcodesCommandTest extends TestCase
|
|||||||
{
|
{
|
||||||
// The paginator will return more than one page for the first 3 times
|
// The paginator will return more than one page for the first 3 times
|
||||||
$data = [];
|
$data = [];
|
||||||
for ($i = 0; $i < 30; $i++) {
|
for ($i = 0; $i < 50; $i++) {
|
||||||
$data[] = new ShortUrl();
|
$data[] = new ShortUrl();
|
||||||
}
|
}
|
||||||
$data = array_chunk($data, 11);
|
|
||||||
|
|
||||||
$questionHelper = $this->questionHelper;
|
$this->shortUrlService->listShortUrls(Argument::cetera())->will(function () use (&$data) {
|
||||||
$that = $this;
|
return new Paginator(new ArrayAdapter($data));
|
||||||
$this->shortUrlService->listShortUrls(Argument::cetera())->will(function () use (
|
|
||||||
&$data,
|
|
||||||
$questionHelper,
|
|
||||||
$that
|
|
||||||
) {
|
|
||||||
$questionHelper->setInputStream($that->getInputStream('y'));
|
|
||||||
return new Paginator(new ArrayAdapter(array_shift($data)));
|
|
||||||
})->shouldBeCalledTimes(3);
|
})->shouldBeCalledTimes(3);
|
||||||
|
|
||||||
|
$this->commandTester->setInputs(['y', 'y', 'n']);
|
||||||
$this->commandTester->execute(['command' => 'shortcode:list']);
|
$this->commandTester->execute(['command' => 'shortcode:list']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,10 +77,10 @@ class ListShortcodesCommandTest extends TestCase
|
|||||||
$data[] = new ShortUrl();
|
$data[] = new ShortUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->questionHelper->setInputStream($this->getInputStream('n'));
|
|
||||||
$this->shortUrlService->listShortUrls(Argument::cetera())->willReturn(new Paginator(new ArrayAdapter($data)))
|
$this->shortUrlService->listShortUrls(Argument::cetera())->willReturn(new Paginator(new ArrayAdapter($data)))
|
||||||
->shouldBeCalledTimes(1);
|
->shouldBeCalledTimes(1);
|
||||||
|
|
||||||
|
$this->commandTester->setInputs(['n']);
|
||||||
$this->commandTester->execute(['command' => 'shortcode:list']);
|
$this->commandTester->execute(['command' => 'shortcode:list']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,10 +90,10 @@ class ListShortcodesCommandTest extends TestCase
|
|||||||
public function passingPageWillMakeListStartOnThatPage()
|
public function passingPageWillMakeListStartOnThatPage()
|
||||||
{
|
{
|
||||||
$page = 5;
|
$page = 5;
|
||||||
$this->questionHelper->setInputStream($this->getInputStream('\n'));
|
|
||||||
$this->shortUrlService->listShortUrls($page, null, [], null)->willReturn(new Paginator(new ArrayAdapter()))
|
$this->shortUrlService->listShortUrls($page, null, [], null)->willReturn(new Paginator(new ArrayAdapter()))
|
||||||
->shouldBeCalledTimes(1);
|
->shouldBeCalledTimes(1);
|
||||||
|
|
||||||
|
$this->commandTester->setInputs(['y']);
|
||||||
$this->commandTester->execute([
|
$this->commandTester->execute([
|
||||||
'command' => 'shortcode:list',
|
'command' => 'shortcode:list',
|
||||||
'--page' => $page,
|
'--page' => $page,
|
||||||
@@ -119,24 +105,15 @@ class ListShortcodesCommandTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function ifTagsFlagIsProvidedTagsColumnIsIncluded()
|
public function ifTagsFlagIsProvidedTagsColumnIsIncluded()
|
||||||
{
|
{
|
||||||
$this->questionHelper->setInputStream($this->getInputStream('\n'));
|
|
||||||
$this->shortUrlService->listShortUrls(1, null, [], null)->willReturn(new Paginator(new ArrayAdapter()))
|
$this->shortUrlService->listShortUrls(1, null, [], null)->willReturn(new Paginator(new ArrayAdapter()))
|
||||||
->shouldBeCalledTimes(1);
|
->shouldBeCalledTimes(1);
|
||||||
|
|
||||||
|
$this->commandTester->setInputs(['y']);
|
||||||
$this->commandTester->execute([
|
$this->commandTester->execute([
|
||||||
'command' => 'shortcode:list',
|
'command' => 'shortcode:list',
|
||||||
'--showTags' => true,
|
'--showTags' => true,
|
||||||
]);
|
]);
|
||||||
$output = $this->commandTester->getDisplay();
|
$output = $this->commandTester->getDisplay();
|
||||||
$this->assertTrue(strpos($output, 'Tags') > 0);
|
$this->assertContains('Tags', $output);
|
||||||
}
|
|
||||||
|
|
||||||
protected function getInputStream($inputData)
|
|
||||||
{
|
|
||||||
$stream = fopen('php://memory', 'r+', false);
|
|
||||||
fputs($stream, $inputData);
|
|
||||||
rewind($stream);
|
|
||||||
|
|
||||||
return $stream;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class ResolveUrlCommandTest extends TestCase
|
|||||||
'shortCode' => $shortCode,
|
'shortCode' => $shortCode,
|
||||||
]);
|
]);
|
||||||
$output = $this->commandTester->getDisplay();
|
$output = $this->commandTester->getDisplay();
|
||||||
$this->assertEquals('Provided short code "' . $shortCode . '" could not be found.' . PHP_EOL, $output);
|
$this->assertContains('Provided short code "' . $shortCode . '" could not be found.', $output);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -83,6 +83,6 @@ class ResolveUrlCommandTest extends TestCase
|
|||||||
'shortCode' => $shortCode,
|
'shortCode' => $shortCode,
|
||||||
]);
|
]);
|
||||||
$output = $this->commandTester->getDisplay();
|
$output = $this->commandTester->getDisplay();
|
||||||
$this->assertEquals('Provided short code "' . $shortCode . '" has an invalid format.' . PHP_EOL, $output);
|
$this->assertContains('Provided short code "' . $shortCode . '" has an invalid format.', $output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ use Zend\I18n\Translator\Translator;
|
|||||||
|
|
||||||
class CreateTagCommandTest extends TestCase
|
class CreateTagCommandTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var CreateTagCommand
|
|
||||||
*/
|
|
||||||
private $command;
|
|
||||||
/**
|
/**
|
||||||
* @var CommandTester
|
* @var CommandTester
|
||||||
*/
|
*/
|
||||||
@@ -63,7 +59,7 @@ class CreateTagCommandTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
$output = $this->commandTester->getDisplay();
|
$output = $this->commandTester->getDisplay();
|
||||||
|
|
||||||
$this->assertContains(sprintf('Created tags: ["%s"]', implode('", "', $tagNames)), $output);
|
$this->assertContains('Tags properly created', $output);
|
||||||
$createTags->shouldHaveBeenCalled();
|
$createTags->shouldHaveBeenCalled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class DeleteTagsCommandTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
$output = $this->commandTester->getDisplay();
|
$output = $this->commandTester->getDisplay();
|
||||||
|
|
||||||
$this->assertContains(sprintf('Deleted tags: ["%s"]', implode('", "', $tagNames)), $output);
|
$this->assertContains('Tags properly deleted', $output);
|
||||||
$deleteTags->shouldHaveBeenCalled();
|
$deleteTags->shouldHaveBeenCalled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Install\Plugin;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Prophecy\Argument;
|
|
||||||
use Prophecy\Prophecy\MethodProphecy;
|
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
|
||||||
use Shlinkio\Shlink\CLI\Install\Plugin\ApplicationConfigCustomizerPlugin;
|
|
||||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
|
||||||
use Symfony\Component\Console\Input\ArrayInput;
|
|
||||||
use Symfony\Component\Console\Output\NullOutput;
|
|
||||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
|
||||||
|
|
||||||
class ApplicationConfigCustomizerPluginTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var ApplicationConfigCustomizerPlugin
|
|
||||||
*/
|
|
||||||
private $plugin;
|
|
||||||
/**
|
|
||||||
* @var ObjectProphecy
|
|
||||||
*/
|
|
||||||
private $questionHelper;
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
$this->questionHelper = $this->prophesize(QuestionHelper::class);
|
|
||||||
$this->plugin = new ApplicationConfigCustomizerPlugin($this->questionHelper->reveal());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function configIsRequestedToTheUser()
|
|
||||||
{
|
|
||||||
/** @var MethodProphecy $askSecret */
|
|
||||||
$askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('the_secret');
|
|
||||||
$config = new CustomizableAppConfig();
|
|
||||||
|
|
||||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
|
||||||
|
|
||||||
$this->assertTrue($config->hasApp());
|
|
||||||
$this->assertEquals([
|
|
||||||
'SECRET' => 'the_secret',
|
|
||||||
], $config->getApp());
|
|
||||||
$askSecret->shouldHaveBeenCalledTimes(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function overwriteIsRequestedIfValueIsAlreadySet()
|
|
||||||
{
|
|
||||||
/** @var MethodProphecy $ask */
|
|
||||||
$ask = $this->questionHelper->ask(Argument::cetera())->will(function (array $args) {
|
|
||||||
$last = array_pop($args);
|
|
||||||
return $last instanceof ConfirmationQuestion ? false : 'the_new_secret';
|
|
||||||
});
|
|
||||||
$config = new CustomizableAppConfig();
|
|
||||||
$config->setApp([
|
|
||||||
'SECRET' => 'foo',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
|
||||||
|
|
||||||
$this->assertEquals([
|
|
||||||
'SECRET' => 'the_new_secret',
|
|
||||||
], $config->getApp());
|
|
||||||
$ask->shouldHaveBeenCalledTimes(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function existingValueIsKeptIfRequested()
|
|
||||||
{
|
|
||||||
/** @var MethodProphecy $ask */
|
|
||||||
$ask = $this->questionHelper->ask(Argument::cetera())->willReturn(true);
|
|
||||||
|
|
||||||
$config = new CustomizableAppConfig();
|
|
||||||
$config->setApp([
|
|
||||||
'SECRET' => 'foo',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
|
||||||
|
|
||||||
$this->assertEquals([
|
|
||||||
'SECRET' => 'foo',
|
|
||||||
], $config->getApp());
|
|
||||||
$ask->shouldHaveBeenCalledTimes(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ShlinkioTest\Shlink\CLI\Install\Plugin;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\Argument;
|
||||||
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
|
use Shlinkio\Shlink\CLI\Install\Plugin\ApplicationConfigCustomizer;
|
||||||
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
class ApplicationConfigCustomizerTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ApplicationConfigCustomizer
|
||||||
|
*/
|
||||||
|
private $plugin;
|
||||||
|
/**
|
||||||
|
* @var ObjectProphecy
|
||||||
|
*/
|
||||||
|
private $io;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->io = $this->prophesize(SymfonyStyle::class);
|
||||||
|
$this->io->title(Argument::any())->willReturn(null);
|
||||||
|
|
||||||
|
$this->plugin = new ApplicationConfigCustomizer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function configIsRequestedToTheUser()
|
||||||
|
{
|
||||||
|
$ask = $this->io->ask(Argument::cetera())->willReturn('the_secret');
|
||||||
|
$config = new CustomizableAppConfig();
|
||||||
|
|
||||||
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
|
$this->assertTrue($config->hasApp());
|
||||||
|
$this->assertEquals([
|
||||||
|
'SECRET' => 'the_secret',
|
||||||
|
'DISABLE_TRACK_PARAM' => 'the_secret',
|
||||||
|
], $config->getApp());
|
||||||
|
$ask->shouldHaveBeenCalledTimes(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function overwriteIsRequestedIfValueIsAlreadySet()
|
||||||
|
{
|
||||||
|
$ask = $this->io->ask(Argument::cetera())->willReturn('the_new_secret');
|
||||||
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(false);
|
||||||
|
$config = new CustomizableAppConfig();
|
||||||
|
$config->setApp([
|
||||||
|
'SECRET' => 'foo',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'SECRET' => 'the_new_secret',
|
||||||
|
'DISABLE_TRACK_PARAM' => 'the_new_secret',
|
||||||
|
], $config->getApp());
|
||||||
|
$ask->shouldHaveBeenCalledTimes(2);
|
||||||
|
$confirm->shouldHaveBeenCalledTimes(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function existingValueIsKeptIfRequested()
|
||||||
|
{
|
||||||
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||||
|
|
||||||
|
$config = new CustomizableAppConfig();
|
||||||
|
$config->setApp([
|
||||||
|
'SECRET' => 'foo',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'SECRET' => 'foo',
|
||||||
|
], $config->getApp());
|
||||||
|
$confirm->shouldHaveBeenCalledTimes(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,26 +5,22 @@ namespace ShlinkioTest\Shlink\CLI\Install\Plugin;
|
|||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
use Prophecy\Prophecy\MethodProphecy;
|
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizerPlugin;
|
use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizer;
|
||||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\Console\Input\ArrayInput;
|
|
||||||
use Symfony\Component\Console\Output\NullOutput;
|
|
||||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
class DatabaseConfigCustomizerPluginTest extends TestCase
|
class DatabaseConfigCustomizerTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var DatabaseConfigCustomizerPlugin
|
* @var DatabaseConfigCustomizer
|
||||||
*/
|
*/
|
||||||
private $plugin;
|
private $plugin;
|
||||||
/**
|
/**
|
||||||
* @var ObjectProphecy
|
* @var ObjectProphecy
|
||||||
*/
|
*/
|
||||||
private $questionHelper;
|
private $io;
|
||||||
/**
|
/**
|
||||||
* @var ObjectProphecy
|
* @var ObjectProphecy
|
||||||
*/
|
*/
|
||||||
@@ -32,13 +28,11 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$this->questionHelper = $this->prophesize(QuestionHelper::class);
|
$this->io = $this->prophesize(SymfonyStyle::class);
|
||||||
|
$this->io->title(Argument::any())->willReturn(null);
|
||||||
$this->filesystem = $this->prophesize(Filesystem::class);
|
$this->filesystem = $this->prophesize(Filesystem::class);
|
||||||
|
|
||||||
$this->plugin = new DatabaseConfigCustomizerPlugin(
|
$this->plugin = new DatabaseConfigCustomizer($this->filesystem->reveal());
|
||||||
$this->questionHelper->reveal(),
|
|
||||||
$this->filesystem->reveal()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,22 +40,23 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function configIsRequestedToTheUser()
|
public function configIsRequestedToTheUser()
|
||||||
{
|
{
|
||||||
/** @var MethodProphecy $askSecret */
|
$choice = $this->io->choice(Argument::cetera())->willReturn('MySQL');
|
||||||
$askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('MySQL');
|
$ask = $this->io->ask(Argument::cetera())->willReturn('param');
|
||||||
$config = new CustomizableAppConfig();
|
$config = new CustomizableAppConfig();
|
||||||
|
|
||||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
$this->assertTrue($config->hasDatabase());
|
$this->assertTrue($config->hasDatabase());
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
'DRIVER' => 'pdo_mysql',
|
'DRIVER' => 'pdo_mysql',
|
||||||
'NAME' => 'MySQL',
|
'NAME' => 'param',
|
||||||
'USER' => 'MySQL',
|
'USER' => 'param',
|
||||||
'PASSWORD' => 'MySQL',
|
'PASSWORD' => 'param',
|
||||||
'HOST' => 'MySQL',
|
'HOST' => 'param',
|
||||||
'PORT' => 'MySQL',
|
'PORT' => 'param',
|
||||||
], $config->getDatabase());
|
], $config->getDatabase());
|
||||||
$askSecret->shouldHaveBeenCalledTimes(6);
|
$choice->shouldHaveBeenCalledTimes(1);
|
||||||
|
$ask->shouldHaveBeenCalledTimes(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,11 +64,9 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function overwriteIsRequestedIfValueIsAlreadySet()
|
public function overwriteIsRequestedIfValueIsAlreadySet()
|
||||||
{
|
{
|
||||||
/** @var MethodProphecy $ask */
|
$choice = $this->io->choice(Argument::cetera())->willReturn('MySQL');
|
||||||
$ask = $this->questionHelper->ask(Argument::cetera())->will(function (array $args) {
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(false);
|
||||||
$last = array_pop($args);
|
$ask = $this->io->ask(Argument::cetera())->willReturn('MySQL');
|
||||||
return $last instanceof ConfirmationQuestion ? false : 'MySQL';
|
|
||||||
});
|
|
||||||
$config = new CustomizableAppConfig();
|
$config = new CustomizableAppConfig();
|
||||||
$config->setDatabase([
|
$config->setDatabase([
|
||||||
'DRIVER' => 'pdo_pgsql',
|
'DRIVER' => 'pdo_pgsql',
|
||||||
@@ -84,7 +77,7 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||||||
'PORT' => 'MySQL',
|
'PORT' => 'MySQL',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
'DRIVER' => 'pdo_mysql',
|
'DRIVER' => 'pdo_mysql',
|
||||||
@@ -94,7 +87,9 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||||||
'HOST' => 'MySQL',
|
'HOST' => 'MySQL',
|
||||||
'PORT' => 'MySQL',
|
'PORT' => 'MySQL',
|
||||||
], $config->getDatabase());
|
], $config->getDatabase());
|
||||||
$ask->shouldHaveBeenCalledTimes(7);
|
$confirm->shouldHaveBeenCalledTimes(1);
|
||||||
|
$choice->shouldHaveBeenCalledTimes(1);
|
||||||
|
$ask->shouldHaveBeenCalledTimes(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,8 +97,7 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function existingValueIsKeptIfRequested()
|
public function existingValueIsKeptIfRequested()
|
||||||
{
|
{
|
||||||
/** @var MethodProphecy $ask */
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||||
$ask = $this->questionHelper->ask(Argument::cetera())->willReturn(true);
|
|
||||||
|
|
||||||
$config = new CustomizableAppConfig();
|
$config = new CustomizableAppConfig();
|
||||||
$config->setDatabase([
|
$config->setDatabase([
|
||||||
@@ -115,7 +109,7 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||||||
'PORT' => 'MySQL',
|
'PORT' => 'MySQL',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
'DRIVER' => 'pdo_pgsql',
|
'DRIVER' => 'pdo_pgsql',
|
||||||
@@ -125,7 +119,7 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||||||
'HOST' => 'MySQL',
|
'HOST' => 'MySQL',
|
||||||
'PORT' => 'MySQL',
|
'PORT' => 'MySQL',
|
||||||
], $config->getDatabase());
|
], $config->getDatabase());
|
||||||
$ask->shouldHaveBeenCalledTimes(1);
|
$confirm->shouldHaveBeenCalledTimes(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -133,9 +127,7 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function sqliteDatabaseIsImportedWhenRequested()
|
public function sqliteDatabaseIsImportedWhenRequested()
|
||||||
{
|
{
|
||||||
/** @var MethodProphecy $ask */
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||||
$ask = $this->questionHelper->ask(Argument::cetera())->willReturn(true);
|
|
||||||
/** @var MethodProphecy $copy */
|
|
||||||
$copy = $this->filesystem->copy(Argument::cetera())->willReturn(null);
|
$copy = $this->filesystem->copy(Argument::cetera())->willReturn(null);
|
||||||
|
|
||||||
$config = new CustomizableAppConfig();
|
$config = new CustomizableAppConfig();
|
||||||
@@ -143,12 +135,12 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||||||
'DRIVER' => 'pdo_sqlite',
|
'DRIVER' => 'pdo_sqlite',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
'DRIVER' => 'pdo_sqlite',
|
'DRIVER' => 'pdo_sqlite',
|
||||||
], $config->getDatabase());
|
], $config->getDatabase());
|
||||||
$ask->shouldHaveBeenCalledTimes(1);
|
$confirm->shouldHaveBeenCalledTimes(1);
|
||||||
$copy->shouldHaveBeenCalledTimes(1);
|
$copy->shouldHaveBeenCalledTimes(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Install\Plugin\Factory;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Shlinkio\Shlink\CLI\Install\Plugin\ApplicationConfigCustomizerPlugin;
|
|
||||||
use Shlinkio\Shlink\CLI\Install\Plugin\Factory\DefaultConfigCustomizerPluginFactory;
|
|
||||||
use Shlinkio\Shlink\CLI\Install\Plugin\LanguageConfigCustomizerPlugin;
|
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
|
||||||
use Zend\ServiceManager\ServiceManager;
|
|
||||||
|
|
||||||
class DefaultConfigCustomizerPluginFactoryTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var DefaultConfigCustomizerPluginFactory
|
|
||||||
*/
|
|
||||||
protected $factory;
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
$this->factory = new DefaultConfigCustomizerPluginFactory();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function createsProperService()
|
|
||||||
{
|
|
||||||
$instance = $this->factory->__invoke(new ServiceManager(['services' => [
|
|
||||||
QuestionHelper::class => $this->prophesize(QuestionHelper::class)->reveal(),
|
|
||||||
]]), ApplicationConfigCustomizerPlugin::class);
|
|
||||||
$this->assertInstanceOf(ApplicationConfigCustomizerPlugin::class, $instance);
|
|
||||||
|
|
||||||
$instance = $this->factory->__invoke(new ServiceManager(['services' => [
|
|
||||||
QuestionHelper::class => $this->prophesize(QuestionHelper::class)->reveal(),
|
|
||||||
]]), LanguageConfigCustomizerPlugin::class);
|
|
||||||
$this->assertInstanceOf(LanguageConfigCustomizerPlugin::class, $instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,30 +5,27 @@ namespace ShlinkioTest\Shlink\CLI\Install\Plugin;
|
|||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
use Prophecy\Prophecy\MethodProphecy;
|
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
use Shlinkio\Shlink\CLI\Install\Plugin\LanguageConfigCustomizerPlugin;
|
use Shlinkio\Shlink\CLI\Install\Plugin\LanguageConfigCustomizer;
|
||||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\Console\Input\ArrayInput;
|
|
||||||
use Symfony\Component\Console\Output\NullOutput;
|
|
||||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
|
||||||
|
|
||||||
class LanguageConfigCustomizerPluginTest extends TestCase
|
class LanguageConfigCustomizerTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var LanguageConfigCustomizerPlugin
|
* @var LanguageConfigCustomizer
|
||||||
*/
|
*/
|
||||||
protected $plugin;
|
protected $plugin;
|
||||||
/**
|
/**
|
||||||
* @var ObjectProphecy
|
* @var ObjectProphecy
|
||||||
*/
|
*/
|
||||||
protected $questionHelper;
|
protected $io;
|
||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$this->questionHelper = $this->prophesize(QuestionHelper::class);
|
$this->io = $this->prophesize(SymfonyStyle::class);
|
||||||
$this->plugin = new LanguageConfigCustomizerPlugin($this->questionHelper->reveal());
|
$this->io->title(Argument::any())->willReturn(null);
|
||||||
|
$this->plugin = new LanguageConfigCustomizer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,18 +33,17 @@ class LanguageConfigCustomizerPluginTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function configIsRequestedToTheUser()
|
public function configIsRequestedToTheUser()
|
||||||
{
|
{
|
||||||
/** @var MethodProphecy $askSecret */
|
$ask = $this->io->choice(Argument::cetera())->willReturn('en');
|
||||||
$askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('en');
|
|
||||||
$config = new CustomizableAppConfig();
|
$config = new CustomizableAppConfig();
|
||||||
|
|
||||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
$this->assertTrue($config->hasLanguage());
|
$this->assertTrue($config->hasLanguage());
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
'DEFAULT' => 'en',
|
'DEFAULT' => 'en',
|
||||||
'CLI' => 'en',
|
'CLI' => 'en',
|
||||||
], $config->getLanguage());
|
], $config->getLanguage());
|
||||||
$askSecret->shouldHaveBeenCalledTimes(2);
|
$ask->shouldHaveBeenCalledTimes(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,24 +51,22 @@ class LanguageConfigCustomizerPluginTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function overwriteIsRequestedIfValueIsAlreadySet()
|
public function overwriteIsRequestedIfValueIsAlreadySet()
|
||||||
{
|
{
|
||||||
/** @var MethodProphecy $ask */
|
$choice = $this->io->choice(Argument::cetera())->willReturn('es');
|
||||||
$ask = $this->questionHelper->ask(Argument::cetera())->will(function (array $args) {
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(false);
|
||||||
$last = array_pop($args);
|
|
||||||
return $last instanceof ConfirmationQuestion ? false : 'es';
|
|
||||||
});
|
|
||||||
$config = new CustomizableAppConfig();
|
$config = new CustomizableAppConfig();
|
||||||
$config->setLanguage([
|
$config->setLanguage([
|
||||||
'DEFAULT' => 'en',
|
'DEFAULT' => 'en',
|
||||||
'CLI' => 'en',
|
'CLI' => 'en',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
'DEFAULT' => 'es',
|
'DEFAULT' => 'es',
|
||||||
'CLI' => 'es',
|
'CLI' => 'es',
|
||||||
], $config->getLanguage());
|
], $config->getLanguage());
|
||||||
$ask->shouldHaveBeenCalledTimes(3);
|
$choice->shouldHaveBeenCalledTimes(2);
|
||||||
|
$confirm->shouldHaveBeenCalledTimes(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,8 +74,7 @@ class LanguageConfigCustomizerPluginTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function existingValueIsKeptIfRequested()
|
public function existingValueIsKeptIfRequested()
|
||||||
{
|
{
|
||||||
/** @var MethodProphecy $ask */
|
$ask = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||||
$ask = $this->questionHelper->ask(Argument::cetera())->willReturn(true);
|
|
||||||
|
|
||||||
$config = new CustomizableAppConfig();
|
$config = new CustomizableAppConfig();
|
||||||
$config->setLanguage([
|
$config->setLanguage([
|
||||||
@@ -89,7 +82,7 @@ class LanguageConfigCustomizerPluginTest extends TestCase
|
|||||||
'CLI' => 'es',
|
'CLI' => 'es',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
'DEFAULT' => 'es',
|
'DEFAULT' => 'es',
|
||||||
@@ -5,30 +5,27 @@ namespace ShlinkioTest\Shlink\CLI\Install\Plugin;
|
|||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
use Prophecy\Prophecy\MethodProphecy;
|
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
use Shlinkio\Shlink\CLI\Install\Plugin\UrlShortenerConfigCustomizerPlugin;
|
use Shlinkio\Shlink\CLI\Install\Plugin\UrlShortenerConfigCustomizer;
|
||||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\Console\Input\ArrayInput;
|
|
||||||
use Symfony\Component\Console\Output\NullOutput;
|
|
||||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
|
||||||
|
|
||||||
class UrlShortenerConfigCustomizerPluginTest extends TestCase
|
class UrlShortenerConfigCustomizerTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var UrlShortenerConfigCustomizerPlugin
|
* @var UrlShortenerConfigCustomizer
|
||||||
*/
|
*/
|
||||||
private $plugin;
|
private $plugin;
|
||||||
/**
|
/**
|
||||||
* @var ObjectProphecy
|
* @var ObjectProphecy
|
||||||
*/
|
*/
|
||||||
private $questionHelper;
|
private $io;
|
||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$this->questionHelper = $this->prophesize(QuestionHelper::class);
|
$this->io = $this->prophesize(SymfonyStyle::class);
|
||||||
$this->plugin = new UrlShortenerConfigCustomizerPlugin($this->questionHelper->reveal());
|
$this->io->title(Argument::any())->willReturn(null);
|
||||||
|
$this->plugin = new UrlShortenerConfigCustomizer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,20 +33,23 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function configIsRequestedToTheUser()
|
public function configIsRequestedToTheUser()
|
||||||
{
|
{
|
||||||
/** @var MethodProphecy $askSecret */
|
$choice = $this->io->choice(Argument::cetera())->willReturn('something');
|
||||||
$askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('something');
|
$ask = $this->io->ask(Argument::cetera())->willReturn('something');
|
||||||
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||||
$config = new CustomizableAppConfig();
|
$config = new CustomizableAppConfig();
|
||||||
|
|
||||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
$this->assertTrue($config->hasUrlShortener());
|
$this->assertTrue($config->hasUrlShortener());
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
'SCHEMA' => 'something',
|
'SCHEMA' => 'something',
|
||||||
'HOSTNAME' => 'something',
|
'HOSTNAME' => 'something',
|
||||||
'CHARS' => 'something',
|
'CHARS' => 'something',
|
||||||
'VALIDATE_URL' => 'something',
|
'VALIDATE_URL' => true,
|
||||||
], $config->getUrlShortener());
|
], $config->getUrlShortener());
|
||||||
$askSecret->shouldHaveBeenCalledTimes(4);
|
$ask->shouldHaveBeenCalledTimes(2);
|
||||||
|
$choice->shouldHaveBeenCalledTimes(1);
|
||||||
|
$confirm->shouldHaveBeenCalledTimes(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,20 +57,18 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function overwriteIsRequestedIfValueIsAlreadySet()
|
public function overwriteIsRequestedIfValueIsAlreadySet()
|
||||||
{
|
{
|
||||||
/** @var MethodProphecy $ask */
|
$choice = $this->io->choice(Argument::cetera())->willReturn('foo');
|
||||||
$ask = $this->questionHelper->ask(Argument::cetera())->will(function (array $args) {
|
$ask = $this->io->ask(Argument::cetera())->willReturn('foo');
|
||||||
$last = array_pop($args);
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(false);
|
||||||
return $last instanceof ConfirmationQuestion ? false : 'foo';
|
|
||||||
});
|
|
||||||
$config = new CustomizableAppConfig();
|
$config = new CustomizableAppConfig();
|
||||||
$config->setUrlShortener([
|
$config->setUrlShortener([
|
||||||
'SCHEMA' => 'bar',
|
'SCHEMA' => 'bar',
|
||||||
'HOSTNAME' => 'bar',
|
'HOSTNAME' => 'bar',
|
||||||
'CHARS' => 'bar',
|
'CHARS' => 'bar',
|
||||||
'VALIDATE_URL' => 'bar',
|
'VALIDATE_URL' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
'SCHEMA' => 'foo',
|
'SCHEMA' => 'foo',
|
||||||
@@ -78,7 +76,9 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase
|
|||||||
'CHARS' => 'foo',
|
'CHARS' => 'foo',
|
||||||
'VALIDATE_URL' => false,
|
'VALIDATE_URL' => false,
|
||||||
], $config->getUrlShortener());
|
], $config->getUrlShortener());
|
||||||
$ask->shouldHaveBeenCalledTimes(5);
|
$ask->shouldHaveBeenCalledTimes(2);
|
||||||
|
$choice->shouldHaveBeenCalledTimes(1);
|
||||||
|
$confirm->shouldHaveBeenCalledTimes(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,8 +86,7 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function existingValueIsKeptIfRequested()
|
public function existingValueIsKeptIfRequested()
|
||||||
{
|
{
|
||||||
/** @var MethodProphecy $ask */
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||||
$ask = $this->questionHelper->ask(Argument::cetera())->willReturn(true);
|
|
||||||
|
|
||||||
$config = new CustomizableAppConfig();
|
$config = new CustomizableAppConfig();
|
||||||
$config->setUrlShortener([
|
$config->setUrlShortener([
|
||||||
@@ -97,7 +96,7 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase
|
|||||||
'VALIDATE_URL' => 'foo',
|
'VALIDATE_URL' => 'foo',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
'SCHEMA' => 'foo',
|
'SCHEMA' => 'foo',
|
||||||
@@ -105,6 +104,6 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase
|
|||||||
'CHARS' => 'foo',
|
'CHARS' => 'foo',
|
||||||
'VALIDATE_URL' => 'foo',
|
'VALIDATE_URL' => 'foo',
|
||||||
], $config->getUrlShortener());
|
], $config->getUrlShortener());
|
||||||
$ask->shouldHaveBeenCalledTimes(1);
|
$confirm->shouldHaveBeenCalledTimes(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink\Common\Exception;
|
namespace Shlinkio\Shlink\Common\Exception;
|
||||||
|
|
||||||
interface ExceptionInterface
|
interface ExceptionInterface extends \Throwable
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace Shlinkio\Shlink\Common\Exception;
|
|||||||
|
|
||||||
class WrongIpException extends RuntimeException
|
class WrongIpException extends RuntimeException
|
||||||
{
|
{
|
||||||
public static function fromIpAddress($ipAddress, \Exception $prev = null)
|
public static function fromIpAddress($ipAddress, \Throwable $prev = null)
|
||||||
{
|
{
|
||||||
return new self(sprintf('Provided IP "%s" is invalid', $ipAddress), 0, $prev);
|
return new self(sprintf('Provided IP "%s" is invalid', $ipAddress), 0, $prev);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace Shlinkio\Shlink\Common\Factory;
|
|||||||
use Interop\Container\ContainerInterface;
|
use Interop\Container\ContainerInterface;
|
||||||
use Interop\Container\Exception\ContainerException;
|
use Interop\Container\Exception\ContainerException;
|
||||||
use Zend\Diactoros\Response\EmptyResponse;
|
use Zend\Diactoros\Response\EmptyResponse;
|
||||||
use Zend\Expressive\Middleware\ImplicitOptionsMiddleware;
|
use Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware;
|
||||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||||
@@ -27,6 +27,8 @@ class EmptyResponseImplicitOptionsMiddlewareFactory implements FactoryInterface
|
|||||||
*/
|
*/
|
||||||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||||
{
|
{
|
||||||
return new ImplicitOptionsMiddleware(new EmptyResponse());
|
return new ImplicitOptionsMiddleware(function () {
|
||||||
|
return new EmptyResponse();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink\Common\Middleware;
|
namespace Shlinkio\Shlink\Common\Middleware;
|
||||||
|
|
||||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
|
||||||
use Interop\Http\ServerMiddleware\MiddlewareInterface;
|
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface as DelegateInterface;
|
||||||
use Zend\I18n\Translator\Translator;
|
use Zend\I18n\Translator\Translator;
|
||||||
|
|
||||||
class LocaleMiddleware implements MiddlewareInterface
|
class LocaleMiddleware implements MiddlewareInterface
|
||||||
@@ -32,15 +32,15 @@ class LocaleMiddleware implements MiddlewareInterface
|
|||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function process(Request $request, DelegateInterface $delegate)
|
public function process(Request $request, DelegateInterface $delegate): Response
|
||||||
{
|
{
|
||||||
if (! $request->hasHeader('Accept-Language')) {
|
if (! $request->hasHeader('Accept-Language')) {
|
||||||
return $delegate->process($request);
|
return $delegate->handle($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
$locale = $request->getHeaderLine('Accept-Language');
|
$locale = $request->getHeaderLine('Accept-Language');
|
||||||
$this->translator->setLocale($this->normalizeLocale($locale));
|
$this->translator->setLocale($this->normalizeLocale($locale));
|
||||||
return $delegate->process($request);
|
return $delegate->handle($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use Zend\Paginator\Adapter\AdapterInterface;
|
|||||||
|
|
||||||
class PaginableRepositoryAdapter implements AdapterInterface
|
class PaginableRepositoryAdapter implements AdapterInterface
|
||||||
{
|
{
|
||||||
const ITEMS_PER_PAGE = 10;
|
public const ITEMS_PER_PAGE = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var PaginableRepositoryInterface
|
* @var PaginableRepositoryInterface
|
||||||
@@ -34,7 +34,7 @@ class PaginableRepositoryAdapter implements AdapterInterface
|
|||||||
$orderBy = null
|
$orderBy = null
|
||||||
) {
|
) {
|
||||||
$this->paginableRepository = $paginableRepository;
|
$this->paginableRepository = $paginableRepository;
|
||||||
$this->searchTerm = $searchTerm !== null ? trim(strip_tags($searchTerm)) : null;
|
$this->searchTerm = $searchTerm !== null ? \trim(\strip_tags($searchTerm)) : null;
|
||||||
$this->orderBy = $orderBy;
|
$this->orderBy = $orderBy;
|
||||||
$this->tags = $tags;
|
$this->tags = $tags;
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ class PaginableRepositoryAdapter implements AdapterInterface
|
|||||||
* @param int $itemCountPerPage Number of items per page
|
* @param int $itemCountPerPage Number of items per page
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getItems($offset, $itemCountPerPage)
|
public function getItems($offset, $itemCountPerPage): array
|
||||||
{
|
{
|
||||||
return $this->paginableRepository->findList(
|
return $this->paginableRepository->findList(
|
||||||
$itemCountPerPage,
|
$itemCountPerPage,
|
||||||
@@ -66,7 +66,7 @@ class PaginableRepositoryAdapter implements AdapterInterface
|
|||||||
* The return value is cast to an integer.
|
* The return value is cast to an integer.
|
||||||
* @since 5.1.0
|
* @since 5.1.0
|
||||||
*/
|
*/
|
||||||
public function count()
|
public function count(): int
|
||||||
{
|
{
|
||||||
return $this->paginableRepository->countList($this->searchTerm, $this->tags);
|
return $this->paginableRepository->countList($this->searchTerm, $this->tags);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,16 @@ use Zend\Stdlib\ArrayUtils;
|
|||||||
|
|
||||||
trait PaginatorUtilsTrait
|
trait PaginatorUtilsTrait
|
||||||
{
|
{
|
||||||
protected function serializePaginator(Paginator $paginator)
|
protected function serializePaginator(Paginator $paginator): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'data' => ArrayUtils::iteratorToArray($paginator->getCurrentItems()),
|
'data' => ArrayUtils::iteratorToArray($paginator->getCurrentItems()),
|
||||||
'pagination' => [
|
'pagination' => [
|
||||||
'currentPage' => $paginator->getCurrentPageNumber(),
|
'currentPage' => $paginator->getCurrentPageNumber(),
|
||||||
'pagesCount' => $paginator->count(),
|
'pagesCount' => $paginator->count(),
|
||||||
|
'itemsPerPage' => $paginator->getItemCountPerPage(),
|
||||||
|
'itemsInCurrentPage' => $paginator->getCurrentItemCount(),
|
||||||
|
'totalItems' => $paginator->getTotalItemCount(),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -25,7 +28,7 @@ trait PaginatorUtilsTrait
|
|||||||
* @param Paginator $paginator
|
* @param Paginator $paginator
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
protected function isLastPage(Paginator $paginator)
|
protected function isLastPage(Paginator $paginator): bool
|
||||||
{
|
{
|
||||||
return $paginator->getCurrentPageNumber() >= $paginator->count();
|
return $paginator->getCurrentPageNumber() >= $paginator->count();
|
||||||
}
|
}
|
||||||
|
|||||||
33
module/Common/src/Response/PixelResponse.php
Normal file
33
module/Common/src/Response/PixelResponse.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Common\Response;
|
||||||
|
|
||||||
|
use Psr\Http\Message\StreamInterface;
|
||||||
|
use Zend\Diactoros\Response;
|
||||||
|
use Zend\Diactoros\Stream;
|
||||||
|
|
||||||
|
class PixelResponse extends Response
|
||||||
|
{
|
||||||
|
private const BASE_64_IMAGE = 'R0lGODlhAQABAJAAAP8AAAAAACH5BAUQAAAALAAAAAABAAEAAAICBAEAOw==';
|
||||||
|
private const CONTENT_TYPE = 'image/gif';
|
||||||
|
|
||||||
|
public function __construct(int $status = 200, array $headers = [])
|
||||||
|
{
|
||||||
|
$headers['content-type'] = self::CONTENT_TYPE;
|
||||||
|
parent::__construct($this->createBody(), $status, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the message body.
|
||||||
|
*
|
||||||
|
* @return StreamInterface
|
||||||
|
*/
|
||||||
|
private function createBody(): StreamInterface
|
||||||
|
{
|
||||||
|
$body = new Stream('php://temp', 'wb+');
|
||||||
|
$body->write(\base64_decode(self::BASE_64_IMAGE));
|
||||||
|
$body->rewind();
|
||||||
|
return $body;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,10 +22,11 @@ class IpLocationResolver implements IpLocationResolverInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $ipAddress
|
* @param string $ipAddress
|
||||||
* @return array
|
* @return array
|
||||||
|
* @throws WrongIpException
|
||||||
*/
|
*/
|
||||||
public function resolveIpLocation($ipAddress)
|
public function resolveIpLocation(string $ipAddress): array
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$response = $this->httpClient->get(sprintf(self::SERVICE_PATTERN, $ipAddress));
|
$response = $this->httpClient->get(sprintf(self::SERVICE_PATTERN, $ipAddress));
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink\Common\Service;
|
namespace Shlinkio\Shlink\Common\Service;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||||
|
|
||||||
interface IpLocationResolverInterface
|
interface IpLocationResolverInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param $ipAddress
|
* @param string $ipAddress
|
||||||
* @return array
|
* @return array
|
||||||
|
* @throws WrongIpException
|
||||||
*/
|
*/
|
||||||
public function resolveIpLocation($ipAddress);
|
public function resolveIpLocation(string $ipAddress): array;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ namespace Shlinkio\Shlink\Common\Util;
|
|||||||
class DateRange
|
class DateRange
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var \DateTimeInterface
|
* @var \DateTimeInterface|null
|
||||||
*/
|
*/
|
||||||
private $startDate;
|
private $startDate;
|
||||||
/**
|
/**
|
||||||
* @var \DateTimeInterface
|
* @var \DateTimeInterface|null
|
||||||
*/
|
*/
|
||||||
private $endDate;
|
private $endDate;
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ class DateRange
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return \DateTimeInterface
|
* @return \DateTimeInterface|null
|
||||||
*/
|
*/
|
||||||
public function getStartDate()
|
public function getStartDate()
|
||||||
{
|
{
|
||||||
@@ -29,7 +29,7 @@ class DateRange
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return \DateTimeInterface
|
* @return \DateTimeInterface|null
|
||||||
*/
|
*/
|
||||||
public function getEndDate()
|
public function getEndDate()
|
||||||
{
|
{
|
||||||
@@ -39,8 +39,8 @@ class DateRange
|
|||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isEmpty()
|
public function isEmpty(): bool
|
||||||
{
|
{
|
||||||
return is_null($this->startDate) && is_null($this->endDate);
|
return $this->startDate === null && $this->endDate === null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,40 +3,17 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Common\DbUnit;
|
namespace ShlinkioTest\Shlink\Common\DbUnit;
|
||||||
|
|
||||||
use Doctrine\DBAL\Driver\PDOConnection;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use PHPUnit\DbUnit\Database\Connection as DbConn;
|
use PHPUnit\Framework\TestCase;
|
||||||
use PHPUnit\DbUnit\DataSet\IDataSet as DataSet;
|
|
||||||
use PHPUnit\DbUnit\TestCase;
|
|
||||||
|
|
||||||
abstract class DatabaseTestCase extends TestCase
|
abstract class DatabaseTestCase extends TestCase
|
||||||
{
|
{
|
||||||
const ENTITIES_TO_EMPTY = [];
|
protected const ENTITIES_TO_EMPTY = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var EntityManagerInterface
|
* @var EntityManagerInterface
|
||||||
*/
|
*/
|
||||||
public static $em;
|
public static $em;
|
||||||
/**
|
|
||||||
* @var DbConn
|
|
||||||
*/
|
|
||||||
private static $conn;
|
|
||||||
|
|
||||||
public function getConnection(): DbConn
|
|
||||||
{
|
|
||||||
if (isset(self::$conn)) {
|
|
||||||
return self::$conn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var PDOConnection $pdo */
|
|
||||||
$pdo = static::$em->getConnection()->getWrappedConnection();
|
|
||||||
return self::$conn = $this->createDefaultDBConnection($pdo, static::$em->getConnection()->getDatabase());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDataSet(): DataSet
|
|
||||||
{
|
|
||||||
return $this->createArrayDataSet([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getEntityManager(): EntityManagerInterface
|
protected function getEntityManager(): EntityManagerInterface
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace ShlinkioTest\Shlink\Common\Factory;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Shlinkio\Shlink\Common\Factory\EmptyResponseImplicitOptionsMiddlewareFactory;
|
use Shlinkio\Shlink\Common\Factory\EmptyResponseImplicitOptionsMiddlewareFactory;
|
||||||
use Zend\Diactoros\Response\EmptyResponse;
|
use Zend\Diactoros\Response\EmptyResponse;
|
||||||
use Zend\Expressive\Middleware\ImplicitOptionsMiddleware;
|
use Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware;
|
||||||
use Zend\ServiceManager\ServiceManager;
|
use Zend\ServiceManager\ServiceManager;
|
||||||
|
|
||||||
class EmptyResponseImplicitOptionsMiddlewareFactoryTest extends TestCase
|
class EmptyResponseImplicitOptionsMiddlewareFactoryTest extends TestCase
|
||||||
@@ -38,8 +38,8 @@ class EmptyResponseImplicitOptionsMiddlewareFactoryTest extends TestCase
|
|||||||
$instance = $this->factory->__invoke(new ServiceManager(), '');
|
$instance = $this->factory->__invoke(new ServiceManager(), '');
|
||||||
|
|
||||||
$ref = new \ReflectionObject($instance);
|
$ref = new \ReflectionObject($instance);
|
||||||
$prop = $ref->getProperty('response');
|
$prop = $ref->getProperty('responseFactory');
|
||||||
$prop->setAccessible(true);
|
$prop->setAccessible(true);
|
||||||
$this->assertInstanceOf(EmptyResponse::class, $prop->getValue($instance));
|
$this->assertInstanceOf(EmptyResponse::class, $prop->getValue($instance)());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class LocaleMiddlewareTest extends TestCase
|
|||||||
public function whenNoHeaderIsPresentLocaleIsNotChanged()
|
public function whenNoHeaderIsPresentLocaleIsNotChanged()
|
||||||
{
|
{
|
||||||
$this->assertEquals('ru', $this->translator->getLocale());
|
$this->assertEquals('ru', $this->translator->getLocale());
|
||||||
$this->middleware->process(ServerRequestFactory::fromGlobals(), TestUtils::createDelegateMock()->reveal());
|
$this->middleware->process(ServerRequestFactory::fromGlobals(), TestUtils::createReqHandlerMock()->reveal());
|
||||||
$this->assertEquals('ru', $this->translator->getLocale());
|
$this->assertEquals('ru', $this->translator->getLocale());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ class LocaleMiddlewareTest extends TestCase
|
|||||||
{
|
{
|
||||||
$this->assertEquals('ru', $this->translator->getLocale());
|
$this->assertEquals('ru', $this->translator->getLocale());
|
||||||
$request = ServerRequestFactory::fromGlobals()->withHeader('Accept-Language', 'es');
|
$request = ServerRequestFactory::fromGlobals()->withHeader('Accept-Language', 'es');
|
||||||
$this->middleware->process($request, TestUtils::createDelegateMock()->reveal());
|
$this->middleware->process($request, TestUtils::createReqHandlerMock()->reveal());
|
||||||
$this->assertEquals('es', $this->translator->getLocale());
|
$this->assertEquals('es', $this->translator->getLocale());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ class LocaleMiddlewareTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function localeGetsNormalized()
|
public function localeGetsNormalized()
|
||||||
{
|
{
|
||||||
$delegate = TestUtils::createDelegateMock();
|
$delegate = TestUtils::createReqHandlerMock();
|
||||||
|
|
||||||
$this->assertEquals('ru', $this->translator->getLocale());
|
$this->assertEquals('ru', $this->translator->getLocale());
|
||||||
|
|
||||||
|
|||||||
@@ -3,22 +3,22 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Common\Util;
|
namespace ShlinkioTest\Shlink\Common\Util;
|
||||||
|
|
||||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
|
||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
use Prophecy\Prophet;
|
use Prophecy\Prophet;
|
||||||
use Psr\Http\Message\RequestInterface;
|
use Psr\Http\Message\RequestInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
use Zend\Diactoros\Response;
|
use Zend\Diactoros\Response;
|
||||||
|
|
||||||
class TestUtils
|
class TestUtils
|
||||||
{
|
{
|
||||||
private static $prophet;
|
private static $prophet;
|
||||||
|
|
||||||
public static function createDelegateMock(ResponseInterface $response = null, RequestInterface $request = null)
|
public static function createReqHandlerMock(ResponseInterface $response = null, RequestInterface $request = null)
|
||||||
{
|
{
|
||||||
$argument = $request ?: Argument::any();
|
$argument = $request ?: Argument::any();
|
||||||
$delegate = static::getProphet()->prophesize(DelegateInterface::class);
|
$delegate = static::getProphet()->prophesize(RequestHandlerInterface::class);
|
||||||
$delegate->process($argument)->willReturn($response ?: new Response());
|
$delegate->handle($argument)->willReturn($response ?: new Response());
|
||||||
|
|
||||||
return $delegate;
|
return $delegate;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use Shlinkio\Shlink\Common\Service\PreviewGenerator;
|
|||||||
use Shlinkio\Shlink\Core\Action;
|
use Shlinkio\Shlink\Core\Action;
|
||||||
use Shlinkio\Shlink\Core\Middleware;
|
use Shlinkio\Shlink\Core\Middleware;
|
||||||
use Shlinkio\Shlink\Core\Options;
|
use Shlinkio\Shlink\Core\Options;
|
||||||
use Shlinkio\Shlink\Core\Response\NotFoundDelegate;
|
use Shlinkio\Shlink\Core\Response\NotFoundHandler;
|
||||||
use Shlinkio\Shlink\Core\Service;
|
use Shlinkio\Shlink\Core\Service;
|
||||||
use Zend\Expressive\Router\RouterInterface;
|
use Zend\Expressive\Router\RouterInterface;
|
||||||
use Zend\Expressive\Template\TemplateRendererInterface;
|
use Zend\Expressive\Template\TemplateRendererInterface;
|
||||||
@@ -17,7 +17,7 @@ return [
|
|||||||
'dependencies' => [
|
'dependencies' => [
|
||||||
'factories' => [
|
'factories' => [
|
||||||
Options\AppOptions::class => Options\AppOptionsFactory::class,
|
Options\AppOptions::class => Options\AppOptionsFactory::class,
|
||||||
NotFoundDelegate::class => ConfigAbstractFactory::class,
|
NotFoundHandler::class => ConfigAbstractFactory::class,
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
Service\UrlShortener::class => ConfigAbstractFactory::class,
|
Service\UrlShortener::class => ConfigAbstractFactory::class,
|
||||||
@@ -28,18 +28,15 @@ return [
|
|||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
Action\RedirectAction::class => ConfigAbstractFactory::class,
|
Action\RedirectAction::class => ConfigAbstractFactory::class,
|
||||||
|
Action\PixelAction::class => ConfigAbstractFactory::class,
|
||||||
Action\QrCodeAction::class => ConfigAbstractFactory::class,
|
Action\QrCodeAction::class => ConfigAbstractFactory::class,
|
||||||
Action\PreviewAction::class => ConfigAbstractFactory::class,
|
Action\PreviewAction::class => ConfigAbstractFactory::class,
|
||||||
Middleware\QrCodeCacheMiddleware::class => ConfigAbstractFactory::class,
|
Middleware\QrCodeCacheMiddleware::class => ConfigAbstractFactory::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
'aliases' => [
|
|
||||||
'Zend\Expressive\Delegate\DefaultDelegate' => NotFoundDelegate::class,
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
|
|
||||||
ConfigAbstractFactory::class => [
|
ConfigAbstractFactory::class => [
|
||||||
NotFoundDelegate::class => [TemplateRendererInterface::class],
|
NotFoundHandler::class => [TemplateRendererInterface::class],
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
Service\UrlShortener::class => [
|
Service\UrlShortener::class => [
|
||||||
@@ -55,9 +52,20 @@ return [
|
|||||||
Service\Tag\TagService::class => ['em'],
|
Service\Tag\TagService::class => ['em'],
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
Action\RedirectAction::class => [Service\UrlShortener::class, Service\VisitsTracker::class],
|
Action\RedirectAction::class => [
|
||||||
|
Service\UrlShortener::class,
|
||||||
|
Service\VisitsTracker::class,
|
||||||
|
Options\AppOptions::class,
|
||||||
|
'Logger_Shlink',
|
||||||
|
],
|
||||||
|
Action\PixelAction::class => [
|
||||||
|
Service\UrlShortener::class,
|
||||||
|
Service\VisitsTracker::class,
|
||||||
|
Options\AppOptions::class,
|
||||||
|
'Logger_Shlink',
|
||||||
|
],
|
||||||
Action\QrCodeAction::class => [RouterInterface::class, Service\UrlShortener::class, 'Logger_Shlink'],
|
Action\QrCodeAction::class => [RouterInterface::class, Service\UrlShortener::class, 'Logger_Shlink'],
|
||||||
Action\PreviewAction::class => [PreviewGenerator::class, Service\UrlShortener::class],
|
Action\PreviewAction::class => [PreviewGenerator::class, Service\UrlShortener::class, 'Logger_Shlink'],
|
||||||
Middleware\QrCodeCacheMiddleware::class => [Cache::class],
|
Middleware\QrCodeCacheMiddleware::class => [Cache::class],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ return [
|
|||||||
'middleware' => Action\RedirectAction::class,
|
'middleware' => Action\RedirectAction::class,
|
||||||
'allowed_methods' => ['GET'],
|
'allowed_methods' => ['GET'],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'name' => 'pixel-tracking',
|
||||||
|
'path' => '/{shortCode}/track',
|
||||||
|
'middleware' => Action\PixelAction::class,
|
||||||
|
'allowed_methods' => ['GET'],
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'name' => 'short-url-qr-code',
|
'name' => 'short-url-qr-code',
|
||||||
'path' => '/{shortCode}/qr-code[/{size:[0-9]+}]',
|
'path' => '/{shortCode}/qr-code[/{size:[0-9]+}]',
|
||||||
|
|||||||
83
module/Core/src/Action/AbstractTrackingAction.php
Normal file
83
module/Core/src/Action/AbstractTrackingAction.php
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Core\Action;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Psr\Log\NullLogger;
|
||||||
|
use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait;
|
||||||
|
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||||
|
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||||
|
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||||
|
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||||
|
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||||
|
|
||||||
|
abstract class AbstractTrackingAction implements MiddlewareInterface
|
||||||
|
{
|
||||||
|
use ErrorResponseBuilderTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var UrlShortenerInterface
|
||||||
|
*/
|
||||||
|
private $urlShortener;
|
||||||
|
/**
|
||||||
|
* @var VisitsTrackerInterface
|
||||||
|
*/
|
||||||
|
private $visitTracker;
|
||||||
|
/**
|
||||||
|
* @var AppOptions
|
||||||
|
*/
|
||||||
|
private $appOptions;
|
||||||
|
/**
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
private $logger;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
UrlShortenerInterface $urlShortener,
|
||||||
|
VisitsTrackerInterface $visitTracker,
|
||||||
|
AppOptions $appOptions,
|
||||||
|
LoggerInterface $logger = null
|
||||||
|
) {
|
||||||
|
$this->urlShortener = $urlShortener;
|
||||||
|
$this->visitTracker = $visitTracker;
|
||||||
|
$this->appOptions = $appOptions;
|
||||||
|
$this->logger = $logger ?: new NullLogger();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process an incoming server request and return a response, optionally delegating
|
||||||
|
* to the next middleware component to create the response.
|
||||||
|
*
|
||||||
|
* @param ServerRequestInterface $request
|
||||||
|
* @param RequestHandlerInterface $handler
|
||||||
|
*
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||||
|
{
|
||||||
|
$shortCode = $request->getAttribute('shortCode', '');
|
||||||
|
$query = $request->getQueryParams();
|
||||||
|
$disableTrackParam = $this->appOptions->getDisableTrackParam();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$longUrl = $this->urlShortener->shortCodeToUrl($shortCode);
|
||||||
|
|
||||||
|
// Track visit to this short code
|
||||||
|
if ($disableTrackParam === null || ! \array_key_exists($disableTrackParam, $query)) {
|
||||||
|
$this->visitTracker->track($shortCode, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->createResp($longUrl);
|
||||||
|
} catch (InvalidShortCodeException | EntityDoesNotExistException $e) {
|
||||||
|
$this->logger->warning('An error occurred while tracking short code.' . PHP_EOL . $e);
|
||||||
|
return $this->buildErrorResponse($request, $handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected function createResp(string $longUrl): ResponseInterface;
|
||||||
|
}
|
||||||
15
module/Core/src/Action/PixelAction.php
Normal file
15
module/Core/src/Action/PixelAction.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Core\Action;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Shlinkio\Shlink\Common\Response\PixelResponse;
|
||||||
|
|
||||||
|
class PixelAction extends AbstractTrackingAction
|
||||||
|
{
|
||||||
|
protected function createResp(string $longUrl): ResponseInterface
|
||||||
|
{
|
||||||
|
return new PixelResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink\Core\Action;
|
namespace Shlinkio\Shlink\Core\Action;
|
||||||
|
|
||||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
|
||||||
use Interop\Http\ServerMiddleware\MiddlewareInterface;
|
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Psr\Log\NullLogger;
|
||||||
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
|
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
|
||||||
use Shlinkio\Shlink\Common\Service\PreviewGeneratorInterface;
|
use Shlinkio\Shlink\Common\Service\PreviewGeneratorInterface;
|
||||||
use Shlinkio\Shlink\Common\Util\ResponseUtilsTrait;
|
use Shlinkio\Shlink\Common\Util\ResponseUtilsTrait;
|
||||||
@@ -28,11 +30,19 @@ class PreviewAction implements MiddlewareInterface
|
|||||||
* @var UrlShortenerInterface
|
* @var UrlShortenerInterface
|
||||||
*/
|
*/
|
||||||
private $urlShortener;
|
private $urlShortener;
|
||||||
|
/**
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
private $logger;
|
||||||
|
|
||||||
public function __construct(PreviewGeneratorInterface $previewGenerator, UrlShortenerInterface $urlShortener)
|
public function __construct(
|
||||||
{
|
PreviewGeneratorInterface $previewGenerator,
|
||||||
|
UrlShortenerInterface $urlShortener,
|
||||||
|
LoggerInterface $logger = null
|
||||||
|
) {
|
||||||
$this->previewGenerator = $previewGenerator;
|
$this->previewGenerator = $previewGenerator;
|
||||||
$this->urlShortener = $urlShortener;
|
$this->urlShortener = $urlShortener;
|
||||||
|
$this->logger = $logger ?: new NullLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,11 +50,11 @@ class PreviewAction implements MiddlewareInterface
|
|||||||
* to the next middleware component to create the response.
|
* to the next middleware component to create the response.
|
||||||
*
|
*
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param DelegateInterface $delegate
|
* @param RequestHandlerInterface $handler
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function process(Request $request, DelegateInterface $delegate)
|
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||||
{
|
{
|
||||||
$shortCode = $request->getAttribute('shortCode');
|
$shortCode = $request->getAttribute('shortCode');
|
||||||
|
|
||||||
@@ -52,12 +62,9 @@ class PreviewAction implements MiddlewareInterface
|
|||||||
$url = $this->urlShortener->shortCodeToUrl($shortCode);
|
$url = $this->urlShortener->shortCodeToUrl($shortCode);
|
||||||
$imagePath = $this->previewGenerator->generatePreview($url);
|
$imagePath = $this->previewGenerator->generatePreview($url);
|
||||||
return $this->generateImageResponse($imagePath);
|
return $this->generateImageResponse($imagePath);
|
||||||
} catch (InvalidShortCodeException $e) {
|
} catch (InvalidShortCodeException | EntityDoesNotExistException | PreviewGenerationException $e) {
|
||||||
return $this->buildErrorResponse($request, $delegate);
|
$this->logger->warning('An error occurred while generating preview image.' . PHP_EOL . $e);
|
||||||
} catch (EntityDoesNotExistException $e) {
|
return $this->buildErrorResponse($request, $handler);
|
||||||
return $this->buildErrorResponse($request, $delegate);
|
|
||||||
} catch (PreviewGenerationException $e) {
|
|
||||||
return $this->buildErrorResponse($request, $delegate);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ declare(strict_types=1);
|
|||||||
namespace Shlinkio\Shlink\Core\Action;
|
namespace Shlinkio\Shlink\Core\Action;
|
||||||
|
|
||||||
use Endroid\QrCode\QrCode;
|
use Endroid\QrCode\QrCode;
|
||||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
|
||||||
use Interop\Http\ServerMiddleware\MiddlewareInterface;
|
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Psr\Log\NullLogger;
|
use Psr\Log\NullLogger;
|
||||||
use Shlinkio\Shlink\Common\Response\QrCodeResponse;
|
use Shlinkio\Shlink\Common\Response\QrCodeResponse;
|
||||||
@@ -15,6 +15,7 @@ use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait;
|
|||||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||||
|
use Zend\Expressive\Router\Exception\RuntimeException;
|
||||||
use Zend\Expressive\Router\RouterInterface;
|
use Zend\Expressive\Router\RouterInterface;
|
||||||
|
|
||||||
class QrCodeAction implements MiddlewareInterface
|
class QrCodeAction implements MiddlewareInterface
|
||||||
@@ -49,22 +50,21 @@ class QrCodeAction implements MiddlewareInterface
|
|||||||
* to the next middleware component to create the response.
|
* to the next middleware component to create the response.
|
||||||
*
|
*
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param DelegateInterface $delegate
|
* @param RequestHandlerInterface $handler
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
* @throws RuntimeException
|
||||||
*/
|
*/
|
||||||
public function process(Request $request, DelegateInterface $delegate)
|
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||||
{
|
{
|
||||||
// Make sure the short URL exists for this short code
|
// Make sure the short URL exists for this short code
|
||||||
$shortCode = $request->getAttribute('shortCode');
|
$shortCode = $request->getAttribute('shortCode');
|
||||||
try {
|
try {
|
||||||
$this->urlShortener->shortCodeToUrl($shortCode);
|
$this->urlShortener->shortCodeToUrl($shortCode);
|
||||||
} catch (InvalidShortCodeException $e) {
|
} catch (InvalidShortCodeException | EntityDoesNotExistException $e) {
|
||||||
$this->logger->warning('Tried to create a QR code with an invalid short code' . PHP_EOL . $e);
|
$this->logger->warning('An error occurred while creating QR code' . PHP_EOL . $e);
|
||||||
return $this->buildErrorResponse($request, $delegate);
|
return $this->buildErrorResponse($request, $handler);
|
||||||
} catch (EntityDoesNotExistException $e) {
|
|
||||||
$this->logger->warning('Tried to create a QR code with a not found short code' . PHP_EOL . $e);
|
|
||||||
return $this->buildErrorResponse($request, $delegate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$path = $this->router->generateUri('long-url-redirect', ['shortCode' => $shortCode]);
|
$path = $this->router->generateUri('long-url-redirect', ['shortCode' => $shortCode]);
|
||||||
@@ -80,7 +80,7 @@ class QrCodeAction implements MiddlewareInterface
|
|||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
protected function getSizeParam(Request $request)
|
private function getSizeParam(Request $request): int
|
||||||
{
|
{
|
||||||
$size = (int) $request->getAttribute('size', 300);
|
$size = (int) $request->getAttribute('size', 300);
|
||||||
if ($size < 50) {
|
if ($size < 50) {
|
||||||
|
|||||||
@@ -3,68 +3,15 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink\Core\Action;
|
namespace Shlinkio\Shlink\Core\Action;
|
||||||
|
|
||||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
|
||||||
use Interop\Http\ServerMiddleware\MiddlewareInterface;
|
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
|
||||||
use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait;
|
|
||||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
|
||||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
|
||||||
use Zend\Diactoros\Response\RedirectResponse;
|
use Zend\Diactoros\Response\RedirectResponse;
|
||||||
|
|
||||||
class RedirectAction implements MiddlewareInterface
|
class RedirectAction extends AbstractTrackingAction
|
||||||
{
|
{
|
||||||
use ErrorResponseBuilderTrait;
|
protected function createResp(string $longUrl): Response
|
||||||
|
|
||||||
/**
|
|
||||||
* @var UrlShortenerInterface
|
|
||||||
*/
|
|
||||||
private $urlShortener;
|
|
||||||
/**
|
|
||||||
* @var VisitsTrackerInterface
|
|
||||||
*/
|
|
||||||
private $visitTracker;
|
|
||||||
|
|
||||||
public function __construct(UrlShortenerInterface $urlShortener, VisitsTrackerInterface $visitTracker)
|
|
||||||
{
|
{
|
||||||
$this->urlShortener = $urlShortener;
|
// Return a redirect response to the long URL.
|
||||||
$this->visitTracker = $visitTracker;
|
// Use a temporary redirect to make sure browsers always hit the server for analytics purposes
|
||||||
}
|
return new RedirectResponse($longUrl);
|
||||||
|
|
||||||
/**
|
|
||||||
* Process an incoming server request and return a response, optionally delegating
|
|
||||||
* to the next middleware component to create the response.
|
|
||||||
*
|
|
||||||
* @param Request $request
|
|
||||||
* @param DelegateInterface $delegate
|
|
||||||
*
|
|
||||||
* @return Response
|
|
||||||
*/
|
|
||||||
public function process(Request $request, DelegateInterface $delegate)
|
|
||||||
{
|
|
||||||
$shortCode = $request->getAttribute('shortCode', '');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$longUrl = $this->urlShortener->shortCodeToUrl($shortCode);
|
|
||||||
|
|
||||||
// If provided shortCode does not belong to a valid long URL, dispatch next middleware, which will trigger
|
|
||||||
// a not-found error
|
|
||||||
if ($longUrl === null) {
|
|
||||||
return $delegate->process($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track visit to this short code
|
|
||||||
$this->visitTracker->track($shortCode, $request);
|
|
||||||
|
|
||||||
// Return a redirect response to the long URL.
|
|
||||||
// Use a temporary redirect to make sure browsers always hit the server for analytics purposes
|
|
||||||
return new RedirectResponse($longUrl);
|
|
||||||
} catch (InvalidShortCodeException $e) {
|
|
||||||
return $this->buildErrorResponse($request, $delegate);
|
|
||||||
} catch (EntityDoesNotExistException $e) {
|
|
||||||
return $this->buildErrorResponse($request, $delegate);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,18 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink\Core\Action\Util;
|
namespace Shlinkio\Shlink\Core\Action\Util;
|
||||||
|
|
||||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Shlinkio\Shlink\Core\Response\NotFoundDelegate;
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Shlinkio\Shlink\Core\Response\NotFoundHandler;
|
||||||
|
|
||||||
trait ErrorResponseBuilderTrait
|
trait ErrorResponseBuilderTrait
|
||||||
{
|
{
|
||||||
private function buildErrorResponse(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface
|
private function buildErrorResponse(
|
||||||
{
|
ServerRequestInterface $request,
|
||||||
$request = $request->withAttribute(NotFoundDelegate::NOT_FOUND_TEMPLATE, 'ShlinkCore::invalid-short-code');
|
RequestHandlerInterface $handler
|
||||||
return $delegate->process($request);
|
): ResponseInterface {
|
||||||
|
$request = $request->withAttribute(NotFoundHandler::NOT_FOUND_TEMPLATE, 'ShlinkCore::invalid-short-code');
|
||||||
|
return $handler->handle($request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user