mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-06 23:33:13 +08:00
Compare commits
234 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
482b792adf | ||
|
|
d3a86f4fae | ||
|
|
6de1d1057b | ||
|
|
d7e68d29de | ||
|
|
b8f24c3584 | ||
|
|
26c455616b | ||
|
|
909ecc2387 | ||
|
|
a9dff56a92 | ||
|
|
ef0dd416f9 | ||
|
|
781ca39938 | ||
|
|
ccb9d5e83a | ||
|
|
91f08c9ead | ||
|
|
433a5a923d | ||
|
|
501a933d2e | ||
|
|
633f3b728f | ||
|
|
be34600494 | ||
|
|
9577a4da4b | ||
|
|
a24688b92a | ||
|
|
91442a3379 | ||
|
|
0bd9f1e19f | ||
|
|
c233e807c2 | ||
|
|
a002c60183 | ||
|
|
c522879c64 | ||
|
|
d04abd1f75 | ||
|
|
c2feffa50c | ||
|
|
e282521040 | ||
|
|
d7b7db670f | ||
|
|
5a500a00d7 | ||
|
|
9fb07f4039 | ||
|
|
cb23d38b38 | ||
|
|
af7c11665c | ||
|
|
7f4678261e | ||
|
|
a1c8c51f70 | ||
|
|
6bbe66e8f1 | ||
|
|
5f0d281255 | ||
|
|
fd468cd4e9 | ||
|
|
1f7a94794d | ||
|
|
0232f68b91 | ||
|
|
070055a8b9 | ||
|
|
a3bbd06fe3 | ||
|
|
68b4cfbae0 | ||
|
|
97a54aef06 | ||
|
|
297c88c334 | ||
|
|
fef5390a62 | ||
|
|
08d18b1dc1 | ||
|
|
16a2349d86 | ||
|
|
18d9815e88 | ||
|
|
c8346bc5f8 | ||
|
|
2d85a207d1 | ||
|
|
0df8f17e7b | ||
|
|
29645e77cf | ||
|
|
ea76092681 | ||
|
|
c12e13dfd7 | ||
|
|
566940349f | ||
|
|
391ef5c323 | ||
|
|
70264be8e7 | ||
|
|
6208f6f0d5 | ||
|
|
c422a14c5c | ||
|
|
fbeb959317 | ||
|
|
e53ffc8d43 | ||
|
|
453ca1728e | ||
|
|
d10583c7c5 | ||
|
|
a9d213990e | ||
|
|
6321cc0f2d | ||
|
|
3e5f0b2451 | ||
|
|
4ad167eb30 | ||
|
|
01a4f9f867 | ||
|
|
b93d65ddc1 | ||
|
|
9ef9da0870 | ||
|
|
ba2053bd3a | ||
|
|
1260da85a7 | ||
|
|
960c7a0835 | ||
|
|
574f407a90 | ||
|
|
abf802093c | ||
|
|
7ca22f8629 | ||
|
|
6300982b07 | ||
|
|
54cb40f6ed | ||
|
|
8256f0c757 | ||
|
|
1009f9e7c6 | ||
|
|
9c8eef12ba | ||
|
|
9260b3ac6b | ||
|
|
ff98e1fb3d | ||
|
|
f3389d3738 | ||
|
|
a138f4153d | ||
|
|
602e11d5e7 | ||
|
|
3cd14153ca | ||
|
|
095d8e73b8 | ||
|
|
b37f303e76 | ||
|
|
c8368c9098 | ||
|
|
8d0bac9478 | ||
|
|
286c24f8c0 | ||
|
|
963d26f59b | ||
|
|
e07c464de8 | ||
|
|
575509c45b | ||
|
|
563e654b99 | ||
|
|
3e268e2012 | ||
|
|
b2d9f2fc01 | ||
|
|
6717102dd2 | ||
|
|
1ba7fc81ac | ||
|
|
caf4fa7fdd | ||
|
|
5c7962966d | ||
|
|
95ec7e0afa | ||
|
|
c37660f763 | ||
|
|
486ea10c3c | ||
|
|
e0f18f8d1f | ||
|
|
a66f116d66 | ||
|
|
dd099dc39c | ||
|
|
c05aeabdee | ||
|
|
23922f6c7b | ||
|
|
69a99949e1 | ||
|
|
d56cde72a3 | ||
|
|
99ffff11c7 | ||
|
|
bb050cc1b6 | ||
|
|
3547889ad5 | ||
|
|
479e694478 | ||
|
|
2368b634e3 | ||
|
|
dcc09975a9 | ||
|
|
102f5c4e12 | ||
|
|
cc688fa3ce | ||
|
|
e7f7cbcaac | ||
|
|
f9c56d7cb1 | ||
|
|
1fe2e6f6bd | ||
|
|
c3cc88f03e | ||
|
|
584e1f5643 | ||
|
|
04c479148a | ||
|
|
c45cb7bacb | ||
|
|
17be221920 | ||
|
|
10da57572f | ||
|
|
52478ca60a | ||
|
|
62b49dcb19 | ||
|
|
a365faef9c | ||
|
|
5d2698e8a1 | ||
|
|
ec4a413a5b | ||
|
|
596d1ee797 | ||
|
|
d117f82bcb | ||
|
|
18abe9d0f9 |
25
.gitattributes
vendored
Normal file
25
.gitattributes
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/data/infra export-ignore
|
||||||
|
/docs export-ignore
|
||||||
|
/module/CLI/test export-ignore
|
||||||
|
/module/CLI/test-resources export-ignore
|
||||||
|
/module/Common/test export-ignore
|
||||||
|
/module/Common/test-func export-ignore
|
||||||
|
/module/Core/test export-ignore
|
||||||
|
/module/Core/test-func export-ignore
|
||||||
|
/module/Rest/test export-ignore
|
||||||
|
.env.dist export-ignore
|
||||||
|
.gitattributes export-ignore
|
||||||
|
.gitignore export-ignore
|
||||||
|
.phpstorm.meta.php export-ignore
|
||||||
|
.scrutinizer.yml export-ignore
|
||||||
|
.travis.yml export-ignore
|
||||||
|
build.sh export-ignore
|
||||||
|
CHANGELOG.md export-ignore
|
||||||
|
docker-compose.override.yml.dist export-ignore
|
||||||
|
docker-compose.yml export-ignore
|
||||||
|
func_tests_bootstrap.php export-ignore
|
||||||
|
indocker export-ignore
|
||||||
|
phpcs.xml export-ignore
|
||||||
|
phpunit.xml.dist export-ignore
|
||||||
|
phpunit-func.xml export-ignore
|
||||||
|
phpstan.neon
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ vendor/
|
|||||||
.env
|
.env
|
||||||
data/database.sqlite
|
data/database.sqlite
|
||||||
docs/swagger-ui
|
docs/swagger-ui
|
||||||
|
docker-compose.override.yml
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace PHPSTORM_META;
|
namespace PHPSTORM_META;
|
||||||
|
|
||||||
use Interop\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Zend\ServiceManager\ServiceManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PhpStorm Container Interop code completion
|
* PhpStorm Container Interop code completion
|
||||||
@@ -16,4 +17,7 @@ $STATIC_METHOD_TYPES = [
|
|||||||
ContainerInterface::get('') => [
|
ContainerInterface::get('') => [
|
||||||
'' == '@',
|
'' == '@',
|
||||||
],
|
],
|
||||||
|
ServiceManager::build('') => [
|
||||||
|
'' == '@',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
extension="memcached.so"
|
|
||||||
11
.travis.yml
11
.travis.yml
@@ -2,15 +2,15 @@ language: php
|
|||||||
|
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- /.*/
|
||||||
- develop
|
|
||||||
|
|
||||||
php:
|
php:
|
||||||
- 5.6
|
|
||||||
- 7
|
|
||||||
- 7.1
|
- 7.1
|
||||||
|
- 7.2
|
||||||
|
|
||||||
before_install: phpenv config-add .travis-php.ini
|
before_install:
|
||||||
|
- phpenv config-add data/infra/travis-php/memcached.ini
|
||||||
|
- phpenv config-add data/infra/travis-php/apcu.ini
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- composer self-update
|
- composer self-update
|
||||||
@@ -21,6 +21,7 @@ script:
|
|||||||
- composer check
|
- composer check
|
||||||
|
|
||||||
after_script:
|
after_script:
|
||||||
|
- vendor/bin/phpcov merge build --clover build/clover.xml
|
||||||
- wget https://scrutinizer-ci.com/ocular.phar
|
- wget https://scrutinizer-ci.com/ocular.phar
|
||||||
- php ocular.phar code-coverage:upload --format=php-clover build/clover.xml
|
- php ocular.phar code-coverage:upload --format=php-clover build/clover.xml
|
||||||
|
|
||||||
|
|||||||
106
CHANGELOG.md
106
CHANGELOG.md
@@ -1,5 +1,111 @@
|
|||||||
## CHANGELOG
|
## CHANGELOG
|
||||||
|
|
||||||
|
### 1.8.1
|
||||||
|
|
||||||
|
**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
|
||||||
|
|
||||||
|
**Tasks**
|
||||||
|
|
||||||
|
* [110: Create gitattributes file to define files to be excluded from distributable package](https://github.com/shlinkio/shlink/issues/110)
|
||||||
|
|
||||||
|
### 1.6.0
|
||||||
|
|
||||||
|
**Features**
|
||||||
|
|
||||||
|
* [44: Consider allowing to set custom slugs instead of generating a short code](https://github.com/shlinkio/shlink/issues/44)
|
||||||
|
* [47: Allow to limit short codes availability by date range](https://github.com/shlinkio/shlink/issues/47)
|
||||||
|
* [48: Allow to limit the number of visits to a short code](https://github.com/shlinkio/shlink/issues/48)
|
||||||
|
* [105: Added option to enable/disable URL validation by response status code.](https://github.com/shlinkio/shlink/pull/105)
|
||||||
|
|
||||||
|
**Enhancements:**
|
||||||
|
|
||||||
|
* [27: Add repository functional tests with dbunit](https://github.com/shlinkio/shlink/issues/27)
|
||||||
|
* [86: Drop support for PHP 5](https://github.com/shlinkio/shlink/issues/86)
|
||||||
|
* [101: Make actions just capture very specific exceptions, and let the ErrorHandler catch any other exception](https://github.com/shlinkio/shlink/issues/101)
|
||||||
|
* [104: Use different templates for requested-short-code-does-not-exist and route-could-not-be-match](https://github.com/shlinkio/shlink/issues/104)
|
||||||
|
|
||||||
|
**Tasks**
|
||||||
|
|
||||||
|
* [99: Replace AnnotatedFactory by ConfigAbstractFactory](https://github.com/shlinkio/shlink/issues/99)
|
||||||
|
* [100: Replace twig by plates](https://github.com/shlinkio/shlink/issues/100)
|
||||||
|
* [102: Improve coding standards strictness](https://github.com/shlinkio/shlink/issues/102)
|
||||||
|
|
||||||
|
**Bugs**
|
||||||
|
|
||||||
|
* [103: Make NotFoundDelegate return proper content types based on accepted content](https://github.com/shlinkio/shlink/issues/103)
|
||||||
|
|
||||||
|
### 1.5.0
|
||||||
|
|
||||||
|
**Enhancements:**
|
||||||
|
|
||||||
|
* [95: Add tags CRUD to CLI](https://github.com/shlinkio/shlink/issues/95)
|
||||||
|
* [59: Add tags CRUD to REST](https://github.com/shlinkio/shlink/issues/59)
|
||||||
|
* [66: Allow to import certain information from older app directory when updating](https://github.com/shlinkio/shlink/issues/66)
|
||||||
|
|
||||||
|
**Tasks**
|
||||||
|
|
||||||
|
* [96: Add namespace to functions](https://github.com/shlinkio/shlink/issues/96)
|
||||||
|
* [76: Add response examples to swagger docs](https://github.com/shlinkio/shlink/issues/76)
|
||||||
|
* [93: Improve cross domain management by using the ImplicitOptionsMiddleware](https://github.com/shlinkio/shlink/issues/93)
|
||||||
|
|
||||||
|
**Bugs**
|
||||||
|
|
||||||
|
* [92: Fix formatted dates, using an ISO compliant format](https://github.com/shlinkio/shlink/issues/92)
|
||||||
|
|
||||||
### 1.4.0
|
### 1.4.0
|
||||||
|
|
||||||
**Enhancements:**
|
**Enhancements:**
|
||||||
|
|||||||
7
bin/cli
7
bin/cli
@@ -1,11 +1,10 @@
|
|||||||
#!/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;
|
||||||
|
|
||||||
/** @var ContainerInterface $container */
|
/** @var ContainerInterface $container */
|
||||||
$container = include __DIR__ . '/../config/container.php';
|
$container = include __DIR__ . '/../config/container.php';
|
||||||
|
$container->get(CliApp::class)->run();
|
||||||
/** @var CliApp $app */
|
|
||||||
$app = $container->get(CliApp::class);
|
|
||||||
$app->run();
|
|
||||||
|
|||||||
27
bin/install
27
bin/install
@@ -1,14 +1,29 @@
|
|||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php
|
<?php
|
||||||
use Shlinkio\Shlink\CLI\Command\Install\InstallCommand;
|
|
||||||
|
use Shlinkio\Shlink\CLI\Factory\InstallApplicationFactory;
|
||||||
|
use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizer;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
use Zend\Config\Writer\PhpArray;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
|
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||||
|
use Zend\ServiceManager\ServiceManager;
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
chdir(dirname(__DIR__));
|
||||||
|
|
||||||
require __DIR__ . '/../vendor/autoload.php';
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
$app = new Application();
|
$container = new ServiceManager([
|
||||||
$app->add(new InstallCommand(new PhpArray()));
|
'factories' => [
|
||||||
$app->setDefaultCommand('shlink:install');
|
Application::class => InstallApplicationFactory::class,
|
||||||
$app->run();
|
Filesystem::class => InvokableFactory::class,
|
||||||
|
],
|
||||||
|
'services' => [
|
||||||
|
'config' => [
|
||||||
|
ConfigAbstractFactory::class => [
|
||||||
|
DatabaseConfigCustomizer::class => [Filesystem::class]
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$container->build(Application::class)->run();
|
||||||
|
|||||||
27
bin/update
27
bin/update
@@ -1,14 +1,29 @@
|
|||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php
|
<?php
|
||||||
use Shlinkio\Shlink\CLI\Command\Install\UpdateCommand;
|
|
||||||
|
use Shlinkio\Shlink\CLI\Factory\InstallApplicationFactory;
|
||||||
|
use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizer;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
use Zend\Config\Writer\PhpArray;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
|
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||||
|
use Zend\ServiceManager\ServiceManager;
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
chdir(dirname(__DIR__));
|
||||||
|
|
||||||
require __DIR__ . '/../vendor/autoload.php';
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
$app = new Application();
|
$container = new ServiceManager([
|
||||||
$app->add(new UpdateCommand(new PhpArray()));
|
'factories' => [
|
||||||
$app->setDefaultCommand('shlink:install');
|
Application::class => InstallApplicationFactory::class,
|
||||||
$app->run();
|
Filesystem::class => InvokableFactory::class,
|
||||||
|
],
|
||||||
|
'services' => [
|
||||||
|
'config' => [
|
||||||
|
ConfigAbstractFactory::class => [
|
||||||
|
DatabaseConfigCustomizer::class => [Filesystem::class]
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$container->build(Application::class, ['isUpdate' => true])->run();
|
||||||
|
|||||||
14
build.sh
14
build.sh
@@ -8,7 +8,7 @@ if [ "$#" -ne 1 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
version=$1
|
version=$1
|
||||||
builtcontent=$(readlink -f '../shlink_build_tmp')
|
builtcontent=$(readlink -f "../shlink_${version}_dist")
|
||||||
projectdir=$(pwd)
|
projectdir=$(pwd)
|
||||||
|
|
||||||
# Copy project content to temp dir
|
# Copy project content to temp dir
|
||||||
@@ -20,8 +20,8 @@ cp -R "${projectdir}"/* "${builtcontent}"
|
|||||||
cd "${builtcontent}"
|
cd "${builtcontent}"
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
rm -r vendor
|
rm -rf vendor
|
||||||
rm composer.lock
|
rm -f composer.lock
|
||||||
composer self-update
|
composer self-update
|
||||||
composer install --no-dev --optimize-autoloader --no-progress --no-interaction
|
composer install --no-dev --optimize-autoloader --no-progress --no-interaction
|
||||||
|
|
||||||
@@ -31,10 +31,12 @@ rm build.sh
|
|||||||
rm CHANGELOG.md
|
rm CHANGELOG.md
|
||||||
rm composer.*
|
rm composer.*
|
||||||
rm LICENSE
|
rm LICENSE
|
||||||
|
rm indocker
|
||||||
|
rm docker-compose.yml
|
||||||
rm php*
|
rm php*
|
||||||
rm README.md
|
rm README.md
|
||||||
rm -r build
|
rm -rf build
|
||||||
rm -f data/database.sqlite
|
rm -ff data/database.sqlite
|
||||||
rm -rf data/infra
|
rm -rf data/infra
|
||||||
rm -rf data/{cache,log,proxies}/{*,.gitignore}
|
rm -rf data/{cache,log,proxies}/{*,.gitignore}
|
||||||
rm -rf config/params/{*,.gitignore}
|
rm -rf config/params/{*,.gitignore}
|
||||||
@@ -42,5 +44,5 @@ rm -rf config/autoload/{{,*.}local.php{,.dist},.gitignore}
|
|||||||
|
|
||||||
# Compressing file
|
# Compressing file
|
||||||
rm -f "${projectdir}"/build/shlink_${version}_dist.zip
|
rm -f "${projectdir}"/build/shlink_${version}_dist.zip
|
||||||
zip -ry "${projectdir}"/build/shlink_${version}_dist.zip .
|
zip -ry "${projectdir}"/build/shlink_${version}_dist.zip "../shlink_${version}_dist"
|
||||||
rm -rf "${builtcontent}"
|
rm -rf "${builtcontent}"
|
||||||
|
|||||||
@@ -12,37 +12,51 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^5.6 || ^7.0",
|
"php": "^7.1",
|
||||||
"zendframework/zend-expressive": "^2.0",
|
"acelaya/ze-content-based-error-handler": "^2.2",
|
||||||
"zendframework/zend-expressive-fastroute": "^2.0",
|
"cocur/slugify": "^3.0",
|
||||||
"zendframework/zend-expressive-twigrenderer": "^1.4",
|
"doctrine/annotations": "^1.4",
|
||||||
"zendframework/zend-stdlib": "^3.0",
|
"doctrine/cache": "^1.6",
|
||||||
"zendframework/zend-servicemanager": "^3.0",
|
"doctrine/collections": "^1.4",
|
||||||
"zendframework/zend-paginator": "^2.6",
|
"doctrine/common": "^2.7",
|
||||||
"zendframework/zend-config": "^3.0",
|
"doctrine/dbal": "^2.5",
|
||||||
"zendframework/zend-i18n": "^2.7",
|
"doctrine/migrations": "^1.4",
|
||||||
"zendframework/zend-config-aggregator": "^0.1",
|
|
||||||
"acelaya/zsm-annotated-services": "^1.0",
|
|
||||||
"acelaya/ze-content-based-error-handler": "^2.0",
|
|
||||||
"doctrine/orm": "^2.5",
|
"doctrine/orm": "^2.5",
|
||||||
"guzzlehttp/guzzle": "^6.2",
|
"endroid/qr-code": "^1.7",
|
||||||
"symfony/console": "^3.0",
|
|
||||||
"symfony/process": "^3.0",
|
|
||||||
"symfony/filesystem": "^3.0",
|
|
||||||
"firebase/php-jwt": "^4.0",
|
"firebase/php-jwt": "^4.0",
|
||||||
"monolog/monolog": "^1.21",
|
"guzzlehttp/guzzle": "^6.2",
|
||||||
"theorchard/monolog-cascade": "^0.4",
|
|
||||||
"endroid/qrcode": "^1.7",
|
|
||||||
"mikehaertl/phpwkhtmltopdf": "^2.2",
|
"mikehaertl/phpwkhtmltopdf": "^2.2",
|
||||||
"doctrine/migrations": "^1.4"
|
"monolog/monolog": "^1.21",
|
||||||
|
"roave/security-advisories": "dev-master",
|
||||||
|
"symfony/console": "^4.0",
|
||||||
|
"symfony/filesystem": "^4.0",
|
||||||
|
"symfony/process": "^4.0",
|
||||||
|
"theorchard/monolog-cascade": "^0.4",
|
||||||
|
"zendframework/zend-config": "^3.0",
|
||||||
|
"zendframework/zend-config-aggregator": "^1.0",
|
||||||
|
"zendframework/zend-diactoros": "^1.7",
|
||||||
|
"zendframework/zend-expressive": "^3.0",
|
||||||
|
"zendframework/zend-expressive-fastroute": "^3.0",
|
||||||
|
"zendframework/zend-expressive-helpers": "^5.0",
|
||||||
|
"zendframework/zend-expressive-platesrenderer": "^2.0",
|
||||||
|
"zendframework/zend-i18n": "^2.7",
|
||||||
|
"zendframework/zend-inputfilter": "^2.8",
|
||||||
|
"zendframework/zend-paginator": "^2.6",
|
||||||
|
"zendframework/zend-servicemanager": "^3.2",
|
||||||
|
"zendframework/zend-stdlib": "^3.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^5.7 || ^6.0",
|
|
||||||
"squizlabs/php_codesniffer": "^2.3",
|
|
||||||
"roave/security-advisories": "dev-master",
|
|
||||||
"filp/whoops": "^2.0",
|
"filp/whoops": "^2.0",
|
||||||
"symfony/var-dumper": "^3.0",
|
"infection/infection": "^0.8.1",
|
||||||
"vlucas/phpdotenv": "^2.2"
|
"phpstan/phpstan": "0.9",
|
||||||
|
"phpunit/phpcov": "^5.0",
|
||||||
|
"phpunit/phpunit": "^7.0",
|
||||||
|
"slevomat/coding-standard": "^4.0",
|
||||||
|
"squizlabs/php_codesniffer": "^3.1 <3.2",
|
||||||
|
"symfony/dotenv": "^4.0",
|
||||||
|
"symfony/var-dumper": "^4.0",
|
||||||
|
"zendframework/zend-component-installer": "^2.1",
|
||||||
|
"zendframework/zend-expressive-tooling": "^1.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
@@ -59,22 +73,45 @@
|
|||||||
"psr-4": {
|
"psr-4": {
|
||||||
"ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test",
|
"ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test",
|
||||||
"ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test",
|
"ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test",
|
||||||
"ShlinkioTest\\Shlink\\Core\\": "module/Core/test",
|
"ShlinkioTest\\Shlink\\Core\\": [
|
||||||
"ShlinkioTest\\Shlink\\Common\\": "module/Common/test"
|
"module/Core/test",
|
||||||
|
"module/Core/test-func"
|
||||||
|
],
|
||||||
|
"ShlinkioTest\\Shlink\\Common\\": [
|
||||||
|
"module/Common/test",
|
||||||
|
"module/Common/test-func"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"check": [
|
"check": [
|
||||||
"@cs",
|
"@cs",
|
||||||
"@test"
|
"@stan",
|
||||||
|
"@test",
|
||||||
|
"@func-test",
|
||||||
|
"@infect"
|
||||||
],
|
],
|
||||||
"cs": "phpcs",
|
"cs": "phpcs",
|
||||||
"cs-fix": "phpcbf",
|
"cs-fix": "phpcbf",
|
||||||
"serve": "php -S 0.0.0.0:8000 -t public/",
|
"serve": "php -S 0.0.0.0:8000 -t public/",
|
||||||
"test": "phpunit --coverage-clover build/clover.xml",
|
"test": "phpunit --coverage-php build/coverage-unit.cov",
|
||||||
"pretty-test": "phpunit --coverage-html build/coverage"
|
"pretty-test": "phpunit --coverage-html build/coverage",
|
||||||
|
"func-test": "phpunit -c phpunit-func.xml --coverage-php build/coverage-func.cov",
|
||||||
|
"complete-pretty-test": [
|
||||||
|
"@test",
|
||||||
|
"@func-test",
|
||||||
|
"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,
|
||||||
|
"platform": {
|
||||||
|
"php": "7.1.8"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
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' => env('SECRET_KEY'),
|
'secret_key' => env('SECRET_KEY'),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,27 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Common\Factory\EmptyResponseImplicitOptionsMiddlewareFactory;
|
||||||
use Zend\Expressive;
|
use Zend\Expressive;
|
||||||
use Zend\Expressive\Container;
|
use Zend\Expressive\Container;
|
||||||
use Zend\Expressive\Router;
|
use Zend\Expressive\Helper;
|
||||||
use Zend\Expressive\Template;
|
use Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware;
|
||||||
use Zend\Expressive\Twig;
|
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 => Twig\TwigRendererFactory::class,
|
|
||||||
\Twig_Environment::class => Twig\TwigEnvironmentFactory::class,
|
Helper\UrlHelper::class => Helper\UrlHelperFactory::class,
|
||||||
Router\RouterInterface::class => Router\FastRouteRouterFactory::class,
|
Helper\ServerUrlHelper::class => InvokableFactory::class,
|
||||||
ErrorHandler::class => Container\ErrorHandlerFactory::class,
|
],
|
||||||
|
|
||||||
|
'delegators' => [
|
||||||
|
Expressive\Application::class => [
|
||||||
|
Container\ApplicationConfigInjectionDelegator::class,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Common;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'entity_manager' => [
|
'entity_manager' => [
|
||||||
@@ -6,9 +10,9 @@ return [
|
|||||||
'proxies_dir' => 'data/proxies',
|
'proxies_dir' => 'data/proxies',
|
||||||
],
|
],
|
||||||
'connection' => [
|
'connection' => [
|
||||||
'user' => env('DB_USER'),
|
'user' => Common\env('DB_USER'),
|
||||||
'password' => env('DB_PASSWORD'),
|
'password' => Common\env('DB_PASSWORD'),
|
||||||
'dbname' => env('DB_NAME', 'shlink'),
|
'dbname' => Common\env('DB_NAME', 'shlink'),
|
||||||
'charset' => 'utf8',
|
'charset' => 'utf8',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Monolog\Handler\RotatingFileHandler;
|
use Monolog\Handler\RotatingFileHandler;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Shlinkio\Shlink\Common\Middleware\LocaleMiddleware;
|
use Shlinkio\Shlink\Common\Middleware\LocaleMiddleware;
|
||||||
|
use Shlinkio\Shlink\Core\Response\NotFoundDelegate;
|
||||||
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;
|
||||||
use Shlinkio\Shlink\Rest\Middleware\PathVersionMiddleware;
|
use Shlinkio\Shlink\Rest\Middleware\PathVersionMiddleware;
|
||||||
use Zend\Expressive\Container\ApplicationFactory;
|
use Zend\Expressive;
|
||||||
use Zend\Stratigility\Middleware\ErrorHandler;
|
use Zend\Stratigility\Middleware\ErrorHandler;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -18,7 +21,7 @@ return [
|
|||||||
'priority' => 11,
|
'priority' => 11,
|
||||||
],
|
],
|
||||||
'pre-routing-rest' => [
|
'pre-routing-rest' => [
|
||||||
'path' => '/rest',
|
// 'path' => '/rest',
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
PathVersionMiddleware::class,
|
PathVersionMiddleware::class,
|
||||||
],
|
],
|
||||||
@@ -27,7 +30,7 @@ return [
|
|||||||
|
|
||||||
'routing' => [
|
'routing' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
ApplicationFactory::ROUTING_MIDDLEWARE,
|
Expressive\Router\Middleware\RouteMiddleware::class,
|
||||||
],
|
],
|
||||||
'priority' => 10,
|
'priority' => 10,
|
||||||
],
|
],
|
||||||
@@ -36,6 +39,7 @@ return [
|
|||||||
'path' => '/rest',
|
'path' => '/rest',
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
CrossDomainMiddleware::class,
|
CrossDomainMiddleware::class,
|
||||||
|
Expressive\Router\Middleware\ImplicitOptionsMiddleware::class,
|
||||||
BodyParserMiddleware::class,
|
BodyParserMiddleware::class,
|
||||||
CheckAuthenticationMiddleware::class,
|
CheckAuthenticationMiddleware::class,
|
||||||
],
|
],
|
||||||
@@ -44,7 +48,8 @@ return [
|
|||||||
|
|
||||||
'post-routing' => [
|
'post-routing' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
ApplicationFactory::DISPATCH_MIDDLEWARE,
|
Expressive\Router\Middleware\DispatchMiddleware::class,
|
||||||
|
NotFoundDelegate::class,
|
||||||
],
|
],
|
||||||
'priority' => 1,
|
'priority' => 1,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'phpwkhtmltopdf' => [
|
'phpwkhtmltopdf' => [
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'preview_generation' => [
|
'preview_generation' => [
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Zend\Expressive\Router\FastRouteRouter;
|
use Zend\Expressive\Router\FastRouteRouter;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'twig' => [
|
'templates' => [
|
||||||
'cache_dir' => 'data/cache/twig',
|
'extension' => 'phtml',
|
||||||
|
],
|
||||||
|
|
||||||
|
'plates' => [
|
||||||
'extensions' => [
|
'extensions' => [
|
||||||
// extension service names or instances
|
// extension service names or instances
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Common;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'translator' => [
|
'translator' => [
|
||||||
'locale' => env('DEFAULT_LOCALE', 'en'),
|
'locale' => Common\env('DEFAULT_LOCALE', 'en'),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||||
|
use function Shlinkio\Shlink\Common\env;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
@@ -9,6 +12,7 @@ return [
|
|||||||
'hostname' => env('SHORTENED_URL_HOSTNAME'),
|
'hostname' => env('SHORTENED_URL_HOSTNAME'),
|
||||||
],
|
],
|
||||||
'shortcode_chars' => env('SHORTCODE_CHARS', UrlShortener::DEFAULT_CHARS),
|
'shortcode_chars' => env('SHORTCODE_CHARS', UrlShortener::DEFAULT_CHARS),
|
||||||
|
'validate_url' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,34 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Doctrine\ORM\EntityManager;
|
use Doctrine\ORM\EntityManager;
|
||||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||||
use Interop\Container\ContainerInterface;
|
use Interop\Container\ContainerInterface;
|
||||||
|
use Zend\ServiceManager\ServiceManager;
|
||||||
|
|
||||||
/** @var ContainerInterface $container */
|
$isTest = false;
|
||||||
|
foreach ($_SERVER['argv'] as $i => $arg) {
|
||||||
|
if ($arg === '--test') {
|
||||||
|
unset($_SERVER['argv'][$i]);
|
||||||
|
$isTest = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var ContainerInterface|ServiceManager $container */
|
||||||
$container = include __DIR__ . '/container.php';
|
$container = include __DIR__ . '/container.php';
|
||||||
|
|
||||||
|
// If in testing env, override DB connection to use an in-memory sqlite database
|
||||||
|
if ($isTest) {
|
||||||
|
$container->setAllowOverride(true);
|
||||||
|
$config = $container->get('config');
|
||||||
|
$config['entity_manager']['connection'] = [
|
||||||
|
'driver' => 'pdo_sqlite',
|
||||||
|
'path' => realpath(sys_get_temp_dir()) . '/shlink-tests.db',
|
||||||
|
];
|
||||||
|
$container->setService('config', $config);
|
||||||
|
}
|
||||||
|
|
||||||
/** @var EntityManager $em */
|
/** @var EntityManager $em */
|
||||||
$em = $container->get(EntityManager::class);
|
$em = $container->get(EntityManager::class);
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Acelaya\ExpressiveErrorHandler;
|
use Acelaya\ExpressiveErrorHandler;
|
||||||
use Shlinkio\Shlink\CLI;
|
use Shlinkio\Shlink\CLI;
|
||||||
use Shlinkio\Shlink\Common;
|
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``.
|
||||||
@@ -16,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,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
use Dotenv\Dotenv;
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Symfony\Component\Dotenv\Dotenv;
|
||||||
use Zend\ServiceManager\ServiceManager;
|
use Zend\ServiceManager\ServiceManager;
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
chdir(dirname(__DIR__));
|
||||||
@@ -9,9 +11,9 @@ require 'vendor/autoload.php';
|
|||||||
// If the Dotenv class exists, load env vars and enable errors
|
// If the Dotenv class exists, load env vars and enable errors
|
||||||
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
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ RUN apk add --no-cache --virtual libpng-dev
|
|||||||
RUN docker-php-ext-install gd
|
RUN docker-php-ext-install gd
|
||||||
|
|
||||||
# Install redis extension
|
# Install redis extension
|
||||||
ADD https://github.com/phpredis/phpredis/archive/php7.tar.gz /tmp/phpredis.tar.gz
|
ADD https://github.com/phpredis/phpredis/archive/3.1.4.tar.gz /tmp/phpredis.tar.gz
|
||||||
RUN mkdir -p /usr/src/php/ext/redis\
|
RUN mkdir -p /usr/src/php/ext/redis\
|
||||||
&& tar xf /tmp/phpredis.tar.gz -C /usr/src/php/ext/redis --strip-components=1
|
&& tar xf /tmp/phpredis.tar.gz -C /usr/src/php/ext/redis --strip-components=1
|
||||||
# configure and install
|
# configure and install
|
||||||
@@ -85,3 +85,6 @@ RUN rm /tmp/xdebug.tar.gz
|
|||||||
RUN php -r "readfile('https://getcomposer.org/installer');" | php
|
RUN php -r "readfile('https://getcomposer.org/installer');" | php
|
||||||
RUN chmod +x composer.phar
|
RUN chmod +x composer.phar
|
||||||
RUN mv composer.phar /usr/local/bin/composer
|
RUN mv composer.phar /usr/local/bin/composer
|
||||||
|
|
||||||
|
# Make home directory writable by anyone
|
||||||
|
RUN chmod 777 /home
|
||||||
|
|||||||
1
data/infra/travis-php/apcu.ini
Normal file
1
data/infra/travis-php/apcu.ini
Normal file
@@ -0,0 +1 @@
|
|||||||
|
extension="apcu.so"
|
||||||
1
data/infra/travis-php/memcached.ini
Normal file
1
data/infra/travis-php/memcached.ini
Normal file
@@ -0,0 +1 @@
|
|||||||
|
extension="memcached.so"
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
|
|||||||
49
data/migrations/Version20171021093246.php
Normal file
49
data/migrations/Version20171021093246.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Migrations\AbstractMigration;
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\DBAL\Schema\SchemaException;
|
||||||
|
use Doctrine\DBAL\Types\Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
class Version20171021093246 extends AbstractMigration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Schema $schema
|
||||||
|
* @throws SchemaException
|
||||||
|
*/
|
||||||
|
public function up(Schema $schema)
|
||||||
|
{
|
||||||
|
$shortUrls = $schema->getTable('short_urls');
|
||||||
|
if ($shortUrls->hasColumn('valid_since')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$shortUrls->addColumn('valid_since', Type::DATETIME, [
|
||||||
|
'notnull' => false,
|
||||||
|
]);
|
||||||
|
$shortUrls->addColumn('valid_until', Type::DATETIME, [
|
||||||
|
'notnull' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Schema $schema
|
||||||
|
* @throws SchemaException
|
||||||
|
*/
|
||||||
|
public function down(Schema $schema)
|
||||||
|
{
|
||||||
|
$shortUrls = $schema->getTable('short_urls');
|
||||||
|
if (! $shortUrls->hasColumn('valid_since')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$shortUrls->dropColumn('valid_since');
|
||||||
|
$shortUrls->dropColumn('valid_until');
|
||||||
|
}
|
||||||
|
}
|
||||||
46
data/migrations/Version20171022064541.php
Normal file
46
data/migrations/Version20171022064541.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Migrations\AbstractMigration;
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\DBAL\Schema\SchemaException;
|
||||||
|
use Doctrine\DBAL\Types\Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
class Version20171022064541 extends AbstractMigration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Schema $schema
|
||||||
|
* @throws SchemaException
|
||||||
|
*/
|
||||||
|
public function up(Schema $schema)
|
||||||
|
{
|
||||||
|
$shortUrls = $schema->getTable('short_urls');
|
||||||
|
if ($shortUrls->hasColumn('max_visits')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$shortUrls->addColumn('max_visits', Type::INTEGER, [
|
||||||
|
'unsigned' => true,
|
||||||
|
'notnull' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Schema $schema
|
||||||
|
* @throws SchemaException
|
||||||
|
*/
|
||||||
|
public function down(Schema $schema)
|
||||||
|
{
|
||||||
|
$shortUrls = $schema->getTable('short_urls');
|
||||||
|
if (! $shortUrls->hasColumn('max_visits')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$shortUrls->dropColumn('max_visits');
|
||||||
|
}
|
||||||
|
}
|
||||||
8
docker-compose.override.yml.dist
Normal file
8
docker-compose.override.yml.dist
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
services:
|
||||||
|
shlink_php:
|
||||||
|
user: 1000:1000
|
||||||
|
volumes:
|
||||||
|
- /etc/passwd:/etc/passwd:ro
|
||||||
|
- /etc/group:/etc/group:ro
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"description": "The authorization token with Bearer type",
|
|
||||||
"required": true,
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
@@ -5,44 +5,76 @@
|
|||||||
],
|
],
|
||||||
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"application/json": {
|
||||||
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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,132 +11,227 @@
|
|||||||
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"application/json": {
|
||||||
|
"shortUrls": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"shortCode": "12C18",
|
||||||
|
"originalUrl": "https://store.steampowered.com",
|
||||||
|
"dateCreated": "2016-08-21T20:34:16+02:00",
|
||||||
|
"visitsCount": 328,
|
||||||
|
"tags": [
|
||||||
|
"games",
|
||||||
|
"tech"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shortCode": "12Kb3",
|
||||||
|
"originalUrl": "https://shlink.io",
|
||||||
|
"dateCreated": "2016-05-01T20:34:16+02:00",
|
||||||
|
"visitsCount": 1029,
|
||||||
|
"tags": [
|
||||||
|
"shlink"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shortCode": "123bA",
|
||||||
|
"originalUrl": "https://www.google.com",
|
||||||
|
"dateCreated": "2015-10-01T20:34:16+02:00",
|
||||||
|
"visitsCount": 25,
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pagination": {
|
||||||
|
"currentPage": 5,
|
||||||
|
"pagesCount": 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,43 +9,152 @@
|
|||||||
{
|
{
|
||||||
"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."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"application/json": {
|
||||||
|
"longUrl": "https://shlink.io"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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,55 +10,96 @@
|
|||||||
{
|
{
|
||||||
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"application/json": {
|
||||||
|
"tags": [
|
||||||
|
"games",
|
||||||
|
"tech"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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,44 +10,86 @@
|
|||||||
{
|
{
|
||||||
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"application/json": {
|
||||||
|
"visits": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"referer": "https://twitter.com",
|
||||||
|
"date": "2015-08-20T05:05:03+04:00",
|
||||||
|
"remoteAddr": "10.20.30.40",
|
||||||
|
"userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"referer": "https://t.co",
|
||||||
|
"date": "2015-08-20T05:05:03+04:00",
|
||||||
|
"remoteAddr": "11.22.33.44",
|
||||||
|
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"referer": null,
|
||||||
|
"date": "2015-08-20T05:05:03+04:00",
|
||||||
|
"remoteAddr": "110.220.5.6",
|
||||||
|
"userAgent": "some_web_crawler/1.4"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
259
docs/swagger/paths/v1_tags.json
Normal file
259
docs/swagger/paths/v1_tags.json
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
{
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Tags"
|
||||||
|
],
|
||||||
|
"summary": "List existing tags",
|
||||||
|
"description": "Returns the list of all tags used in any short URL, ordered by name",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "The list of tags",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"tags": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"application/json": {
|
||||||
|
"tags": {
|
||||||
|
"data": [
|
||||||
|
"games",
|
||||||
|
"php",
|
||||||
|
"shlink",
|
||||||
|
"tech"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Unexpected error.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Tags"
|
||||||
|
],
|
||||||
|
"summary": "Create tags",
|
||||||
|
"description": "Provided a list of tags, creates all that do not yet exist",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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": {
|
||||||
|
"200": {
|
||||||
|
"description": "The list of tags",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"tags": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"application/json": {
|
||||||
|
"tags": {
|
||||||
|
"data": [
|
||||||
|
"games",
|
||||||
|
"php",
|
||||||
|
"shlink",
|
||||||
|
"tech"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Unexpected error.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"put": {
|
||||||
|
"tags": [
|
||||||
|
"Tags"
|
||||||
|
],
|
||||||
|
"summary": "Rename tag",
|
||||||
|
"description": "Renames one existing tag",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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": {
|
||||||
|
"204": {
|
||||||
|
"description": "The tag has been properly renamed"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "You have not provided either the oldName or the newName params.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "There's no tag found with the name provided in oldName param.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Unexpected error.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"delete": {
|
||||||
|
"tags": [
|
||||||
|
"Tags"
|
||||||
|
],
|
||||||
|
"summary": "Delete tags",
|
||||||
|
"description": "Deletes provided list of tags",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "tags[]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "The names of the tags to delete",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "Tags properly deleted"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Unexpected error.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../definitions/Error.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +1,58 @@
|
|||||||
{
|
{
|
||||||
"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.2.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"
|
||||||
},
|
},
|
||||||
|
|
||||||
"/v1/short-codes": {
|
"/v1/short-codes": {
|
||||||
"$ref": "paths/v1_short-codes.json"
|
"$ref": "paths/v1_short-codes.json"
|
||||||
},
|
},
|
||||||
"/v1/short-codes/{shortCode}": {
|
"/v1/short-codes/{shortCode}": {
|
||||||
"$ref": "paths/v1_short-codes_{shortCode}.json"
|
"$ref": "paths/v1_short-codes_{shortCode}.json"
|
||||||
},
|
},
|
||||||
"/v1/short-codes/{shortCode}/visits": {
|
|
||||||
"$ref": "paths/v1_short-codes_{shortCode}_visits.json"
|
|
||||||
},
|
|
||||||
"/v1/short-codes/{shortCode}/tags": {
|
"/v1/short-codes/{shortCode}/tags": {
|
||||||
"$ref": "paths/v1_short-codes_{shortCode}_tags.json"
|
"$ref": "paths/v1_short-codes_{shortCode}_tags.json"
|
||||||
|
},
|
||||||
|
|
||||||
|
"/v1/tags": {
|
||||||
|
"$ref": "paths/v1_tags.json"
|
||||||
|
},
|
||||||
|
|
||||||
|
"/v1/short-codes/{shortCode}/visits": {
|
||||||
|
"$ref": "paths/v1_short-codes_{shortCode}_visits.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
func_tests_bootstrap.php
Normal file
33
func_tests_bootstrap.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use ShlinkioTest\Shlink\Common\DbUnit\DatabaseTestCase;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
use Zend\ServiceManager\ServiceManager;
|
||||||
|
|
||||||
|
// Create an empty .env file
|
||||||
|
if (! file_exists('.env')) {
|
||||||
|
touch('.env');
|
||||||
|
}
|
||||||
|
|
||||||
|
$shlinkDbPath = realpath(sys_get_temp_dir()) . '/shlink-tests.db';
|
||||||
|
if (file_exists($shlinkDbPath)) {
|
||||||
|
unlink($shlinkDbPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var ServiceManager $sm */
|
||||||
|
$sm = require __DIR__ . '/config/container.php';
|
||||||
|
$sm->setAllowOverride(true);
|
||||||
|
$config = $sm->get('config');
|
||||||
|
$config['entity_manager']['connection'] = [
|
||||||
|
'driver' => 'pdo_sqlite',
|
||||||
|
'path' => $shlinkDbPath,
|
||||||
|
];
|
||||||
|
$sm->setService('config', $config);
|
||||||
|
|
||||||
|
// Create database
|
||||||
|
$process = new Process('vendor/bin/doctrine orm:schema-tool:create --no-interaction -q --test', __DIR__);
|
||||||
|
$process->inheritEnvironmentVariables()
|
||||||
|
->mustRun();
|
||||||
|
|
||||||
|
DatabaseTestCase::$em = $sm->get('em');
|
||||||
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": "."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,34 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Shlinkio\Shlink\CLI\Command;
|
use Shlinkio\Shlink\CLI\Command;
|
||||||
|
use Shlinkio\Shlink\Common;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'cli' => [
|
'cli' => [
|
||||||
'locale' => 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\Api\GenerateKeyCommand::NAME => Command\Api\GenerateKeyCommand::class,
|
||||||
|
Command\Api\DisableKeyCommand::NAME => Command\Api\DisableKeyCommand::class,
|
||||||
|
Command\Api\ListKeysCommand::NAME => Command\Api\ListKeysCommand::class,
|
||||||
|
|
||||||
|
Command\Tag\ListTagsCommand::NAME => Command\Tag\ListTagsCommand::class,
|
||||||
|
Command\Tag\CreateTagCommand::NAME => Command\Tag\CreateTagCommand::class,
|
||||||
|
Command\Tag\RenameTagCommand::NAME => Command\Tag\RenameTagCommand::class,
|
||||||
|
Command\Tag\DeleteTagsCommand::NAME => Command\Tag\DeleteTagsCommand::class,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
use Acelaya\ZsmAnnotatedServices\Factory\V3\AnnotatedFactory;
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Shlinkio\Shlink\CLI\Command;
|
use Shlinkio\Shlink\CLI\Command;
|
||||||
use Shlinkio\Shlink\CLI\Factory\ApplicationFactory;
|
use Shlinkio\Shlink\CLI\Factory\ApplicationFactory;
|
||||||
|
use Shlinkio\Shlink\Common\Service\IpLocationResolver;
|
||||||
|
use Shlinkio\Shlink\Common\Service\PreviewGenerator;
|
||||||
|
use Shlinkio\Shlink\Core\Service;
|
||||||
|
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
|
use Zend\I18n\Translator\Translator;
|
||||||
|
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
@@ -10,18 +17,52 @@ return [
|
|||||||
'factories' => [
|
'factories' => [
|
||||||
Application::class => ApplicationFactory::class,
|
Application::class => ApplicationFactory::class,
|
||||||
|
|
||||||
Command\Shortcode\GenerateShortcodeCommand::class => AnnotatedFactory::class,
|
Command\Shortcode\GenerateShortcodeCommand::class => ConfigAbstractFactory::class,
|
||||||
Command\Shortcode\ResolveUrlCommand::class => AnnotatedFactory::class,
|
Command\Shortcode\ResolveUrlCommand::class => ConfigAbstractFactory::class,
|
||||||
Command\Shortcode\ListShortcodesCommand::class => AnnotatedFactory::class,
|
Command\Shortcode\ListShortcodesCommand::class => ConfigAbstractFactory::class,
|
||||||
Command\Shortcode\GetVisitsCommand::class => AnnotatedFactory::class,
|
Command\Shortcode\GetVisitsCommand::class => ConfigAbstractFactory::class,
|
||||||
Command\Shortcode\GeneratePreviewCommand::class => AnnotatedFactory::class,
|
Command\Shortcode\GeneratePreviewCommand::class => ConfigAbstractFactory::class,
|
||||||
Command\Visit\ProcessVisitsCommand::class => AnnotatedFactory::class,
|
Command\Visit\ProcessVisitsCommand::class => ConfigAbstractFactory::class,
|
||||||
Command\Config\GenerateCharsetCommand::class => AnnotatedFactory::class,
|
Command\Config\GenerateCharsetCommand::class => ConfigAbstractFactory::class,
|
||||||
Command\Config\GenerateSecretCommand::class => AnnotatedFactory::class,
|
Command\Config\GenerateSecretCommand::class => ConfigAbstractFactory::class,
|
||||||
Command\Api\GenerateKeyCommand::class => AnnotatedFactory::class,
|
Command\Api\GenerateKeyCommand::class => ConfigAbstractFactory::class,
|
||||||
Command\Api\DisableKeyCommand::class => AnnotatedFactory::class,
|
Command\Api\DisableKeyCommand::class => ConfigAbstractFactory::class,
|
||||||
Command\Api\ListKeysCommand::class => AnnotatedFactory::class,
|
Command\Api\ListKeysCommand::class => ConfigAbstractFactory::class,
|
||||||
|
Command\Tag\ListTagsCommand::class => ConfigAbstractFactory::class,
|
||||||
|
Command\Tag\CreateTagCommand::class => ConfigAbstractFactory::class,
|
||||||
|
Command\Tag\RenameTagCommand::class => ConfigAbstractFactory::class,
|
||||||
|
Command\Tag\DeleteTagsCommand::class => ConfigAbstractFactory::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
ConfigAbstractFactory::class => [
|
||||||
|
Command\Shortcode\GenerateShortcodeCommand::class => [
|
||||||
|
Service\UrlShortener::class,
|
||||||
|
'translator',
|
||||||
|
'config.url_shortener.domain',
|
||||||
|
],
|
||||||
|
Command\Shortcode\ResolveUrlCommand::class => [Service\UrlShortener::class, 'translator'],
|
||||||
|
Command\Shortcode\ListShortcodesCommand::class => [Service\ShortUrlService::class, 'translator'],
|
||||||
|
Command\Shortcode\GetVisitsCommand::class => [Service\VisitsTracker::class, 'translator'],
|
||||||
|
Command\Shortcode\GeneratePreviewCommand::class => [
|
||||||
|
Service\ShortUrlService::class,
|
||||||
|
PreviewGenerator::class,
|
||||||
|
'translator',
|
||||||
|
],
|
||||||
|
Command\Visit\ProcessVisitsCommand::class => [
|
||||||
|
Service\VisitService::class,
|
||||||
|
IpLocationResolver::class,
|
||||||
|
'translator',
|
||||||
|
],
|
||||||
|
Command\Config\GenerateCharsetCommand::class => ['translator'],
|
||||||
|
Command\Config\GenerateSecretCommand::class => ['translator'],
|
||||||
|
Command\Api\GenerateKeyCommand::class => [ApiKeyService::class, 'translator'],
|
||||||
|
Command\Api\DisableKeyCommand::class => [ApiKeyService::class, 'translator'],
|
||||||
|
Command\Api\ListKeysCommand::class => [ApiKeyService::class, 'translator'],
|
||||||
|
Command\Tag\ListTagsCommand::class => [Service\Tag\TagService::class, Translator::class],
|
||||||
|
Command\Tag\CreateTagCommand::class => [Service\Tag\TagService::class, Translator::class],
|
||||||
|
Command\Tag\RenameTagCommand::class => [Service\Tag\TagService::class, Translator::class],
|
||||||
|
Command\Tag\DeleteTagsCommand::class => [Service\Tag\TagService::class, Translator::class],
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'translator' => [
|
'translator' => [
|
||||||
|
|||||||
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: 2016-10-22 23:12+0200\n"
|
"POT-Creation-Date: 2018-01-21 09:36+0100\n"
|
||||||
"PO-Revision-Date: 2016-10-22 23:13+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 1.8.7.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 "
|
||||||
@@ -92,7 +95,7 @@ msgid "Processing URL %s..."
|
|||||||
msgstr "Procesando URL %s..."
|
msgstr "Procesando URL %s..."
|
||||||
|
|
||||||
msgid " <info>Success!</info>"
|
msgid " <info>Success!</info>"
|
||||||
msgstr "<info>¡Correcto!</info>"
|
msgstr " <info>¡Correcto!</info>"
|
||||||
|
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Error"
|
msgstr "Error"
|
||||||
@@ -107,22 +110,53 @@ msgstr "La URL larga a procesar"
|
|||||||
msgid "Tags to apply to the new short URL"
|
msgid "Tags to apply to the new short URL"
|
||||||
msgstr "Etiquetas a aplicar a la nueva URL acortada"
|
msgstr "Etiquetas a aplicar a la nueva URL acortada"
|
||||||
|
|
||||||
msgid "A long URL was not provided. Which URL do you want to shorten?:"
|
msgid ""
|
||||||
|
"The date from which this short URL will be valid. If someone tries to access "
|
||||||
|
"it before this date, it will not be found."
|
||||||
|
msgstr ""
|
||||||
|
"La fecha desde la cual será válida esta URL acortada. Si alguien intenta "
|
||||||
|
"acceder a ella antes de esta fecha, no será encontrada."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"The date until which this short URL will be valid. If someone tries to "
|
||||||
|
"access it after this date, it will not be found."
|
||||||
|
msgstr ""
|
||||||
|
"La fecha hasta la cual será válida está URL acortada. Si alguien intenta "
|
||||||
|
"acceder a ella después de esta fecha, no será encontrada."
|
||||||
|
|
||||||
|
msgid "If provided, this slug will be used instead of generating a short code"
|
||||||
|
msgstr ""
|
||||||
|
"Si se proporciona, este slug será usado en vez de generar un código corto"
|
||||||
|
|
||||||
|
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."
|
||||||
msgstr "La URL proporcionada \"%s\" e inválida. Prueba con una diferente."
|
msgstr "La URL proporcionada \"%s\" e inválida. Prueba con una diferente."
|
||||||
|
|
||||||
|
#, php-format
|
||||||
|
msgid ""
|
||||||
|
"Provided slug \"%s\" is already in use by another URL. Try with a different "
|
||||||
|
"one."
|
||||||
|
msgstr ""
|
||||||
|
"El slug proporcionado \"%s\" ya está siendo usado para otra URL. Prueba con "
|
||||||
|
"uno diferente."
|
||||||
|
|
||||||
msgid "Returns the detailed visits information for provided short code"
|
msgid "Returns the detailed visits information for provided short code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Devuelve la información detallada de visitas para el código corto "
|
"Devuelve la información detallada de visitas para el código corto "
|
||||||
@@ -140,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"
|
||||||
@@ -196,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"
|
||||||
@@ -208,14 +242,10 @@ 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:"
|
||||||
|
|
||||||
@@ -223,6 +253,57 @@ msgstr "URL larga:"
|
|||||||
msgid "Provided short code \"%s\" has an invalid format."
|
msgid "Provided short code \"%s\" has an invalid format."
|
||||||
msgstr "El código corto proporcionado \"%s\" tiene un formato inválido."
|
msgstr "El código corto proporcionado \"%s\" tiene un formato inválido."
|
||||||
|
|
||||||
|
#, php-format
|
||||||
|
msgid "Provided short code \"%s\" could not be found."
|
||||||
|
msgstr "El código corto proporcionado \"%s\" no ha podido ser encontrado."
|
||||||
|
|
||||||
|
msgid "Creates one or more tags."
|
||||||
|
msgstr "Crea una o más etiquetas."
|
||||||
|
|
||||||
|
msgid "The name of the tags to create"
|
||||||
|
msgstr "El nombre de las etiquetas a crear"
|
||||||
|
|
||||||
|
msgid "You have to provide at least one tag name"
|
||||||
|
msgstr "Debes proporcionar al menos un nombre de etiqueta"
|
||||||
|
|
||||||
|
msgid "Tags properly created"
|
||||||
|
msgstr "Etiquetas correctamente creadas"
|
||||||
|
|
||||||
|
msgid "Deletes one or more tags."
|
||||||
|
msgstr "Elimina una o más etiquetas."
|
||||||
|
|
||||||
|
msgid "The name of the tags to delete"
|
||||||
|
msgstr "El nombre de las etiquetas a eliminar"
|
||||||
|
|
||||||
|
msgid "Tags properly deleted"
|
||||||
|
msgstr "Etiquetas correctamente eliminadas"
|
||||||
|
|
||||||
|
msgid "Lists existing tags."
|
||||||
|
msgstr "Lista las etiquetas existentes."
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
|
msgid "Name"
|
||||||
|
msgstr "Nombre"
|
||||||
|
|
||||||
|
msgid "No tags yet"
|
||||||
|
msgstr "Aún no hay etiquetas"
|
||||||
|
|
||||||
|
msgid "Renames one existing tag."
|
||||||
|
msgstr "Renombra una etiqueta existente."
|
||||||
|
|
||||||
|
msgid "Current name of the tag."
|
||||||
|
msgstr "Nombre actual de la etiqueta."
|
||||||
|
|
||||||
|
msgid "New name of the tag."
|
||||||
|
msgstr "Nuevo nombre de la etiqueta."
|
||||||
|
|
||||||
|
msgid "Tag properly renamed."
|
||||||
|
msgstr "Etiqueta correctamente renombrada."
|
||||||
|
|
||||||
|
#, php-format
|
||||||
|
msgid "A tag with name \"%s\" was not found"
|
||||||
|
msgstr "Una etiqueta con nombre \"%s\" no ha sido encontrada"
|
||||||
|
|
||||||
msgid "Processes visits where location is not set yet"
|
msgid "Processes visits where location is not set yet"
|
||||||
msgstr "Procesa las visitas donde la localización no ha sido establecida aún"
|
msgstr "Procesa las visitas donde la localización no ha sido establecida aún"
|
||||||
|
|
||||||
@@ -238,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"
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Api;
|
namespace Shlinkio\Shlink\CLI\Command\Api;
|
||||||
|
|
||||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
|
||||||
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
|
|
||||||
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\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
|
||||||
*/
|
*/
|
||||||
@@ -21,23 +24,16 @@ class DisableKeyCommand extends Command
|
|||||||
*/
|
*/
|
||||||
private $translator;
|
private $translator;
|
||||||
|
|
||||||
/**
|
|
||||||
* DisableKeyCommand constructor.
|
|
||||||
* @param ApiKeyServiceInterface|ApiKeyService $apiKeyService
|
|
||||||
* @param TranslatorInterface $translator
|
|
||||||
*
|
|
||||||
* @Inject({ApiKeyService::class, "translator"})
|
|
||||||
*/
|
|
||||||
public function __construct(ApiKeyServiceInterface $apiKeyService, TranslatorInterface $translator)
|
public function __construct(ApiKeyServiceInterface $apiKeyService, TranslatorInterface $translator)
|
||||||
{
|
{
|
||||||
$this->apiKeyService = $apiKeyService;
|
$this->apiKeyService = $apiKeyService;
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
parent::__construct(null);
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
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'));
|
||||||
}
|
}
|
||||||
@@ -45,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
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Api;
|
namespace Shlinkio\Shlink\CLI\Command\Api;
|
||||||
|
|
||||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
|
||||||
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
|
|
||||||
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\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
|
||||||
*/
|
*/
|
||||||
@@ -21,23 +24,16 @@ class GenerateKeyCommand extends Command
|
|||||||
*/
|
*/
|
||||||
private $translator;
|
private $translator;
|
||||||
|
|
||||||
/**
|
|
||||||
* GenerateKeyCommand constructor.
|
|
||||||
* @param ApiKeyServiceInterface|ApiKeyService $apiKeyService
|
|
||||||
* @param TranslatorInterface $translator
|
|
||||||
*
|
|
||||||
* @Inject({ApiKeyService::class, "translator"})
|
|
||||||
*/
|
|
||||||
public function __construct(ApiKeyServiceInterface $apiKeyService, TranslatorInterface $translator)
|
public function __construct(ApiKeyServiceInterface $apiKeyService, TranslatorInterface $translator)
|
||||||
{
|
{
|
||||||
$this->apiKeyService = $apiKeyService;
|
$this->apiKeyService = $apiKeyService;
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
parent::__construct(null);
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
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',
|
||||||
@@ -51,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)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Api;
|
namespace Shlinkio\Shlink\CLI\Command\Api;
|
||||||
|
|
||||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
|
||||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||||
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
|
|
||||||
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
|
||||||
*/
|
*/
|
||||||
@@ -23,23 +25,16 @@ class ListKeysCommand extends Command
|
|||||||
*/
|
*/
|
||||||
private $translator;
|
private $translator;
|
||||||
|
|
||||||
/**
|
|
||||||
* ListKeysCommand constructor.
|
|
||||||
* @param ApiKeyServiceInterface|ApiKeyService $apiKeyService
|
|
||||||
* @param TranslatorInterface $translator
|
|
||||||
*
|
|
||||||
* @Inject({ApiKeyService::class, "translator"})
|
|
||||||
*/
|
|
||||||
public function __construct(ApiKeyServiceInterface $apiKeyService, TranslatorInterface $translator)
|
public function __construct(ApiKeyServiceInterface $apiKeyService, TranslatorInterface $translator)
|
||||||
{
|
{
|
||||||
$this->apiKeyService = $apiKeyService;
|
$this->apiKeyService = $apiKeyService;
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
parent::__construct(null);
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
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',
|
||||||
@@ -51,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::ISO8601) : '-';
|
$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() ? '---' : '+++';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,33 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Config;
|
namespace Shlinkio\Shlink\CLI\Command\Config;
|
||||||
|
|
||||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
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
|
||||||
*/
|
*/
|
||||||
private $translator;
|
private $translator;
|
||||||
|
|
||||||
/**
|
|
||||||
* GenerateCharsetCommand constructor.
|
|
||||||
* @param TranslatorInterface $translator
|
|
||||||
*
|
|
||||||
* @Inject({"translator"})
|
|
||||||
*/
|
|
||||||
public function __construct(TranslatorInterface $translator)
|
public function __construct(TranslatorInterface $translator)
|
||||||
{
|
{
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
parent::__construct(null);
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
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'
|
||||||
@@ -39,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)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,35 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Config;
|
namespace Shlinkio\Shlink\CLI\Command\Config;
|
||||||
|
|
||||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
|
||||||
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
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
|
||||||
*/
|
*/
|
||||||
private $translator;
|
private $translator;
|
||||||
|
|
||||||
/**
|
|
||||||
* GenerateCharsetCommand constructor.
|
|
||||||
* @param TranslatorInterface $translator
|
|
||||||
*
|
|
||||||
* @Inject({"translator"})
|
|
||||||
*/
|
|
||||||
public function __construct(TranslatorInterface $translator)
|
public function __construct(TranslatorInterface $translator)
|
||||||
{
|
{
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
parent::__construct(null);
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
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'
|
||||||
));
|
));
|
||||||
@@ -40,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)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,33 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Install;
|
namespace Shlinkio\Shlink\CLI\Command\Install;
|
||||||
|
|
||||||
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
use Psr\Container\ContainerExceptionInterface;
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
use Psr\Container\NotFoundExceptionInterface;
|
||||||
|
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerManagerInterface;
|
||||||
|
use Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
|
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\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\ChoiceQuestion;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\Console\Question\Question;
|
use Symfony\Component\Filesystem\Exception\IOException;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
use Zend\Config\Writer\WriterInterface;
|
use Zend\Config\Writer\WriterInterface;
|
||||||
|
|
||||||
class InstallCommand extends Command
|
class InstallCommand extends Command
|
||||||
{
|
{
|
||||||
use StringUtilsTrait;
|
const GENERATED_CONFIG_PATH = 'config/params/generated_config.php';
|
||||||
|
|
||||||
const DATABASE_DRIVERS = [
|
|
||||||
'MySQL' => 'pdo_mysql',
|
|
||||||
'PostgreSQL' => 'pdo_pgsql',
|
|
||||||
'SQLite' => 'pdo_sqlite',
|
|
||||||
];
|
|
||||||
const SUPPORTED_LANGUAGES = ['en', 'es'];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var InputInterface
|
* @var SymfonyStyle
|
||||||
*/
|
*/
|
||||||
private $input;
|
private $io;
|
||||||
/**
|
|
||||||
* @var OutputInterface
|
|
||||||
*/
|
|
||||||
private $output;
|
|
||||||
/**
|
|
||||||
* @var QuestionHelper
|
|
||||||
*/
|
|
||||||
private $questionHelper;
|
|
||||||
/**
|
/**
|
||||||
* @var ProcessHelper
|
* @var ProcessHelper
|
||||||
*/
|
*/
|
||||||
@@ -43,280 +36,200 @@ class InstallCommand extends Command
|
|||||||
* @var WriterInterface
|
* @var WriterInterface
|
||||||
*/
|
*/
|
||||||
private $configWriter;
|
private $configWriter;
|
||||||
|
/**
|
||||||
|
* @var Filesystem
|
||||||
|
*/
|
||||||
|
private $filesystem;
|
||||||
|
/**
|
||||||
|
* @var ConfigCustomizerManagerInterface
|
||||||
|
*/
|
||||||
|
private $configCustomizers;
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $isUpdate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InstallCommand constructor.
|
* InstallCommand constructor.
|
||||||
* @param WriterInterface $configWriter
|
* @param WriterInterface $configWriter
|
||||||
* @param callable|null $databaseCreationLogic
|
* @param Filesystem $filesystem
|
||||||
|
* @param ConfigCustomizerManagerInterface $configCustomizers
|
||||||
|
* @param bool $isUpdate
|
||||||
|
* @throws LogicException
|
||||||
*/
|
*/
|
||||||
public function __construct(WriterInterface $configWriter)
|
public function __construct(
|
||||||
{
|
WriterInterface $configWriter,
|
||||||
parent::__construct(null);
|
Filesystem $filesystem,
|
||||||
|
ConfigCustomizerManagerInterface $configCustomizers,
|
||||||
|
$isUpdate = false
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
$this->configWriter = $configWriter;
|
$this->configWriter = $configWriter;
|
||||||
|
$this->isUpdate = $isUpdate;
|
||||||
|
$this->filesystem = $filesystem;
|
||||||
|
$this->configCustomizers = $configCustomizers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configure()
|
public function configure()
|
||||||
{
|
{
|
||||||
$this->setName('shlink:install')
|
$this
|
||||||
->setDescription('Installs Shlink');
|
->setName('shlink:install')
|
||||||
|
->setDescription('Installs or updates Shlink');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @return void
|
||||||
|
* @throws ContainerExceptionInterface
|
||||||
|
* @throws NotFoundExceptionInterface
|
||||||
|
*/
|
||||||
public function execute(InputInterface $input, OutputInterface $output)
|
public function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
$this->input = $input;
|
$this->io = new SymfonyStyle($input, $output);
|
||||||
$this->output = $output;
|
|
||||||
$this->questionHelper = $this->getHelper('question');
|
|
||||||
$this->processHelper = $this->getHelper('process');
|
|
||||||
$params = [];
|
|
||||||
|
|
||||||
$output->writeln([
|
$this->io->writeln([
|
||||||
'<info>Welcome to Shlink!!</info>',
|
'<info>Welcome to Shlink!!</info>',
|
||||||
'This process will guide you through the installation.',
|
'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 (file_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...');
|
||||||
if (unlink('data/cache/app_config.php')) {
|
try {
|
||||||
$output->writeln(' <info>Success</info>');
|
$this->filesystem->remove('data/cache/app_config.php');
|
||||||
} else {
|
$this->io->writeln(' <info>Success</info>');
|
||||||
$output->writeln(
|
} catch (IOException $e) {
|
||||||
' <error>Failed!</error> You will have to manually delete the data/cache/app_config.php file to get'
|
$this->io->error(
|
||||||
. ' new config applied.'
|
'Failed! You will have to manually delete the data/cache/app_config.php file to'
|
||||||
|
. ' get new config applied.'
|
||||||
);
|
);
|
||||||
|
if ($this->io->isVerbose()) {
|
||||||
|
$this->getApplication()->renderException($e, $output);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If running update command, ask the user to import previous config
|
||||||
|
$config = $this->isUpdate ? $this->importConfig() : new CustomizableAppConfig();
|
||||||
|
|
||||||
// Ask for custom config params
|
// Ask for custom config params
|
||||||
$params['DATABASE'] = $this->askDatabase();
|
foreach ([
|
||||||
$params['URL_SHORTENER'] = $this->askUrlShortener();
|
Plugin\DatabaseConfigCustomizer::class,
|
||||||
$params['LANGUAGE'] = $this->askLanguage();
|
Plugin\UrlShortenerConfigCustomizer::class,
|
||||||
$params['APP'] = $this->askApplication();
|
Plugin\LanguageConfigCustomizer::class,
|
||||||
|
Plugin\ApplicationConfigCustomizer::class,
|
||||||
|
] as $pluginName) {
|
||||||
|
/** @var Plugin\ConfigCustomizerInterface $configCustomizer */
|
||||||
|
$configCustomizer = $this->configCustomizers->get($pluginName);
|
||||||
|
$configCustomizer->process($this->io, $config);
|
||||||
|
}
|
||||||
|
|
||||||
// Generate config params files
|
// Generate config params files
|
||||||
$config = $this->buildAppConfig($params);
|
$this->configWriter->toFile(self::GENERATED_CONFIG_PATH, $config->getArrayCopy(), false);
|
||||||
$this->configWriter->toFile('config/params/generated_config.php', $config, false);
|
$this->io->writeln(['<info>Custom configuration properly generated!</info>', '']);
|
||||||
$output->writeln(['<info>Custom configuration properly generated!</info>', '']);
|
|
||||||
|
|
||||||
// Generate database
|
// If current command is not update, generate database
|
||||||
if (! $this->createDatabase()) {
|
if (! $this->isUpdate) {
|
||||||
return;
|
$this->io->write('Initializing database...');
|
||||||
|
if (! $this->runCommand(
|
||||||
|
'php vendor/bin/doctrine orm:schema-tool:create',
|
||||||
|
'Error generating database.',
|
||||||
|
$output
|
||||||
|
)) {
|
||||||
|
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->runCommand(
|
||||||
|
'php vendor/bin/doctrine-migrations 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->runCommand(
|
||||||
|
'php vendor/bin/doctrine orm:generate-proxies',
|
||||||
|
'Error generating proxies.',
|
||||||
|
$output
|
||||||
|
)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected function askDatabase()
|
$this->io->success('Installation complete!');
|
||||||
{
|
|
||||||
$params = [];
|
|
||||||
$this->printTitle('DATABASE');
|
|
||||||
|
|
||||||
// Select database type
|
|
||||||
$databases = array_keys(self::DATABASE_DRIVERS);
|
|
||||||
$dbType = $this->questionHelper->ask($this->input, $this->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('Database name', 'shlink');
|
|
||||||
$params['USER'] = $this->ask('Database username');
|
|
||||||
$params['PASSWORD'] = $this->ask('Database password');
|
|
||||||
$params['HOST'] = $this->ask('Database host', 'localhost');
|
|
||||||
$params['PORT'] = $this->ask('Database port', $this->getDefaultDbPort($params['DRIVER']));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $params;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getDefaultDbPort($driver)
|
|
||||||
{
|
|
||||||
return $driver === 'pdo_mysql' ? '3306' : '5432';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function askUrlShortener()
|
|
||||||
{
|
|
||||||
$this->printTitle('URL SHORTENER');
|
|
||||||
|
|
||||||
// Ask for URL shortener params
|
|
||||||
return [
|
|
||||||
'SCHEMA' => $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
|
|
||||||
'<question>Select schema for generated short URLs (defaults to http):</question>',
|
|
||||||
['http', 'https'],
|
|
||||||
0
|
|
||||||
)),
|
|
||||||
'HOSTNAME' => $this->ask('Hostname for generated URLs'),
|
|
||||||
'CHARS' => $this->ask(
|
|
||||||
'Character set for generated short codes (leave empty to autogenerate one)',
|
|
||||||
null,
|
|
||||||
true
|
|
||||||
) ?: str_shuffle(UrlShortener::DEFAULT_CHARS)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function askLanguage()
|
|
||||||
{
|
|
||||||
$this->printTitle('LANGUAGE');
|
|
||||||
|
|
||||||
return [
|
|
||||||
'DEFAULT' => $this->questionHelper->ask($this->input, $this->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($this->input, $this->output, new ChoiceQuestion(
|
|
||||||
'<question>Select default language for CLI executions (defaults to '
|
|
||||||
. self::SUPPORTED_LANGUAGES[0] . '):</question>',
|
|
||||||
self::SUPPORTED_LANGUAGES,
|
|
||||||
0
|
|
||||||
)),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function askApplication()
|
|
||||||
{
|
|
||||||
$this->printTitle('APPLICATION');
|
|
||||||
|
|
||||||
return [
|
|
||||||
'SECRET' => $this->ask(
|
|
||||||
'Define a secret string that will be used to sign API tokens (leave empty to autogenerate one)',
|
|
||||||
null,
|
|
||||||
true
|
|
||||||
) ?: $this->generateRandomString(32),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $text
|
* @return CustomizableAppConfig
|
||||||
|
* @throws RuntimeException
|
||||||
*/
|
*/
|
||||||
protected function printTitle($text)
|
private function importConfig(): CustomizableAppConfig
|
||||||
{
|
{
|
||||||
$text = trim($text);
|
$config = new CustomizableAppConfig();
|
||||||
$length = strlen($text) + 4;
|
|
||||||
$header = str_repeat('*', $length);
|
|
||||||
|
|
||||||
$this->output->writeln([
|
// Ask the user if he/she wants to import an older configuration
|
||||||
'',
|
$importConfig = $this->io->confirm('Do you want to import configuration from previous installation?');
|
||||||
'<info>' . $header . '</info>',
|
if (! $importConfig) {
|
||||||
'<info>* ' . strtoupper($text) . ' *</info>',
|
return $config;
|
||||||
'<info>' . $header . '</info>',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $text
|
|
||||||
* @param string|null $default
|
|
||||||
* @param bool $allowEmpty
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function ask($text, $default = null, $allowEmpty = false)
|
|
||||||
{
|
|
||||||
if (isset($default)) {
|
|
||||||
$text .= ' (defaults to ' . $default . ')';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ask the user for the older shlink path
|
||||||
|
$keepAsking = true;
|
||||||
do {
|
do {
|
||||||
$value = $this->questionHelper->ask($this->input, $this->output, new Question(
|
$config->setImportedInstallationPath($this->io->ask(
|
||||||
'<question>' . $text . ':</question> ',
|
'Previous shlink installation path from which to import config'
|
||||||
$default
|
|
||||||
));
|
));
|
||||||
if (empty($value) && ! $allowEmpty) {
|
$configFile = $config->getImportedInstallationPath() . '/' . self::GENERATED_CONFIG_PATH;
|
||||||
$this->output->writeln('<error>Value can\'t be empty</error>');
|
$configExists = $this->filesystem->exists($configFile);
|
||||||
|
|
||||||
|
if (! $configExists) {
|
||||||
|
$keepAsking = $this->io->confirm(
|
||||||
|
'Provided path does not seem to be a valid shlink root path. Do you want to try another path?'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} while (empty($value) && empty($default) && ! $allowEmpty);
|
} while (! $configExists && $keepAsking);
|
||||||
|
|
||||||
return $value;
|
// If after some retries the user has chosen not to test another path, return
|
||||||
}
|
if (! $configExists) {
|
||||||
|
return $config;
|
||||||
/**
|
|
||||||
* @param array $params
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function buildAppConfig(array $params)
|
|
||||||
{
|
|
||||||
// Build simple config
|
|
||||||
$config = [
|
|
||||||
'app_options' => [
|
|
||||||
'secret_key' => $params['APP']['SECRET'],
|
|
||||||
],
|
|
||||||
'entity_manager' => [
|
|
||||||
'connection' => [
|
|
||||||
'driver' => $params['DATABASE']['DRIVER'],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'translator' => [
|
|
||||||
'locale' => $params['LANGUAGE']['DEFAULT'],
|
|
||||||
],
|
|
||||||
'cli' => [
|
|
||||||
'locale' => $params['LANGUAGE']['CLI'],
|
|
||||||
],
|
|
||||||
'url_shortener' => [
|
|
||||||
'domain' => [
|
|
||||||
'schema' => $params['URL_SHORTENER']['SCHEMA'],
|
|
||||||
'hostname' => $params['URL_SHORTENER']['HOSTNAME'],
|
|
||||||
],
|
|
||||||
'shortcode_chars' => $params['URL_SHORTENER']['CHARS'],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
// Build dynamic database config
|
|
||||||
if ($params['DATABASE']['DRIVER'] === 'pdo_sqlite') {
|
|
||||||
$config['entity_manager']['connection']['path'] = 'data/database.sqlite';
|
|
||||||
} else {
|
|
||||||
$config['entity_manager']['connection']['user'] = $params['DATABASE']['USER'];
|
|
||||||
$config['entity_manager']['connection']['password'] = $params['DATABASE']['PASSWORD'];
|
|
||||||
$config['entity_manager']['connection']['dbname'] = $params['DATABASE']['NAME'];
|
|
||||||
$config['entity_manager']['connection']['host'] = $params['DATABASE']['HOST'];
|
|
||||||
$config['entity_manager']['connection']['port'] = $params['DATABASE']['PORT'];
|
|
||||||
|
|
||||||
if ($params['DATABASE']['DRIVER'] === 'pdo_mysql') {
|
|
||||||
$config['entity_manager']['connection']['driverOptions'] = [
|
|
||||||
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the config file
|
||||||
|
$config->exchangeArray(include $configFile);
|
||||||
return $config;
|
return $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createDatabase()
|
|
||||||
{
|
|
||||||
$this->output->writeln('Initializing database...');
|
|
||||||
return $this->runCommand('php vendor/bin/doctrine.php orm:schema-tool:create', 'Error generating database.');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $command
|
* @param string $command
|
||||||
* @param string $errorMessage
|
* @param string $errorMessage
|
||||||
|
* @param OutputInterface $output
|
||||||
* @return bool
|
* @return bool
|
||||||
|
* @throws LogicException
|
||||||
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
protected function runCommand($command, $errorMessage)
|
private function runCommand($command, $errorMessage, OutputInterface $output): bool
|
||||||
{
|
{
|
||||||
$process = $this->processHelper->run($this->output, $command);
|
if ($this->processHelper === null) {
|
||||||
|
$this->processHelper = $this->getHelper('process');
|
||||||
|
}
|
||||||
|
|
||||||
|
$process = $this->processHelper->run($output, $command);
|
||||||
if ($process->isSuccessful()) {
|
if ($process->isSuccessful()) {
|
||||||
$this->output->writeln(' <info>Success!</info>');
|
$this->io->writeln(' <info>Success!</info>');
|
||||||
return true;
|
return true;
|
||||||
} else {
|
}
|
||||||
if ($this->output->isVerbose()) {
|
|
||||||
return false;
|
if ($this->io->isVerbose()) {
|
||||||
}
|
|
||||||
$this->output->writeln(
|
|
||||||
' <error>' . $errorMessage . '</error> Run this command with -vvv to see specific error info.'
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->io->error($errorMessage . ' Run this command with -vvv to see specific error info.');
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Install;
|
|
||||||
|
|
||||||
use Zend\Config\Writer\WriterInterface;
|
|
||||||
|
|
||||||
class UpdateCommand extends InstallCommand
|
|
||||||
{
|
|
||||||
public function createDatabase()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
||||||
|
|
||||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
|
||||||
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
|
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
|
||||||
use Shlinkio\Shlink\Common\Service\PreviewGenerator;
|
|
||||||
use Shlinkio\Shlink\Common\Service\PreviewGeneratorInterface;
|
use Shlinkio\Shlink\Common\Service\PreviewGeneratorInterface;
|
||||||
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
|
||||||
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\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
|
||||||
*/
|
*/
|
||||||
@@ -27,14 +29,6 @@ class GeneratePreviewCommand extends Command
|
|||||||
*/
|
*/
|
||||||
private $shortUrlService;
|
private $shortUrlService;
|
||||||
|
|
||||||
/**
|
|
||||||
* GeneratePreviewCommand constructor.
|
|
||||||
* @param ShortUrlServiceInterface $shortUrlService
|
|
||||||
* @param PreviewGeneratorInterface $previewGenerator
|
|
||||||
* @param TranslatorInterface $translator
|
|
||||||
*
|
|
||||||
* @Inject({ShortUrlService::class, PreviewGenerator::class, "translator"})
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
ShortUrlServiceInterface $shortUrlService,
|
ShortUrlServiceInterface $shortUrlService,
|
||||||
PreviewGeneratorInterface $previewGenerator,
|
PreviewGeneratorInterface $previewGenerator,
|
||||||
@@ -48,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.'
|
||||||
@@ -68,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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
||||||
|
|
||||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
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
|
||||||
*/
|
*/
|
||||||
@@ -30,14 +32,6 @@ class GenerateShortcodeCommand extends Command
|
|||||||
*/
|
*/
|
||||||
private $translator;
|
private $translator;
|
||||||
|
|
||||||
/**
|
|
||||||
* GenerateShortcodeCommand constructor.
|
|
||||||
* @param UrlShortenerInterface $urlShortener
|
|
||||||
* @param TranslatorInterface $translator
|
|
||||||
* @param array $domainConfig
|
|
||||||
*
|
|
||||||
* @Inject({UrlShortener::class, "translator", "config.url_shortener.domain"})
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
UrlShortenerInterface $urlShortener,
|
UrlShortenerInterface $urlShortener,
|
||||||
TranslatorInterface $translator,
|
TranslatorInterface $translator,
|
||||||
@@ -51,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')
|
||||||
)
|
)
|
||||||
@@ -59,26 +53,36 @@ class GenerateShortcodeCommand extends Command
|
|||||||
->addOption(
|
->addOption(
|
||||||
'tags',
|
'tags',
|
||||||
't',
|
't',
|
||||||
InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
|
InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED,
|
||||||
$this->translator->translate('Tags to apply to the new short URL')
|
$this->translator->translate('Tags to apply to the new short URL')
|
||||||
);
|
)
|
||||||
|
->addOption('validSince', 's', InputOption::VALUE_REQUIRED, $this->translator->translate(
|
||||||
|
'The date from which this short URL will be valid. '
|
||||||
|
. 'If someone tries to access it before this date, it will not be found.'
|
||||||
|
))
|
||||||
|
->addOption('validUntil', 'u', InputOption::VALUE_REQUIRED, $this->translator->translate(
|
||||||
|
'The date until which this short URL will be valid. '
|
||||||
|
. 'If someone tries to access it after this date, it will not be found.'
|
||||||
|
))
|
||||||
|
->addOption('customSlug', 'c', InputOption::VALUE_REQUIRED, $this->translator->translate(
|
||||||
|
'If provided, this slug will be used instead of generating a short code'
|
||||||
|
))
|
||||||
|
->addOption('maxVisits', 'm', InputOption::VALUE_REQUIRED, $this->translator->translate(
|
||||||
|
'This will limit the number of visits for this short URL.'
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -86,37 +90,58 @@ 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');
|
||||||
|
$maxVisits = $input->getOption('maxVisits');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (! isset($longUrl)) {
|
$shortCode = $this->urlShortener->urlToShortCode(
|
||||||
$output->writeln(sprintf('<error>%s</error>', $this->translator->translate('A URL was not provided!')));
|
new Uri($longUrl),
|
||||||
return;
|
$tags,
|
||||||
}
|
$this->getOptionalDate($input, 'validSince'),
|
||||||
|
$this->getOptionalDate($input, 'validUntil'),
|
||||||
$shortCode = $this->urlShortener->urlToShortCode(new Uri($longUrl), $tags);
|
$customSlug,
|
||||||
|
$maxVisits !== null ? (int) $maxVisits : null
|
||||||
|
);
|
||||||
$shortUrl = (new Uri())->withPath($shortCode)
|
$shortUrl = (new Uri())->withPath($shortCode)
|
||||||
->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) {
|
||||||
|
$io->error(\sprintf(
|
||||||
|
$this->translator->translate(
|
||||||
|
'Provided slug "%s" is already in use by another URL. Try with a different one.'
|
||||||
|
),
|
||||||
|
$customSlug
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getOptionalDate(InputInterface $input, string $fieldName)
|
||||||
|
{
|
||||||
|
$since = $input->getOption($fieldName);
|
||||||
|
return $since !== null ? new \DateTime($since) : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
||||||
|
|
||||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
|
||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
|
||||||
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
|
||||||
*/
|
*/
|
||||||
@@ -26,23 +26,16 @@ class GetVisitsCommand extends Command
|
|||||||
*/
|
*/
|
||||||
private $translator;
|
private $translator;
|
||||||
|
|
||||||
/**
|
|
||||||
* GetVisitsCommand constructor.
|
|
||||||
* @param VisitsTrackerInterface $visitsTracker
|
|
||||||
* @param TranslatorInterface $translator
|
|
||||||
*
|
|
||||||
* @Inject({VisitsTracker::class, "translator"})
|
|
||||||
*/
|
|
||||||
public function __construct(VisitsTrackerInterface $visitsTracker, TranslatorInterface $translator)
|
public function __construct(VisitsTrackerInterface $visitsTracker, TranslatorInterface $translator)
|
||||||
{
|
{
|
||||||
$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')
|
||||||
)
|
)
|
||||||
@@ -72,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);
|
||||||
}
|
}
|
||||||
@@ -87,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
||||||
|
|
||||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
|
||||||
use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter;
|
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\ShortUrlService;
|
|
||||||
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
|
||||||
*/
|
*/
|
||||||
@@ -28,23 +28,16 @@ class ListShortcodesCommand extends Command
|
|||||||
*/
|
*/
|
||||||
private $translator;
|
private $translator;
|
||||||
|
|
||||||
/**
|
|
||||||
* ListShortcodesCommand constructor.
|
|
||||||
* @param ShortUrlServiceInterface $shortUrlService
|
|
||||||
* @param TranslatorInterface $translator
|
|
||||||
*
|
|
||||||
* @Inject({ShortUrlService::class, "translator"})
|
|
||||||
*/
|
|
||||||
public function __construct(ShortUrlServiceInterface $shortUrlService, TranslatorInterface $translator)
|
public function __construct(ShortUrlServiceInterface $shortUrlService, TranslatorInterface $translator)
|
||||||
{
|
{
|
||||||
$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',
|
||||||
@@ -88,20 +81,16 @@ class ListShortcodesCommand extends Command
|
|||||||
|
|
||||||
public function execute(InputInterface $input, OutputInterface $output)
|
public function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
$page = intval($input->getOption('page'));
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$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');
|
||||||
$orderBy = $input->getOption('orderBy');
|
|
||||||
|
|
||||||
/** @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'),
|
||||||
@@ -112,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) {
|
||||||
@@ -126,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)) {
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
||||||
|
|
||||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
|
||||||
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
|
||||||
*/
|
*/
|
||||||
@@ -24,13 +26,6 @@ class ResolveUrlCommand extends Command
|
|||||||
*/
|
*/
|
||||||
private $translator;
|
private $translator;
|
||||||
|
|
||||||
/**
|
|
||||||
* ResolveUrlCommand constructor.
|
|
||||||
* @param UrlShortenerInterface $urlShortener
|
|
||||||
* @param TranslatorInterface $translator
|
|
||||||
*
|
|
||||||
* @Inject({UrlShortener::class, "translator"})
|
|
||||||
*/
|
|
||||||
public function __construct(UrlShortenerInterface $urlShortener, TranslatorInterface $translator)
|
public function __construct(UrlShortenerInterface $urlShortener, TranslatorInterface $translator)
|
||||||
{
|
{
|
||||||
$this->urlShortener = $urlShortener;
|
$this->urlShortener = $urlShortener;
|
||||||
@@ -40,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',
|
||||||
@@ -56,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);
|
||||||
}
|
}
|
||||||
@@ -71,23 +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) {
|
||||||
|
$io->error(
|
||||||
|
\sprintf($this->translator->translate('Provided short code "%s" could not be found.'), $shortCode)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
60
module/CLI/src/Command/Tag/CreateTagCommand.php
Normal file
60
module/CLI/src/Command/Tag/CreateTagCommand.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Command\Tag;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
|
class CreateTagCommand extends Command
|
||||||
|
{
|
||||||
|
const NAME = 'tag:create';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var TagServiceInterface
|
||||||
|
*/
|
||||||
|
private $tagService;
|
||||||
|
/**
|
||||||
|
* @var TranslatorInterface
|
||||||
|
*/
|
||||||
|
private $translator;
|
||||||
|
|
||||||
|
public function __construct(TagServiceInterface $tagService, TranslatorInterface $translator)
|
||||||
|
{
|
||||||
|
$this->tagService = $tagService;
|
||||||
|
$this->translator = $translator;
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName(self::NAME)
|
||||||
|
->setDescription($this->translator->translate('Creates one or more tags.'))
|
||||||
|
->addOption(
|
||||||
|
'name',
|
||||||
|
't',
|
||||||
|
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||||
|
$this->translator->translate('The name of the tags to create')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$tagNames = $input->getOption('name');
|
||||||
|
|
||||||
|
if (empty($tagNames)) {
|
||||||
|
$io->warning($this->translator->translate('You have to provide at least one tag name'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->tagService->createTags($tagNames);
|
||||||
|
$io->success($this->translator->translate('Tags properly created'));
|
||||||
|
}
|
||||||
|
}
|
||||||
60
module/CLI/src/Command/Tag/DeleteTagsCommand.php
Normal file
60
module/CLI/src/Command/Tag/DeleteTagsCommand.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Command\Tag;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
|
class DeleteTagsCommand extends Command
|
||||||
|
{
|
||||||
|
const NAME = 'tag:delete';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var TagServiceInterface
|
||||||
|
*/
|
||||||
|
private $tagService;
|
||||||
|
/**
|
||||||
|
* @var TranslatorInterface
|
||||||
|
*/
|
||||||
|
private $translator;
|
||||||
|
|
||||||
|
public function __construct(TagServiceInterface $tagService, TranslatorInterface $translator)
|
||||||
|
{
|
||||||
|
$this->tagService = $tagService;
|
||||||
|
$this->translator = $translator;
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName(self::NAME)
|
||||||
|
->setDescription($this->translator->translate('Deletes one or more tags.'))
|
||||||
|
->addOption(
|
||||||
|
'name',
|
||||||
|
't',
|
||||||
|
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||||
|
$this->translator->translate('The name of the tags to delete')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$tagNames = $input->getOption('name');
|
||||||
|
|
||||||
|
if (empty($tagNames)) {
|
||||||
|
$io->warning($this->translator->translate('You have to provide at least one tag name'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->tagService->deleteTags($tagNames);
|
||||||
|
$io->success($this->translator->translate('Tags properly deleted'));
|
||||||
|
}
|
||||||
|
}
|
||||||
58
module/CLI/src/Command/Tag/ListTagsCommand.php
Normal file
58
module/CLI/src/Command/Tag/ListTagsCommand.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Command\Tag;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||||
|
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
|
class ListTagsCommand extends Command
|
||||||
|
{
|
||||||
|
const NAME = 'tag:list';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var TagServiceInterface
|
||||||
|
*/
|
||||||
|
private $tagService;
|
||||||
|
/**
|
||||||
|
* @var TranslatorInterface
|
||||||
|
*/
|
||||||
|
private $translator;
|
||||||
|
|
||||||
|
public function __construct(TagServiceInterface $tagService, TranslatorInterface $translator)
|
||||||
|
{
|
||||||
|
$this->tagService = $tagService;
|
||||||
|
$this->translator = $translator;
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName(self::NAME)
|
||||||
|
->setDescription($this->translator->translate('Lists existing tags.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$io->table([$this->translator->translate('Name')], $this->getTagsRows());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTagsRows()
|
||||||
|
{
|
||||||
|
$tags = $this->tagService->listTags();
|
||||||
|
if (empty($tags)) {
|
||||||
|
return [[$this->translator->translate('No tags yet')]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return \array_map(function (Tag $tag) {
|
||||||
|
return [$tag->getName()];
|
||||||
|
}, $tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
57
module/CLI/src/Command/Tag/RenameTagCommand.php
Normal file
57
module/CLI/src/Command/Tag/RenameTagCommand.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Command\Tag;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||||
|
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
|
class RenameTagCommand extends Command
|
||||||
|
{
|
||||||
|
const NAME = 'tag:rename';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var TagServiceInterface
|
||||||
|
*/
|
||||||
|
private $tagService;
|
||||||
|
/**
|
||||||
|
* @var TranslatorInterface
|
||||||
|
*/
|
||||||
|
private $translator;
|
||||||
|
|
||||||
|
public function __construct(TagServiceInterface $tagService, TranslatorInterface $translator)
|
||||||
|
{
|
||||||
|
$this->tagService = $tagService;
|
||||||
|
$this->translator = $translator;
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName(self::NAME)
|
||||||
|
->setDescription($this->translator->translate('Renames one existing 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.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$oldName = $input->getArgument('oldName');
|
||||||
|
$newName = $input->getArgument('newName');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->tagService->renameTag($oldName, $newName);
|
||||||
|
$io->success($this->translator->translate('Tag properly renamed.'));
|
||||||
|
} catch (EntityDoesNotExistException $e) {
|
||||||
|
$io->error(\sprintf($this->translator->translate('A tag with name "%s" was not found'), $oldName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Visit;
|
namespace Shlinkio\Shlink\CLI\Command\Visit;
|
||||||
|
|
||||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
|
||||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||||
use Shlinkio\Shlink\Common\Service\IpLocationResolver;
|
|
||||||
use Shlinkio\Shlink\Common\Service\IpLocationResolverInterface;
|
use Shlinkio\Shlink\Common\Service\IpLocationResolverInterface;
|
||||||
use Shlinkio\Shlink\Core\Entity\VisitLocation;
|
use Shlinkio\Shlink\Core\Entity\VisitLocation;
|
||||||
use Shlinkio\Shlink\Core\Service\VisitService;
|
|
||||||
use Shlinkio\Shlink\Core\Service\VisitServiceInterface;
|
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
|
||||||
@@ -30,14 +31,6 @@ class ProcessVisitsCommand extends Command
|
|||||||
*/
|
*/
|
||||||
private $translator;
|
private $translator;
|
||||||
|
|
||||||
/**
|
|
||||||
* ProcessVisitsCommand constructor.
|
|
||||||
* @param VisitServiceInterface $visitService
|
|
||||||
* @param IpLocationResolverInterface $ipLocationResolver
|
|
||||||
* @param TranslatorInterface $translator
|
|
||||||
*
|
|
||||||
* @Inject({VisitService::class, IpLocationResolver::class, "translator"})
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
VisitServiceInterface $visitService,
|
VisitServiceInterface $visitService,
|
||||||
IpLocationResolverInterface $ipLocationResolver,
|
IpLocationResolverInterface $ipLocationResolver,
|
||||||
@@ -51,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')
|
||||||
);
|
);
|
||||||
@@ -59,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;
|
||||||
@@ -73,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()
|
||||||
));
|
));
|
||||||
@@ -86,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'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI;
|
namespace Shlinkio\Shlink\CLI;
|
||||||
|
|
||||||
use Zend\Config\Factory;
|
use Zend\Config\Factory;
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Factory;
|
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;
|
||||||
@@ -18,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;
|
||||||
}
|
}
|
||||||
|
|||||||
57
module/CLI/src/Factory/InstallApplicationFactory.php
Normal file
57
module/CLI/src/Factory/InstallApplicationFactory.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Factory;
|
||||||
|
|
||||||
|
use Interop\Container\ContainerInterface;
|
||||||
|
use Interop\Container\Exception\ContainerException;
|
||||||
|
use Shlinkio\Shlink\CLI\Command\Install\InstallCommand;
|
||||||
|
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerManager;
|
||||||
|
use Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
|
use Symfony\Component\Console\Application;
|
||||||
|
use Symfony\Component\Console\Exception\LogicException;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use Zend\Config\Writer\PhpArray;
|
||||||
|
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
|
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||||
|
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||||
|
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||||
|
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||||
|
|
||||||
|
class InstallApplicationFactory implements FactoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create an object
|
||||||
|
*
|
||||||
|
* @param ContainerInterface $container
|
||||||
|
* @param string $requestedName
|
||||||
|
* @param null|array $options
|
||||||
|
* @return object
|
||||||
|
* @throws LogicException
|
||||||
|
* @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)
|
||||||
|
{
|
||||||
|
$isUpdate = $options !== null && isset($options['isUpdate']) ? (bool) $options['isUpdate'] : false;
|
||||||
|
|
||||||
|
$app = new Application();
|
||||||
|
$command = new InstallCommand(
|
||||||
|
new PhpArray(),
|
||||||
|
$container->get(Filesystem::class),
|
||||||
|
new ConfigCustomizerManager($container, ['factories' => [
|
||||||
|
Plugin\DatabaseConfigCustomizer::class => ConfigAbstractFactory::class,
|
||||||
|
Plugin\UrlShortenerConfigCustomizer::class => InvokableFactory::class,
|
||||||
|
Plugin\LanguageConfigCustomizer::class => InvokableFactory::class,
|
||||||
|
Plugin\ApplicationConfigCustomizer::class => InvokableFactory::class,
|
||||||
|
]]),
|
||||||
|
$isUpdate
|
||||||
|
);
|
||||||
|
$app->add($command);
|
||||||
|
$app->setDefaultCommand($command->getName());
|
||||||
|
|
||||||
|
return $app;
|
||||||
|
}
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
10
module/CLI/src/Install/ConfigCustomizerManagerInterface.php
Normal file
10
module/CLI/src/Install/ConfigCustomizerManagerInterface.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install;
|
||||||
|
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
|
interface ConfigCustomizerManagerInterface extends ContainerInterface
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
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);
|
||||||
|
}
|
||||||
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';
|
||||||
|
}
|
||||||
|
}
|
||||||
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
270
module/CLI/src/Model/CustomizableAppConfig.php
Normal file
270
module/CLI/src/Model/CustomizableAppConfig.php
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Model;
|
||||||
|
|
||||||
|
use Zend\Stdlib\ArraySerializableInterface;
|
||||||
|
|
||||||
|
final class CustomizableAppConfig implements ArraySerializableInterface
|
||||||
|
{
|
||||||
|
const SQLITE_DB_PATH = 'data/database.sqlite';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $database;
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $urlShortener;
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $language;
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $app;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $importedInstallationPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getDatabase()
|
||||||
|
{
|
||||||
|
return $this->database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $database
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setDatabase(array $database)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasDatabase()
|
||||||
|
{
|
||||||
|
return ! empty($this->database);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getUrlShortener()
|
||||||
|
{
|
||||||
|
return $this->urlShortener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $urlShortener
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setUrlShortener(array $urlShortener)
|
||||||
|
{
|
||||||
|
$this->urlShortener = $urlShortener;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasUrlShortener()
|
||||||
|
{
|
||||||
|
return ! empty($this->urlShortener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getLanguage()
|
||||||
|
{
|
||||||
|
return $this->language;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $language
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setLanguage(array $language)
|
||||||
|
{
|
||||||
|
$this->language = $language;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasLanguage()
|
||||||
|
{
|
||||||
|
return ! empty($this->language);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getApp()
|
||||||
|
{
|
||||||
|
return $this->app;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $app
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setApp(array $app)
|
||||||
|
{
|
||||||
|
$this->app = $app;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasApp()
|
||||||
|
{
|
||||||
|
return ! empty($this->app);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getImportedInstallationPath()
|
||||||
|
{
|
||||||
|
return $this->importedInstallationPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $importedInstallationPath
|
||||||
|
* @return $this|self
|
||||||
|
*/
|
||||||
|
public function setImportedInstallationPath($importedInstallationPath)
|
||||||
|
{
|
||||||
|
$this->importedInstallationPath = $importedInstallationPath;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasImportedInstallationPath()
|
||||||
|
{
|
||||||
|
return $this->importedInstallationPath !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchange internal values from provided array
|
||||||
|
*
|
||||||
|
* @param array $array
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function exchangeArray(array $array)
|
||||||
|
{
|
||||||
|
if (isset($array['app_options'], $array['app_options']['secret_key'])) {
|
||||||
|
$this->setApp([
|
||||||
|
'SECRET' => $array['app_options']['secret_key'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($array['entity_manager'], $array['entity_manager']['connection'])) {
|
||||||
|
$this->deserializeDatabase($array['entity_manager']['connection']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($array['translator'], $array['translator']['locale'], $array['cli'], $array['cli']['locale'])) {
|
||||||
|
$this->setLanguage([
|
||||||
|
'DEFAULT' => $array['translator']['locale'],
|
||||||
|
'CLI' => $array['cli']['locale'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($array['url_shortener'])) {
|
||||||
|
$urlShortener = $array['url_shortener'];
|
||||||
|
$this->setUrlShortener([
|
||||||
|
'SCHEMA' => $urlShortener['domain']['schema'],
|
||||||
|
'HOSTNAME' => $urlShortener['domain']['hostname'],
|
||||||
|
'CHARS' => $urlShortener['shortcode_chars'],
|
||||||
|
'VALIDATE_URL' => $urlShortener['validate_url'] ?? true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deserializeDatabase(array $conn)
|
||||||
|
{
|
||||||
|
if (! isset($conn['driver'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$driver = $conn['driver'];
|
||||||
|
|
||||||
|
$params = ['DRIVER' => $driver];
|
||||||
|
if ($driver !== 'pdo_sqlite') {
|
||||||
|
$params['USER'] = $conn['user'];
|
||||||
|
$params['PASSWORD'] = $conn['password'];
|
||||||
|
$params['NAME'] = $conn['dbname'];
|
||||||
|
$params['HOST'] = $conn['host'];
|
||||||
|
$params['PORT'] = $conn['port'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setDatabase($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an array representation of the object
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getArrayCopy()
|
||||||
|
{
|
||||||
|
$config = [
|
||||||
|
'app_options' => [
|
||||||
|
'secret_key' => $this->app['SECRET'],
|
||||||
|
'disable_track_param' => $this->app['DISABLE_TRACK_PARAM'] ?? null,
|
||||||
|
],
|
||||||
|
'entity_manager' => [
|
||||||
|
'connection' => [
|
||||||
|
'driver' => $this->database['DRIVER'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'translator' => [
|
||||||
|
'locale' => $this->language['DEFAULT'],
|
||||||
|
],
|
||||||
|
'cli' => [
|
||||||
|
'locale' => $this->language['CLI'],
|
||||||
|
],
|
||||||
|
'url_shortener' => [
|
||||||
|
'domain' => [
|
||||||
|
'schema' => $this->urlShortener['SCHEMA'],
|
||||||
|
'hostname' => $this->urlShortener['HOSTNAME'],
|
||||||
|
],
|
||||||
|
'shortcode_chars' => $this->urlShortener['CHARS'],
|
||||||
|
'validate_url' => $this->urlShortener['VALIDATE_URL'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Build dynamic database config based on selected driver
|
||||||
|
if ($this->database['DRIVER'] === 'pdo_sqlite') {
|
||||||
|
$config['entity_manager']['connection']['path'] = self::SQLITE_DB_PATH;
|
||||||
|
} else {
|
||||||
|
$config['entity_manager']['connection']['user'] = $this->database['USER'];
|
||||||
|
$config['entity_manager']['connection']['password'] = $this->database['PASSWORD'];
|
||||||
|
$config['entity_manager']['connection']['dbname'] = $this->database['NAME'];
|
||||||
|
$config['entity_manager']['connection']['host'] = $this->database['HOST'];
|
||||||
|
$config['entity_manager']['connection']['port'] = $this->database['PORT'];
|
||||||
|
|
||||||
|
if ($this->database['DRIVER'] === 'pdo_mysql') {
|
||||||
|
$config['entity_manager']['connection']['driverOptions'] = [
|
||||||
|
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<?php
|
||||||
|
return [];
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Command\Api;
|
namespace ShlinkioTest\Shlink\CLI\Command\Api;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
@@ -57,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Command\Api;
|
namespace ShlinkioTest\Shlink\CLI\Command\Api;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Command\Api;
|
namespace ShlinkioTest\Shlink\CLI\Command\Api;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Command\Config;
|
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;
|
||||||
@@ -30,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',
|
||||||
@@ -38,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)
|
||||||
|
|||||||
@@ -1,18 +1,29 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Command\Install;
|
namespace ShlinkioTest\Shlink\CLI\Command\Install;
|
||||||
|
|
||||||
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\Command\Install\InstallCommand;
|
use Shlinkio\Shlink\CLI\Command\Install\InstallCommand;
|
||||||
|
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerManagerInterface;
|
||||||
|
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\Filesystem;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
use Zend\Config\Writer\WriterInterface;
|
use Zend\Config\Writer\WriterInterface;
|
||||||
|
|
||||||
class InstallCommandTest extends TestCase
|
class InstallCommandTest extends TestCase
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var InstallCommand
|
||||||
|
*/
|
||||||
|
protected $command;
|
||||||
/**
|
/**
|
||||||
* @var CommandTester
|
* @var CommandTester
|
||||||
*/
|
*/
|
||||||
@@ -21,6 +32,10 @@ class InstallCommandTest extends TestCase
|
|||||||
* @var ObjectProphecy
|
* @var ObjectProphecy
|
||||||
*/
|
*/
|
||||||
protected $configWriter;
|
protected $configWriter;
|
||||||
|
/**
|
||||||
|
* @var ObjectProphecy
|
||||||
|
*/
|
||||||
|
protected $filesystem;
|
||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
@@ -31,81 +46,96 @@ class InstallCommandTest extends TestCase
|
|||||||
$processHelper->setHelperSet(Argument::any())->willReturn(null);
|
$processHelper->setHelperSet(Argument::any())->willReturn(null);
|
||||||
$processHelper->run(Argument::cetera())->willReturn($processMock->reveal());
|
$processHelper->run(Argument::cetera())->willReturn($processMock->reveal());
|
||||||
|
|
||||||
|
$this->filesystem = $this->prophesize(Filesystem::class);
|
||||||
|
$this->filesystem->exists(Argument::cetera())->willReturn(false);
|
||||||
|
|
||||||
|
$this->configWriter = $this->prophesize(WriterInterface::class);
|
||||||
|
|
||||||
|
$configCustomizer = $this->prophesize(ConfigCustomizerInterface::class);
|
||||||
|
$configCustomizers = $this->prophesize(ConfigCustomizerManagerInterface::class);
|
||||||
|
$configCustomizers->get(Argument::cetera())->willReturn($configCustomizer->reveal());
|
||||||
|
|
||||||
$app = new Application();
|
$app = new Application();
|
||||||
$helperSet = $app->getHelperSet();
|
$helperSet = $app->getHelperSet();
|
||||||
$helperSet->set($processHelper->reveal());
|
$helperSet->set($processHelper->reveal());
|
||||||
$app->setHelperSet($helperSet);
|
$app->setHelperSet($helperSet);
|
||||||
|
$this->command = new InstallCommand(
|
||||||
$this->configWriter = $this->prophesize(WriterInterface::class);
|
$this->configWriter->reveal(),
|
||||||
$command = new InstallCommand($this->configWriter->reveal());
|
$this->filesystem->reveal(),
|
||||||
$app->add($command);
|
$configCustomizers->reveal()
|
||||||
|
|
||||||
$questionHelper = $command->getHelper('question');
|
|
||||||
$questionHelper->setInputStream($this->createInputStream());
|
|
||||||
$this->commandTester = new CommandTester($command);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function createInputStream()
|
|
||||||
{
|
|
||||||
$stream = fopen('php://memory', 'rb+', false);
|
|
||||||
fwrite($stream, <<<CLI_INPUT
|
|
||||||
|
|
||||||
shlink_db
|
|
||||||
alejandro
|
|
||||||
1234
|
|
||||||
|
|
||||||
|
|
||||||
0
|
|
||||||
doma.in
|
|
||||||
abc123BCA
|
|
||||||
|
|
||||||
1
|
|
||||||
my_secret
|
|
||||||
CLI_INPUT
|
|
||||||
);
|
);
|
||||||
rewind($stream);
|
$app->add($this->command);
|
||||||
|
|
||||||
return $stream;
|
$this->commandTester = new CommandTester($this->command);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
*/
|
*/
|
||||||
public function inputIsProperlyParsed()
|
public function generatedConfigIsProperlyPersisted()
|
||||||
{
|
{
|
||||||
$this->configWriter->toFile(Argument::any(), [
|
$this->configWriter->toFile(Argument::any(), Argument::type('array'), false)->shouldBeCalledTimes(1);
|
||||||
'app_options' => [
|
$this->commandTester->execute([]);
|
||||||
'secret_key' => 'my_secret',
|
}
|
||||||
],
|
|
||||||
'entity_manager' => [
|
/**
|
||||||
'connection' => [
|
* @test
|
||||||
'driver' => 'pdo_mysql',
|
*/
|
||||||
'dbname' => 'shlink_db',
|
public function cachedConfigIsDeletedIfExists()
|
||||||
'user' => 'alejandro',
|
{
|
||||||
'password' => '1234',
|
/** @var MethodProphecy $appConfigExists */
|
||||||
'host' => 'localhost',
|
$appConfigExists = $this->filesystem->exists('data/cache/app_config.php')->willReturn(true);
|
||||||
'port' => '3306',
|
/** @var MethodProphecy $appConfigRemove */
|
||||||
'driverOptions' => [
|
$appConfigRemove = $this->filesystem->remove('data/cache/app_config.php')->willReturn(null);
|
||||||
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
|
||||||
]
|
$this->commandTester->execute([]);
|
||||||
],
|
|
||||||
],
|
$appConfigExists->shouldHaveBeenCalledTimes(1);
|
||||||
'translator' => [
|
$appConfigRemove->shouldHaveBeenCalledTimes(1);
|
||||||
'locale' => 'en',
|
}
|
||||||
],
|
|
||||||
'cli' => [
|
/**
|
||||||
'locale' => 'es',
|
* @test
|
||||||
],
|
*/
|
||||||
'url_shortener' => [
|
public function exceptionWhileDeletingCachedConfigCancelsProcess()
|
||||||
'domain' => [
|
{
|
||||||
'schema' => 'http',
|
/** @var MethodProphecy $appConfigExists */
|
||||||
'hostname' => 'doma.in',
|
$appConfigExists = $this->filesystem->exists('data/cache/app_config.php')->willReturn(true);
|
||||||
],
|
/** @var MethodProphecy $appConfigRemove */
|
||||||
'shortcode_chars' => 'abc123BCA',
|
$appConfigRemove = $this->filesystem->remove('data/cache/app_config.php')->willThrow(IOException::class);
|
||||||
],
|
/** @var MethodProphecy $configToFile */
|
||||||
], false)->shouldBeCalledTimes(1);
|
$configToFile = $this->configWriter->toFile(Argument::cetera())->willReturn(true);
|
||||||
$this->commandTester->execute([
|
|
||||||
'command' => 'shlink:install',
|
$this->commandTester->execute([]);
|
||||||
|
|
||||||
|
$appConfigExists->shouldHaveBeenCalledTimes(1);
|
||||||
|
$appConfigRemove->shouldHaveBeenCalledTimes(1);
|
||||||
|
$configToFile->shouldNotHaveBeenCalled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function whenCommandIsUpdatePreviousConfigCanBeImported()
|
||||||
|
{
|
||||||
|
$ref = new \ReflectionObject($this->command);
|
||||||
|
$prop = $ref->getProperty('isUpdate');
|
||||||
|
$prop->setAccessible(true);
|
||||||
|
$prop->setValue($this->command, true);
|
||||||
|
|
||||||
|
/** @var MethodProphecy $importedConfigExists */
|
||||||
|
$importedConfigExists = $this->filesystem->exists(
|
||||||
|
__DIR__ . '/../../../test-resources/' . InstallCommand::GENERATED_CONFIG_PATH
|
||||||
|
)->willReturn(true);
|
||||||
|
|
||||||
|
$this->commandTester->setInputs([
|
||||||
|
'',
|
||||||
|
'/foo/bar/wrong_previous_shlink',
|
||||||
|
'',
|
||||||
|
__DIR__ . '/../../../test-resources',
|
||||||
]);
|
]);
|
||||||
|
$this->commandTester->execute([]);
|
||||||
|
|
||||||
|
$importedConfigExists->shouldHaveBeenCalled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Command\Shortcode;
|
namespace ShlinkioTest\Shlink\CLI\Command\Shortcode;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
@@ -63,7 +65,7 @@ class GeneratePreviewCommandTest extends TestCase
|
|||||||
$this->previewGenerator->generatePreview('http://baz.com/something')->shouldBeCalledTimes(1);
|
$this->previewGenerator->generatePreview('http://baz.com/something')->shouldBeCalledTimes(1);
|
||||||
|
|
||||||
$this->commandTester->execute([
|
$this->commandTester->execute([
|
||||||
'command' => 'shortcode:process-previews'
|
'command' => 'shortcode:process-previews',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +85,7 @@ class GeneratePreviewCommandTest extends TestCase
|
|||||||
->shouldBeCalledTimes(count($items));
|
->shouldBeCalledTimes(count($items));
|
||||||
|
|
||||||
$this->commandTester->execute([
|
$this->commandTester->execute([
|
||||||
'command' => 'shortcode:process-previews'
|
'command' => 'shortcode:process-previews',
|
||||||
]);
|
]);
|
||||||
$output = $this->commandTester->getDisplay();
|
$output = $this->commandTester->getDisplay();
|
||||||
$this->assertEquals(count($items), substr_count($output, 'Error'));
|
$this->assertEquals(count($items), substr_count($output, 'Error'));
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Command\Shortcode;
|
namespace ShlinkioTest\Shlink\CLI\Command\Shortcode;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
@@ -27,7 +29,7 @@ class GenerateShortcodeCommandTest extends TestCase
|
|||||||
$this->urlShortener = $this->prophesize(UrlShortener::class);
|
$this->urlShortener = $this->prophesize(UrlShortener::class);
|
||||||
$command = new GenerateShortcodeCommand($this->urlShortener->reveal(), Translator::factory([]), [
|
$command = new GenerateShortcodeCommand($this->urlShortener->reveal(), Translator::factory([]), [
|
||||||
'schema' => 'http',
|
'schema' => 'http',
|
||||||
'hostname' => 'foo.com'
|
'hostname' => 'foo.com',
|
||||||
]);
|
]);
|
||||||
$app = new Application();
|
$app = new Application();
|
||||||
$app->add($command);
|
$app->add($command);
|
||||||
@@ -44,7 +46,7 @@ class GenerateShortcodeCommandTest extends TestCase
|
|||||||
|
|
||||||
$this->commandTester->execute([
|
$this->commandTester->execute([
|
||||||
'command' => 'shortcode:generate',
|
'command' => 'shortcode:generate',
|
||||||
'longUrl' => 'http://domain.com/foo/bar'
|
'longUrl' => 'http://domain.com/foo/bar',
|
||||||
]);
|
]);
|
||||||
$output = $this->commandTester->getDisplay();
|
$output = $this->commandTester->getDisplay();
|
||||||
$this->assertTrue(strpos($output, 'http://foo.com/abc123') > 0);
|
$this->assertTrue(strpos($output, 'http://foo.com/abc123') > 0);
|
||||||
@@ -60,11 +62,12 @@ class GenerateShortcodeCommandTest extends TestCase
|
|||||||
|
|
||||||
$this->commandTester->execute([
|
$this->commandTester->execute([
|
||||||
'command' => 'shortcode:generate',
|
'command' => 'shortcode:generate',
|
||||||
'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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Command\Shortcode;
|
namespace ShlinkioTest\Shlink\CLI\Command\Shortcode;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Command\Shortcode;
|
namespace ShlinkioTest\Shlink\CLI\Command\Shortcode;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
@@ -8,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;
|
||||||
@@ -20,10 +21,6 @@ class ListShortcodesCommandTest extends TestCase
|
|||||||
* @var CommandTester
|
* @var CommandTester
|
||||||
*/
|
*/
|
||||||
protected $commandTester;
|
protected $commandTester;
|
||||||
/**
|
|
||||||
* @var QuestionHelper
|
|
||||||
*/
|
|
||||||
protected $questionHelper;
|
|
||||||
/**
|
/**
|
||||||
* @var ObjectProphecy
|
* @var ObjectProphecy
|
||||||
*/
|
*/
|
||||||
@@ -35,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,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']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,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']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,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']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,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,
|
||||||
@@ -117,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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Command\Shortcode;
|
namespace ShlinkioTest\Shlink\CLI\Command\Shortcode;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
use Shlinkio\Shlink\CLI\Command\Shortcode\ResolveUrlCommand;
|
use Shlinkio\Shlink\CLI\Command\Shortcode\ResolveUrlCommand;
|
||||||
|
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
@@ -55,7 +58,7 @@ class ResolveUrlCommandTest extends TestCase
|
|||||||
public function incorrectShortCodeOutputsErrorMessage()
|
public function incorrectShortCodeOutputsErrorMessage()
|
||||||
{
|
{
|
||||||
$shortCode = 'abc123';
|
$shortCode = 'abc123';
|
||||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn(null)
|
$this->urlShortener->shortCodeToUrl($shortCode)->willThrow(EntityDoesNotExistException::class)
|
||||||
->shouldBeCalledTimes(1);
|
->shouldBeCalledTimes(1);
|
||||||
|
|
||||||
$this->commandTester->execute([
|
$this->commandTester->execute([
|
||||||
@@ -63,7 +66,7 @@ class ResolveUrlCommandTest extends TestCase
|
|||||||
'shortCode' => $shortCode,
|
'shortCode' => $shortCode,
|
||||||
]);
|
]);
|
||||||
$output = $this->commandTester->getDisplay();
|
$output = $this->commandTester->getDisplay();
|
||||||
$this->assertEquals('No URL found for short code "' . $shortCode . '"' . PHP_EOL, $output);
|
$this->assertContains('Provided short code "' . $shortCode . '" could not be found.', $output);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
65
module/CLI/test/Command/Tag/CreateTagCommandTest.php
Normal file
65
module/CLI/test/Command/Tag/CreateTagCommandTest.php
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ShlinkioTest\Shlink\CLI\Command\Tag;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\Prophecy\MethodProphecy;
|
||||||
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
|
use Shlinkio\Shlink\CLI\Command\Tag\CreateTagCommand;
|
||||||
|
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
|
||||||
|
use Symfony\Component\Console\Application;
|
||||||
|
use Symfony\Component\Console\Tester\CommandTester;
|
||||||
|
use Zend\I18n\Translator\Translator;
|
||||||
|
|
||||||
|
class CreateTagCommandTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var CommandTester
|
||||||
|
*/
|
||||||
|
private $commandTester;
|
||||||
|
/**
|
||||||
|
* @var ObjectProphecy
|
||||||
|
*/
|
||||||
|
private $tagService;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->tagService = $this->prophesize(TagServiceInterface::class);
|
||||||
|
|
||||||
|
$command = new CreateTagCommand($this->tagService->reveal(), Translator::factory([]));
|
||||||
|
$app = new Application();
|
||||||
|
$app->add($command);
|
||||||
|
|
||||||
|
$this->commandTester = new CommandTester($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function errorIsReturnedWhenNoTagsAreProvided()
|
||||||
|
{
|
||||||
|
$this->commandTester->execute([]);
|
||||||
|
|
||||||
|
$output = $this->commandTester->getDisplay();
|
||||||
|
$this->assertContains('You have to provide at least one tag name', $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function serviceIsInvokedOnSuccess()
|
||||||
|
{
|
||||||
|
$tagNames = ['foo', 'bar'];
|
||||||
|
/** @var MethodProphecy $createTags */
|
||||||
|
$createTags = $this->tagService->createTags($tagNames)->willReturn([]);
|
||||||
|
|
||||||
|
$this->commandTester->execute([
|
||||||
|
'--name' => $tagNames,
|
||||||
|
]);
|
||||||
|
$output = $this->commandTester->getDisplay();
|
||||||
|
|
||||||
|
$this->assertContains('Tags properly created', $output);
|
||||||
|
$createTags->shouldHaveBeenCalled();
|
||||||
|
}
|
||||||
|
}
|
||||||
70
module/CLI/test/Command/Tag/DeleteTagsCommandTest.php
Normal file
70
module/CLI/test/Command/Tag/DeleteTagsCommandTest.php
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ShlinkioTest\Shlink\CLI\Command\Tag;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\Prophecy\MethodProphecy;
|
||||||
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
|
use Shlinkio\Shlink\CLI\Command\Tag\DeleteTagsCommand;
|
||||||
|
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
|
||||||
|
use Symfony\Component\Console\Application;
|
||||||
|
use Symfony\Component\Console\Tester\CommandTester;
|
||||||
|
use Zend\I18n\Translator\Translator;
|
||||||
|
|
||||||
|
class DeleteTagsCommandTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var DeleteTagsCommand
|
||||||
|
*/
|
||||||
|
private $command;
|
||||||
|
/**
|
||||||
|
* @var CommandTester
|
||||||
|
*/
|
||||||
|
private $commandTester;
|
||||||
|
/**
|
||||||
|
* @var ObjectProphecy
|
||||||
|
*/
|
||||||
|
private $tagService;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->tagService = $this->prophesize(TagServiceInterface::class);
|
||||||
|
|
||||||
|
$command = new DeleteTagsCommand($this->tagService->reveal(), Translator::factory([]));
|
||||||
|
$app = new Application();
|
||||||
|
$app->add($command);
|
||||||
|
|
||||||
|
$this->commandTester = new CommandTester($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function errorIsReturnedWhenNoTagsAreProvided()
|
||||||
|
{
|
||||||
|
$this->commandTester->execute([]);
|
||||||
|
|
||||||
|
$output = $this->commandTester->getDisplay();
|
||||||
|
$this->assertContains('You have to provide at least one tag name', $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function serviceIsInvokedOnSuccess()
|
||||||
|
{
|
||||||
|
$tagNames = ['foo', 'bar'];
|
||||||
|
/** @var MethodProphecy $deleteTags */
|
||||||
|
$deleteTags = $this->tagService->deleteTags($tagNames)->will(function () {
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->commandTester->execute([
|
||||||
|
'--name' => $tagNames,
|
||||||
|
]);
|
||||||
|
$output = $this->commandTester->getDisplay();
|
||||||
|
|
||||||
|
$this->assertContains('Tags properly deleted', $output);
|
||||||
|
$deleteTags->shouldHaveBeenCalled();
|
||||||
|
}
|
||||||
|
}
|
||||||
75
module/CLI/test/Command/Tag/ListTagsCommandTest.php
Normal file
75
module/CLI/test/Command/Tag/ListTagsCommandTest.php
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ShlinkioTest\Shlink\CLI\Command\Tag;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\Prophecy\MethodProphecy;
|
||||||
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
|
use Shlinkio\Shlink\CLI\Command\Tag\ListTagsCommand;
|
||||||
|
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||||
|
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
|
||||||
|
use Symfony\Component\Console\Application;
|
||||||
|
use Symfony\Component\Console\Tester\CommandTester;
|
||||||
|
use Zend\I18n\Translator\Translator;
|
||||||
|
|
||||||
|
class ListTagsCommandTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ListTagsCommand
|
||||||
|
*/
|
||||||
|
private $command;
|
||||||
|
/**
|
||||||
|
* @var CommandTester
|
||||||
|
*/
|
||||||
|
private $commandTester;
|
||||||
|
/**
|
||||||
|
* @var ObjectProphecy
|
||||||
|
*/
|
||||||
|
private $tagService;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->tagService = $this->prophesize(TagServiceInterface::class);
|
||||||
|
|
||||||
|
$command = new ListTagsCommand($this->tagService->reveal(), Translator::factory([]));
|
||||||
|
$app = new Application();
|
||||||
|
$app->add($command);
|
||||||
|
|
||||||
|
$this->commandTester = new CommandTester($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function noTagsPrintsEmptyMessage()
|
||||||
|
{
|
||||||
|
/** @var MethodProphecy $listTags */
|
||||||
|
$listTags = $this->tagService->listTags()->willReturn([]);
|
||||||
|
|
||||||
|
$this->commandTester->execute([]);
|
||||||
|
$output = $this->commandTester->getDisplay();
|
||||||
|
|
||||||
|
$this->assertContains('No tags yet', $output);
|
||||||
|
$listTags->shouldHaveBeenCalled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function listOfTagsIsPrinted()
|
||||||
|
{
|
||||||
|
/** @var MethodProphecy $listTags */
|
||||||
|
$listTags = $this->tagService->listTags()->willReturn([
|
||||||
|
(new Tag())->setName('foo'),
|
||||||
|
(new Tag())->setName('bar'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->commandTester->execute([]);
|
||||||
|
$output = $this->commandTester->getDisplay();
|
||||||
|
|
||||||
|
$this->assertContains('foo', $output);
|
||||||
|
$this->assertContains('bar', $output);
|
||||||
|
$listTags->shouldHaveBeenCalled();
|
||||||
|
}
|
||||||
|
}
|
||||||
82
module/CLI/test/Command/Tag/RenameTagCommandTest.php
Normal file
82
module/CLI/test/Command/Tag/RenameTagCommandTest.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ShlinkioTest\Shlink\CLI\Command\Tag;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\Prophecy\MethodProphecy;
|
||||||
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
|
use Shlinkio\Shlink\CLI\Command\Tag\RenameTagCommand;
|
||||||
|
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||||
|
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||||
|
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
|
||||||
|
use Symfony\Component\Console\Application;
|
||||||
|
use Symfony\Component\Console\Tester\CommandTester;
|
||||||
|
use Zend\I18n\Translator\Translator;
|
||||||
|
|
||||||
|
class RenameTagCommandTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var RenameTagCommand
|
||||||
|
*/
|
||||||
|
private $command;
|
||||||
|
/**
|
||||||
|
* @var CommandTester
|
||||||
|
*/
|
||||||
|
private $commandTester;
|
||||||
|
/**
|
||||||
|
* @var ObjectProphecy
|
||||||
|
*/
|
||||||
|
private $tagService;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->tagService = $this->prophesize(TagServiceInterface::class);
|
||||||
|
|
||||||
|
$command = new RenameTagCommand($this->tagService->reveal(), Translator::factory([]));
|
||||||
|
$app = new Application();
|
||||||
|
$app->add($command);
|
||||||
|
|
||||||
|
$this->commandTester = new CommandTester($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function errorIsPrintedIfExceptionIsThrown()
|
||||||
|
{
|
||||||
|
$oldName = 'foo';
|
||||||
|
$newName = 'bar';
|
||||||
|
/** @var MethodProphecy $renameTag */
|
||||||
|
$renameTag = $this->tagService->renameTag($oldName, $newName)->willThrow(EntityDoesNotExistException::class);
|
||||||
|
|
||||||
|
$this->commandTester->execute([
|
||||||
|
'oldName' => $oldName,
|
||||||
|
'newName' => $newName,
|
||||||
|
]);
|
||||||
|
$output = $this->commandTester->getDisplay();
|
||||||
|
|
||||||
|
$this->assertContains('A tag with name "foo" was not found', $output);
|
||||||
|
$renameTag->shouldHaveBeenCalled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function successIsPrintedIfNoErrorOccurs()
|
||||||
|
{
|
||||||
|
$oldName = 'foo';
|
||||||
|
$newName = 'bar';
|
||||||
|
/** @var MethodProphecy $renameTag */
|
||||||
|
$renameTag = $this->tagService->renameTag($oldName, $newName)->willReturn(new Tag());
|
||||||
|
|
||||||
|
$this->commandTester->execute([
|
||||||
|
'oldName' => $oldName,
|
||||||
|
'newName' => $newName,
|
||||||
|
]);
|
||||||
|
$output = $this->commandTester->getDisplay();
|
||||||
|
|
||||||
|
$this->assertContains('Tag properly renamed', $output);
|
||||||
|
$renameTag->shouldHaveBeenCalled();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Command\Visit;
|
namespace ShlinkioTest\Shlink\CLI\Command\Visit;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI;
|
namespace ShlinkioTest\Shlink\CLI;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\CLI\Factory;
|
namespace ShlinkioTest\Shlink\CLI\Factory;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|||||||
35
module/CLI/test/Factory/InstallApplicationFactoryTest.php
Normal file
35
module/CLI/test/Factory/InstallApplicationFactoryTest.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ShlinkioTest\Shlink\CLI\Factory;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Shlinkio\Shlink\CLI\Factory\InstallApplicationFactory;
|
||||||
|
use Symfony\Component\Console\Application;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use Zend\ServiceManager\ServiceManager;
|
||||||
|
|
||||||
|
class InstallApplicationFactoryTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var InstallApplicationFactory
|
||||||
|
*/
|
||||||
|
private $factory;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->factory = new InstallApplicationFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function serviceIsCreated()
|
||||||
|
{
|
||||||
|
$instance = $this->factory->__invoke(new ServiceManager(['services' => [
|
||||||
|
Filesystem::class => $this->prophesize(Filesystem::class)->reveal(),
|
||||||
|
]]), '');
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Application::class, $instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
146
module/CLI/test/Install/Plugin/DatabaseConfigCustomizerTest.php
Normal file
146
module/CLI/test/Install/Plugin/DatabaseConfigCustomizerTest.php
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
<?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\DatabaseConfigCustomizer;
|
||||||
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
|
class DatabaseConfigCustomizerTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var DatabaseConfigCustomizer
|
||||||
|
*/
|
||||||
|
private $plugin;
|
||||||
|
/**
|
||||||
|
* @var ObjectProphecy
|
||||||
|
*/
|
||||||
|
private $io;
|
||||||
|
/**
|
||||||
|
* @var ObjectProphecy
|
||||||
|
*/
|
||||||
|
private $filesystem;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->io = $this->prophesize(SymfonyStyle::class);
|
||||||
|
$this->io->title(Argument::any())->willReturn(null);
|
||||||
|
$this->filesystem = $this->prophesize(Filesystem::class);
|
||||||
|
|
||||||
|
$this->plugin = new DatabaseConfigCustomizer($this->filesystem->reveal());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function configIsRequestedToTheUser()
|
||||||
|
{
|
||||||
|
$choice = $this->io->choice(Argument::cetera())->willReturn('MySQL');
|
||||||
|
$ask = $this->io->ask(Argument::cetera())->willReturn('param');
|
||||||
|
$config = new CustomizableAppConfig();
|
||||||
|
|
||||||
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
|
$this->assertTrue($config->hasDatabase());
|
||||||
|
$this->assertEquals([
|
||||||
|
'DRIVER' => 'pdo_mysql',
|
||||||
|
'NAME' => 'param',
|
||||||
|
'USER' => 'param',
|
||||||
|
'PASSWORD' => 'param',
|
||||||
|
'HOST' => 'param',
|
||||||
|
'PORT' => 'param',
|
||||||
|
], $config->getDatabase());
|
||||||
|
$choice->shouldHaveBeenCalledTimes(1);
|
||||||
|
$ask->shouldHaveBeenCalledTimes(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function overwriteIsRequestedIfValueIsAlreadySet()
|
||||||
|
{
|
||||||
|
$choice = $this->io->choice(Argument::cetera())->willReturn('MySQL');
|
||||||
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(false);
|
||||||
|
$ask = $this->io->ask(Argument::cetera())->willReturn('MySQL');
|
||||||
|
$config = new CustomizableAppConfig();
|
||||||
|
$config->setDatabase([
|
||||||
|
'DRIVER' => 'pdo_pgsql',
|
||||||
|
'NAME' => 'MySQL',
|
||||||
|
'USER' => 'MySQL',
|
||||||
|
'PASSWORD' => 'MySQL',
|
||||||
|
'HOST' => 'MySQL',
|
||||||
|
'PORT' => 'MySQL',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'DRIVER' => 'pdo_mysql',
|
||||||
|
'NAME' => 'MySQL',
|
||||||
|
'USER' => 'MySQL',
|
||||||
|
'PASSWORD' => 'MySQL',
|
||||||
|
'HOST' => 'MySQL',
|
||||||
|
'PORT' => 'MySQL',
|
||||||
|
], $config->getDatabase());
|
||||||
|
$confirm->shouldHaveBeenCalledTimes(1);
|
||||||
|
$choice->shouldHaveBeenCalledTimes(1);
|
||||||
|
$ask->shouldHaveBeenCalledTimes(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function existingValueIsKeptIfRequested()
|
||||||
|
{
|
||||||
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||||
|
|
||||||
|
$config = new CustomizableAppConfig();
|
||||||
|
$config->setDatabase([
|
||||||
|
'DRIVER' => 'pdo_pgsql',
|
||||||
|
'NAME' => 'MySQL',
|
||||||
|
'USER' => 'MySQL',
|
||||||
|
'PASSWORD' => 'MySQL',
|
||||||
|
'HOST' => 'MySQL',
|
||||||
|
'PORT' => 'MySQL',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'DRIVER' => 'pdo_pgsql',
|
||||||
|
'NAME' => 'MySQL',
|
||||||
|
'USER' => 'MySQL',
|
||||||
|
'PASSWORD' => 'MySQL',
|
||||||
|
'HOST' => 'MySQL',
|
||||||
|
'PORT' => 'MySQL',
|
||||||
|
], $config->getDatabase());
|
||||||
|
$confirm->shouldHaveBeenCalledTimes(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function sqliteDatabaseIsImportedWhenRequested()
|
||||||
|
{
|
||||||
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||||
|
$copy = $this->filesystem->copy(Argument::cetera())->willReturn(null);
|
||||||
|
|
||||||
|
$config = new CustomizableAppConfig();
|
||||||
|
$config->setDatabase([
|
||||||
|
'DRIVER' => 'pdo_sqlite',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'DRIVER' => 'pdo_sqlite',
|
||||||
|
], $config->getDatabase());
|
||||||
|
$confirm->shouldHaveBeenCalledTimes(1);
|
||||||
|
$copy->shouldHaveBeenCalledTimes(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<?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\LanguageConfigCustomizer;
|
||||||
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
class LanguageConfigCustomizerTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var LanguageConfigCustomizer
|
||||||
|
*/
|
||||||
|
protected $plugin;
|
||||||
|
/**
|
||||||
|
* @var ObjectProphecy
|
||||||
|
*/
|
||||||
|
protected $io;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->io = $this->prophesize(SymfonyStyle::class);
|
||||||
|
$this->io->title(Argument::any())->willReturn(null);
|
||||||
|
$this->plugin = new LanguageConfigCustomizer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function configIsRequestedToTheUser()
|
||||||
|
{
|
||||||
|
$ask = $this->io->choice(Argument::cetera())->willReturn('en');
|
||||||
|
$config = new CustomizableAppConfig();
|
||||||
|
|
||||||
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
|
$this->assertTrue($config->hasLanguage());
|
||||||
|
$this->assertEquals([
|
||||||
|
'DEFAULT' => 'en',
|
||||||
|
'CLI' => 'en',
|
||||||
|
], $config->getLanguage());
|
||||||
|
$ask->shouldHaveBeenCalledTimes(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function overwriteIsRequestedIfValueIsAlreadySet()
|
||||||
|
{
|
||||||
|
$choice = $this->io->choice(Argument::cetera())->willReturn('es');
|
||||||
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(false);
|
||||||
|
$config = new CustomizableAppConfig();
|
||||||
|
$config->setLanguage([
|
||||||
|
'DEFAULT' => 'en',
|
||||||
|
'CLI' => 'en',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'DEFAULT' => 'es',
|
||||||
|
'CLI' => 'es',
|
||||||
|
], $config->getLanguage());
|
||||||
|
$choice->shouldHaveBeenCalledTimes(2);
|
||||||
|
$confirm->shouldHaveBeenCalledTimes(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function existingValueIsKeptIfRequested()
|
||||||
|
{
|
||||||
|
$ask = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||||
|
|
||||||
|
$config = new CustomizableAppConfig();
|
||||||
|
$config->setLanguage([
|
||||||
|
'DEFAULT' => 'es',
|
||||||
|
'CLI' => 'es',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'DEFAULT' => 'es',
|
||||||
|
'CLI' => 'es',
|
||||||
|
], $config->getLanguage());
|
||||||
|
$ask->shouldHaveBeenCalledTimes(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
<?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\UrlShortenerConfigCustomizer;
|
||||||
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
class UrlShortenerConfigCustomizerTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var UrlShortenerConfigCustomizer
|
||||||
|
*/
|
||||||
|
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 UrlShortenerConfigCustomizer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function configIsRequestedToTheUser()
|
||||||
|
{
|
||||||
|
$choice = $this->io->choice(Argument::cetera())->willReturn('something');
|
||||||
|
$ask = $this->io->ask(Argument::cetera())->willReturn('something');
|
||||||
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||||
|
$config = new CustomizableAppConfig();
|
||||||
|
|
||||||
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
|
$this->assertTrue($config->hasUrlShortener());
|
||||||
|
$this->assertEquals([
|
||||||
|
'SCHEMA' => 'something',
|
||||||
|
'HOSTNAME' => 'something',
|
||||||
|
'CHARS' => 'something',
|
||||||
|
'VALIDATE_URL' => true,
|
||||||
|
], $config->getUrlShortener());
|
||||||
|
$ask->shouldHaveBeenCalledTimes(2);
|
||||||
|
$choice->shouldHaveBeenCalledTimes(1);
|
||||||
|
$confirm->shouldHaveBeenCalledTimes(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function overwriteIsRequestedIfValueIsAlreadySet()
|
||||||
|
{
|
||||||
|
$choice = $this->io->choice(Argument::cetera())->willReturn('foo');
|
||||||
|
$ask = $this->io->ask(Argument::cetera())->willReturn('foo');
|
||||||
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(false);
|
||||||
|
$config = new CustomizableAppConfig();
|
||||||
|
$config->setUrlShortener([
|
||||||
|
'SCHEMA' => 'bar',
|
||||||
|
'HOSTNAME' => 'bar',
|
||||||
|
'CHARS' => 'bar',
|
||||||
|
'VALIDATE_URL' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'SCHEMA' => 'foo',
|
||||||
|
'HOSTNAME' => 'foo',
|
||||||
|
'CHARS' => 'foo',
|
||||||
|
'VALIDATE_URL' => false,
|
||||||
|
], $config->getUrlShortener());
|
||||||
|
$ask->shouldHaveBeenCalledTimes(2);
|
||||||
|
$choice->shouldHaveBeenCalledTimes(1);
|
||||||
|
$confirm->shouldHaveBeenCalledTimes(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function existingValueIsKeptIfRequested()
|
||||||
|
{
|
||||||
|
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||||
|
|
||||||
|
$config = new CustomizableAppConfig();
|
||||||
|
$config->setUrlShortener([
|
||||||
|
'SCHEMA' => 'foo',
|
||||||
|
'HOSTNAME' => 'foo',
|
||||||
|
'CHARS' => 'foo',
|
||||||
|
'VALIDATE_URL' => 'foo',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->plugin->process($this->io->reveal(), $config);
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'SCHEMA' => 'foo',
|
||||||
|
'HOSTNAME' => 'foo',
|
||||||
|
'CHARS' => 'foo',
|
||||||
|
'VALIDATE_URL' => 'foo',
|
||||||
|
], $config->getUrlShortener());
|
||||||
|
$confirm->shouldHaveBeenCalledTimes(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user