mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-02 05:03:12 +08:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b53e51de33 | ||
|
|
9478c71af1 | ||
|
|
a2c4eebec8 | ||
|
|
2e5a7d76df | ||
|
|
288249d0b8 | ||
|
|
cd47aae902 | ||
|
|
9bd18ee041 | ||
|
|
22c76df8e6 | ||
|
|
6c87436a96 | ||
|
|
734dac9456 | ||
|
|
85ca366893 | ||
|
|
46db736af8 | ||
|
|
7530048fbd | ||
|
|
c3c03a3a3b | ||
|
|
d1018b6da7 | ||
|
|
fe7928ae0e | ||
|
|
f6c39285c9 | ||
|
|
0e2a289f9f | ||
|
|
f7424da16b | ||
|
|
2e10ee66b7 | ||
|
|
7e442671c3 | ||
|
|
ca646ec2b7 | ||
|
|
4df1af5fd8 | ||
|
|
b4548f3401 | ||
|
|
1819481710 | ||
|
|
e59ae654c0 | ||
|
|
a8bf699f2d | ||
|
|
de9d9d8667 | ||
|
|
869865f22a | ||
|
|
29fd313337 | ||
|
|
7781f07352 | ||
|
|
072371d459 | ||
|
|
51bf948458 | ||
|
|
48acded6ed | ||
|
|
6821f5cf97 | ||
|
|
a15b17e08b | ||
|
|
275b17e1e8 | ||
|
|
27b08ff47b | ||
|
|
bf7c760ca9 | ||
|
|
8af9b0ee02 | ||
|
|
b225c03ef1 | ||
|
|
8a12ed6b8c | ||
|
|
c3fd433446 | ||
|
|
0b9753582d | ||
|
|
9ac48bfbc5 | ||
|
|
85146e5676 | ||
|
|
18ae541c93 | ||
|
|
7a665ec26f | ||
|
|
e3cbac38ce | ||
|
|
31594d47b3 | ||
|
|
42f86a4a24 | ||
|
|
850ce152cd | ||
|
|
c22bbecdc5 | ||
|
|
230f2d155b | ||
|
|
47a2c18c7e | ||
|
|
52bb14bd66 | ||
|
|
8b9caf02d2 | ||
|
|
4580d11d32 | ||
|
|
8610a158d4 | ||
|
|
0a6030b35d | ||
|
|
543c0e62d0 | ||
|
|
4c76e17178 | ||
|
|
611a314cdf | ||
|
|
e7b4d24e5d | ||
|
|
cf60440288 | ||
|
|
15896045f3 | ||
|
|
a9f480ca99 | ||
|
|
4bd67d5f98 | ||
|
|
924ba58f73 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ composer.lock
|
||||
vendor/
|
||||
.env
|
||||
data/database.sqlite
|
||||
docs/swagger-ui
|
||||
|
||||
125
CHANGELOG.md
125
CHANGELOG.md
@@ -1,94 +1,137 @@
|
||||
## CHANGELOG
|
||||
|
||||
### 1.4.0
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
* [89: Update to expressive 2](https://github.com/shlinkio/shlink/issues/89)
|
||||
|
||||
### 1.3.1
|
||||
|
||||
**Tasks**
|
||||
|
||||
* [82: Enable FastRoute routes cache](https://github.com/shlinkio/shlink/issues/82)
|
||||
* [85: Update year in license file](https://github.com/shlinkio/shlink/issues/85)
|
||||
* [81: Add docker containers config](https://github.com/shlinkio/shlink/issues/81)
|
||||
|
||||
**Bugs**
|
||||
|
||||
* [83: Short codes list: search in tags when filtering by query string](https://github.com/shlinkio/shlink/issues/83)
|
||||
* [79: Increase the number of followed redirects](https://github.com/shlinkio/shlink/issues/79)
|
||||
* [75: Apply PathVersionMiddleware only to rest routes defining it by configuration instead of code](https://github.com/shlinkio/shlink/issues/75)
|
||||
* [77: Allow defining database server hostname and port](https://github.com/shlinkio/shlink/issues/77)
|
||||
|
||||
### 1.3.0
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
* [67: Allow to order the short codes list](https://github.com/shlinkio/shlink/issues/67)
|
||||
* [60: Accept JSON requests in REST and use a body parser middleware to set the parsedBody](https://github.com/shlinkio/shlink/issues/60)
|
||||
* [72: When listing API keys from CLI, display in yellow color enabled keys that have expired](https://github.com/shlinkio/shlink/issues/72)
|
||||
* [58: Allow to filter short URLs by tag](https://github.com/shlinkio/shlink/issues/58)
|
||||
* [69: Allow to filter short codes by text query](https://github.com/shlinkio/shlink/issues/69)
|
||||
|
||||
**Tasks**
|
||||
|
||||
* [73: Tag endpoints in swagger file](https://github.com/shlinkio/shlink/issues/73)
|
||||
* [71: Separate swagger docs into multiple files](https://github.com/shlinkio/shlink/issues/71)
|
||||
* [63: Add path versioning to REST API routes](https://github.com/shlinkio/shlink/issues/63)
|
||||
|
||||
### 1.2.2
|
||||
|
||||
**Bugs**
|
||||
|
||||
* Fixed minor bugs on CORS requests
|
||||
|
||||
### 1.2.1
|
||||
|
||||
**Bugs**
|
||||
|
||||
* [62: Fix cross-domain requests in REST API](https://github.com/acelaya/url-shortener/issues/62)
|
||||
* [62: Fix cross-domain requests in REST API](https://github.com/shlinkio/shlink/issues/62)
|
||||
|
||||
### 1.2.0
|
||||
|
||||
**Features**
|
||||
|
||||
* [45: Allow to define tags on short codes, to improve filtering and classification](https://github.com/acelaya/url-shortener/issues/45)
|
||||
* [7: Add website previews while listing available URLs](https://github.com/acelaya/url-shortener/issues/7)
|
||||
* [45: Allow to define tags on short codes, to improve filtering and classification](https://github.com/shlinkio/shlink/issues/45)
|
||||
* [7: Add website previews while listing available URLs](https://github.com/shlinkio/shlink/issues/7)
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
* [57: Add database migrations system to improve updating between versions](https://github.com/acelaya/url-shortener/issues/57)
|
||||
* [31: Add support for other database management systems by improving the EntityManager factory](https://github.com/acelaya/url-shortener/issues/31)
|
||||
* [51: Generate build process to paquetize the app and ease distribution](https://github.com/acelaya/url-shortener/issues/51)
|
||||
* [38: Define installation script. It will request dynamic data on the fly so that there is no need to define env vars](https://github.com/acelaya/url-shortener/issues/38)
|
||||
* [57: Add database migrations system to improve updating between versions](https://github.com/shlinkio/shlink/issues/57)
|
||||
* [31: Add support for other database management systems by improving the EntityManager factory](https://github.com/shlinkio/shlink/issues/31)
|
||||
* [51: Generate build process to paquetize the app and ease distribution](https://github.com/shlinkio/shlink/issues/51)
|
||||
* [38: Define installation script. It will request dynamic data on the fly so that there is no need to define env vars](https://github.com/shlinkio/shlink/issues/38)
|
||||
|
||||
**Tasks**
|
||||
|
||||
* [55: Create update script which does not try to create a new database](https://github.com/acelaya/url-shortener/issues/55)
|
||||
* [54: Add cache namespace to prevent name collisions with other apps in the same environment](https://github.com/acelaya/url-shortener/issues/54)
|
||||
* [29: Use the acelaya/ze-content-based-error-handler package instead of custom error handler implementation](https://github.com/acelaya/url-shortener/issues/29)
|
||||
* [55: Create update script which does not try to create a new database](https://github.com/shlinkio/shlink/issues/55)
|
||||
* [54: Add cache namespace to prevent name collisions with other apps in the same environment](https://github.com/shlinkio/shlink/issues/54)
|
||||
* [29: Use the acelaya/ze-content-based-error-handler package instead of custom error handler implementation](https://github.com/shlinkio/shlink/issues/29)
|
||||
|
||||
**Bugs**
|
||||
|
||||
* [53: Fix entities database interoperability](https://github.com/acelaya/url-shortener/issues/53)
|
||||
* [52: Add missing htaccess file for apache environments](https://github.com/acelaya/url-shortener/issues/52)
|
||||
* [53: Fix entities database interoperability](https://github.com/shlinkio/shlink/issues/53)
|
||||
* [52: Add missing htaccess file for apache environments](https://github.com/shlinkio/shlink/issues/52)
|
||||
|
||||
### 1.1.0
|
||||
|
||||
**Features**
|
||||
|
||||
* [46: Define a route that returns a QR code representing the shortened URL](https://github.com/acelaya/url-shortener/issues/46)
|
||||
* [46: Define a route that returns a QR code representing the shortened URL](https://github.com/shlinkio/shlink/issues/46)
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
* [32: Add support for other cache adapters by improving the Cache factory](https://github.com/acelaya/url-shortener/issues/32)
|
||||
* [14: https://github.com/shlinkio/shlink/issues/14](https://github.com/acelaya/url-shortener/issues/14)
|
||||
* [41: Cache the "short code" => "URL" map to prevent extra DB hits](https://github.com/acelaya/url-shortener/issues/41)
|
||||
* [13: Improve REST authentication](https://github.com/acelaya/url-shortener/issues/13)
|
||||
* [32: Add support for other cache adapters by improving the Cache factory](https://github.com/shlinkio/shlink/issues/32)
|
||||
* [14: https://github.com/shlinkio/shlink/issues/14](https://github.com/shlinkio/shlink/issues/14)
|
||||
* [41: Cache the "short code" => "URL" map to prevent extra DB hits](https://github.com/shlinkio/shlink/issues/41)
|
||||
* [13: Improve REST authentication](https://github.com/shlinkio/shlink/issues/13)
|
||||
|
||||
**Tasks**
|
||||
|
||||
* [39: Change copyright from "Alejandro Celaya" to "Shlink" in error pages](https://github.com/acelaya/url-shortener/issues/39)
|
||||
* [42: Make REST endpoints that need to find something return a 404 when "something" is not found](https://github.com/acelaya/url-shortener/issues/42)
|
||||
* [35: Make CLI commands to use the same PHP namespace as the one used for the command name](https://github.com/acelaya/url-shortener/issues/35)
|
||||
* [39: Change copyright from "Alejandro Celaya" to "Shlink" in error pages](https://github.com/shlinkio/shlink/issues/39)
|
||||
* [42: Make REST endpoints that need to find something return a 404 when "something" is not found](https://github.com/shlinkio/shlink/issues/42)
|
||||
* [35: Make CLI commands to use the same PHP namespace as the one used for the command name](https://github.com/shlinkio/shlink/issues/35)
|
||||
|
||||
**Bugs**
|
||||
|
||||
* [40: Take into account the X-Forwarded-For header in order to get the visitor information, in case the server is behind a load balancer or proxy](https://github.com/acelaya/url-shortener/issues/40)
|
||||
* [40: Take into account the X-Forwarded-For header in order to get the visitor information, in case the server is behind a load balancer or proxy](https://github.com/shlinkio/shlink/issues/40)
|
||||
|
||||
### 1.0.0
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
* [33: Create a command to generate a short code charset by randomizing the default one](https://github.com/acelaya/url-shortener/issues/33)
|
||||
* [15: Return JSON/HTML responses for errors (4xx and 5xx) based on accept header (content negotiation)](https://github.com/acelaya/url-shortener/issues/15)
|
||||
* [23: Translate application literals](https://github.com/acelaya/url-shortener/issues/23)
|
||||
* [21: Allow to filter visits by date range](https://github.com/acelaya/url-shortener/issues/21)
|
||||
* [22: Save visits locations data on a visit_locations table](https://github.com/acelaya/url-shortener/issues/22)
|
||||
* [20: Inject cross domain headers in response only if the Origin header is present in the request](https://github.com/acelaya/url-shortener/issues/20)
|
||||
* [11: Separate code into multiple modules](https://github.com/acelaya/url-shortener/issues/11)
|
||||
* [18: Group routable middleware in an Action namespace](https://github.com/acelaya/url-shortener/issues/18)
|
||||
* [33: Create a command to generate a short code charset by randomizing the default one](https://github.com/shlinkio/shlink/issues/33)
|
||||
* [15: Return JSON/HTML responses for errors (4xx and 5xx) based on accept header (content negotiation)](https://github.com/shlinkio/shlink/issues/15)
|
||||
* [23: Translate application literals](https://github.com/shlinkio/shlink/issues/23)
|
||||
* [21: Allow to filter visits by date range](https://github.com/shlinkio/shlink/issues/21)
|
||||
* [22: Save visits locations data on a visit_locations table](https://github.com/shlinkio/shlink/issues/22)
|
||||
* [20: Inject cross domain headers in response only if the Origin header is present in the request](https://github.com/shlinkio/shlink/issues/20)
|
||||
* [11: Separate code into multiple modules](https://github.com/shlinkio/shlink/issues/11)
|
||||
* [18: Group routable middleware in an Action namespace](https://github.com/shlinkio/shlink/issues/18)
|
||||
|
||||
**Tasks**
|
||||
|
||||
* [36: Remove hhvm from the CI matrix since it doesn't support array constants and will fail](https://github.com/acelaya/url-shortener/issues/36)
|
||||
* [4: Installation steps](https://github.com/acelaya/url-shortener/issues/4)
|
||||
* [6: Remove dependency on expressive helpers package](https://github.com/acelaya/url-shortener/issues/6)
|
||||
* [30: Replace the "services" first level config entry by "dependencies", in order to fulfill default Expressive name](https://github.com/acelaya/url-shortener/issues/30)
|
||||
* [12: Improve code coverage](https://github.com/acelaya/url-shortener/issues/12)
|
||||
* [25: Replace "Middleware" suffix on routable middlewares by "Action"](https://github.com/acelaya/url-shortener/issues/25)
|
||||
* [19: Update the vendor and app namespace from Acelaya\UrlShortener to Shlinkio\Shlink](https://github.com/acelaya/url-shortener/issues/19)
|
||||
* [36: Remove hhvm from the CI matrix since it doesn't support array constants and will fail](https://github.com/shlinkio/shlink/issues/36)
|
||||
* [4: Installation steps](https://github.com/shlinkio/shlink/issues/4)
|
||||
* [6: Remove dependency on expressive helpers package](https://github.com/shlinkio/shlink/issues/6)
|
||||
* [30: Replace the "services" first level config entry by "dependencies", in order to fulfill default Expressive name](https://github.com/shlinkio/shlink/issues/30)
|
||||
* [12: Improve code coverage](https://github.com/shlinkio/shlink/issues/12)
|
||||
* [25: Replace "Middleware" suffix on routable middlewares by "Action"](https://github.com/shlinkio/shlink/issues/25)
|
||||
* [19: Update the vendor and app namespace from Acelaya\UrlShortener to Shlinkio\Shlink](https://github.com/shlinkio/shlink/issues/19)
|
||||
|
||||
**Bugs**
|
||||
|
||||
* [24: Prevent duplicated shortcodes errors because of the case insensitive behavior on MySQL](https://github.com/acelaya/url-shortener/issues/24)
|
||||
* [24: Prevent duplicated shortcodes errors because of the case insensitive behavior on MySQL](https://github.com/shlinkio/shlink/issues/24)
|
||||
|
||||
### 0.2.0
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
* [9: Use symfony/console to dispatch console requests, instead of trying to integrate the process with expressive](https://github.com/acelaya/url-shortener/issues/9)
|
||||
* [8: Create a REST API](https://github.com/acelaya/url-shortener/issues/8)
|
||||
* [10: Add more CLI functionality](https://github.com/acelaya/url-shortener/issues/10)
|
||||
* [9: Use symfony/console to dispatch console requests, instead of trying to integrate the process with expressive](https://github.com/shlinkio/shlink/issues/9)
|
||||
* [8: Create a REST API](https://github.com/shlinkio/shlink/issues/8)
|
||||
* [10: Add more CLI functionality](https://github.com/shlinkio/shlink/issues/10)
|
||||
|
||||
**Tasks**
|
||||
|
||||
* [5: Create CHANGELOG file](https://github.com/acelaya/url-shortener/issues/5)
|
||||
* [5: Create CHANGELOG file](https://github.com/shlinkio/shlink/issues/5)
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Alejandro Celaya
|
||||
Copyright (c) 2017 Alejandro Celaya
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
4
build.sh
4
build.sh
@@ -15,6 +15,7 @@ projectdir=$(pwd)
|
||||
echo 'Copying project files...'
|
||||
rm -rf "${builtcontent}"
|
||||
mkdir "${builtcontent}"
|
||||
sudo chmod -R 777 "${projectdir}"/data/infra/{database,nginx}
|
||||
cp -R "${projectdir}"/* "${builtcontent}"
|
||||
cd "${builtcontent}"
|
||||
|
||||
@@ -22,7 +23,7 @@ cd "${builtcontent}"
|
||||
rm -r vendor
|
||||
rm composer.lock
|
||||
composer self-update
|
||||
composer install --no-dev --optimize-autoloader
|
||||
composer install --no-dev --optimize-autoloader --no-progress --no-interaction
|
||||
|
||||
# Delete development files
|
||||
echo 'Deleting dev files...'
|
||||
@@ -34,6 +35,7 @@ rm php*
|
||||
rm README.md
|
||||
rm -r build
|
||||
rm -f data/database.sqlite
|
||||
rm -rf data/infra
|
||||
rm -rf data/{cache,log,proxies}/{*,.gitignore}
|
||||
rm -rf config/params/{*,.gitignore}
|
||||
rm -rf config/autoload/{{,*.}local.php{,.dist},.gitignore}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
{
|
||||
"name": "shlinkio/shlink",
|
||||
"type": "project",
|
||||
"homepage": "http://shlink.io",
|
||||
"homepage": "https://shlink.io",
|
||||
"description": "A self-hosted and PHP-based URL shortener application with CLI and REST interfaces",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Alejandro Celaya Alastrué",
|
||||
"homepage": "http://www.alejandrocelaya.com",
|
||||
"homepage": "https://www.alejandrocelaya.com",
|
||||
"email": "alejandro@alejandrocelaya.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^5.6 || ^7.0",
|
||||
"zendframework/zend-expressive": "^1.0",
|
||||
"zendframework/zend-expressive-fastroute": "^1.1",
|
||||
"zendframework/zend-expressive-twigrenderer": "^1.0",
|
||||
"zendframework/zend-stdlib": "^2.7",
|
||||
"zendframework/zend-expressive": "^2.0",
|
||||
"zendframework/zend-expressive-fastroute": "^2.0",
|
||||
"zendframework/zend-expressive-twigrenderer": "^1.4",
|
||||
"zendframework/zend-stdlib": "^3.0",
|
||||
"zendframework/zend-servicemanager": "^3.0",
|
||||
"zendframework/zend-paginator": "^2.6",
|
||||
"zendframework/zend-config": "^2.6",
|
||||
"zendframework/zend-config": "^3.0",
|
||||
"zendframework/zend-i18n": "^2.7",
|
||||
"mtymek/expressive-config-manager": "^0.4",
|
||||
"acelaya/zsm-annotated-services": "^0.2.0",
|
||||
"acelaya/ze-content-based-error-handler": "^1.0",
|
||||
"zendframework/zend-config-aggregator": "^0.1",
|
||||
"acelaya/zsm-annotated-services": "^1.0",
|
||||
"acelaya/ze-content-based-error-handler": "^2.0",
|
||||
"doctrine/orm": "^2.5",
|
||||
"guzzlehttp/guzzle": "^6.2",
|
||||
"symfony/console": "^3.0",
|
||||
@@ -37,7 +37,7 @@
|
||||
"doctrine/migrations": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.0",
|
||||
"phpunit/phpunit": "^5.7 || ^6.0",
|
||||
"squizlabs/php_codesniffer": "^2.3",
|
||||
"roave/security-advisories": "dev-master",
|
||||
"filp/whoops": "^2.0",
|
||||
@@ -73,5 +73,8 @@
|
||||
"serve": "php -S 0.0.0.0:8000 -t public/",
|
||||
"test": "phpunit --coverage-clover build/clover.xml",
|
||||
"pretty-test": "phpunit --coverage-html build/coverage"
|
||||
},
|
||||
"config": {
|
||||
"process-timeout": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,17 @@ use Zend\Expressive\Container;
|
||||
use Zend\Expressive\Router;
|
||||
use Zend\Expressive\Template;
|
||||
use Zend\Expressive\Twig;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
use Zend\Stratigility\Middleware\ErrorHandler;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
Expressive\Application::class => Container\ApplicationFactory::class,
|
||||
Router\FastRouteRouter::class => InvokableFactory::class,
|
||||
Template\TemplateRendererInterface::class => Twig\TwigRendererFactory::class,
|
||||
],
|
||||
'aliases' => [
|
||||
Router\RouterInterface::class => Router\FastRouteRouter::class,
|
||||
\Twig_Environment::class => Twig\TwigEnvironmentFactory::class,
|
||||
Router\RouterInterface::class => Router\FastRouteRouterFactory::class,
|
||||
ErrorHandler::class => Container\ErrorHandlerFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -6,14 +6,10 @@ return [
|
||||
'proxies_dir' => 'data/proxies',
|
||||
],
|
||||
'connection' => [
|
||||
'driver' => 'pdo_mysql',
|
||||
'user' => env('DB_USER'),
|
||||
'password' => env('DB_PASSWORD'),
|
||||
'dbname' => env('DB_NAME', 'shlink'),
|
||||
'charset' => 'utf8',
|
||||
'driverOptions' => [
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
14
config/autoload/entity-manager.local.php.dist
Normal file
14
config/autoload/entity-manager.local.php.dist
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
return [
|
||||
|
||||
'entity_manager' => [
|
||||
'connection' => [
|
||||
'driver' => 'pdo_mysql',
|
||||
'host' => 'shlink_db',
|
||||
'driverOptions' => [
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
use Acelaya\ExpressiveErrorHandler\ErrorHandler\ContentBasedErrorHandler;
|
||||
use Zend\Expressive\Container\WhoopsErrorHandlerFactory;
|
||||
use Zend\Expressive\Container\WhoopsErrorResponseGeneratorFactory;
|
||||
|
||||
return [
|
||||
'dependencies' => [
|
||||
@@ -21,7 +20,7 @@ return [
|
||||
'error_handler' => [
|
||||
'plugins' => [
|
||||
'factories' => [
|
||||
ContentBasedErrorHandler::DEFAULT_CONTENT => WhoopsErrorHandlerFactory::class,
|
||||
'text/html' => WhoopsErrorResponseGeneratorFactory::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
<?php
|
||||
use Shlinkio\Shlink\Common\Middleware\LocaleMiddleware;
|
||||
use Shlinkio\Shlink\Rest\Middleware\BodyParserMiddleware;
|
||||
use Shlinkio\Shlink\Rest\Middleware\CheckAuthenticationMiddleware;
|
||||
use Shlinkio\Shlink\Rest\Middleware\CrossDomainMiddleware;
|
||||
use Shlinkio\Shlink\Rest\Middleware\PathVersionMiddleware;
|
||||
use Zend\Expressive\Container\ApplicationFactory;
|
||||
use Zend\Stratigility\Middleware\ErrorHandler;
|
||||
|
||||
return [
|
||||
|
||||
'middleware_pipeline' => [
|
||||
'pre-routing' => [
|
||||
'middleware' => [
|
||||
ErrorHandler::class,
|
||||
LocaleMiddleware::class,
|
||||
],
|
||||
'priority' => 11,
|
||||
],
|
||||
'pre-routing-rest' => [
|
||||
'path' => '/rest',
|
||||
'middleware' => [
|
||||
PathVersionMiddleware::class,
|
||||
],
|
||||
'priority' => 11,
|
||||
],
|
||||
|
||||
'routing' => [
|
||||
'middleware' => [
|
||||
ApplicationFactory::ROUTING_MIDDLEWARE,
|
||||
@@ -11,6 +32,16 @@ return [
|
||||
'priority' => 10,
|
||||
],
|
||||
|
||||
'rest' => [
|
||||
'path' => '/rest',
|
||||
'middleware' => [
|
||||
CrossDomainMiddleware::class,
|
||||
BodyParserMiddleware::class,
|
||||
CheckAuthenticationMiddleware::class,
|
||||
],
|
||||
'priority' => 5,
|
||||
],
|
||||
|
||||
'post-routing' => [
|
||||
'middleware' => [
|
||||
ApplicationFactory::DISPATCH_MIDDLEWARE,
|
||||
|
||||
13
config/autoload/router.global.php
Normal file
13
config/autoload/router.global.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
use Zend\Expressive\Router\FastRouteRouter;
|
||||
|
||||
return [
|
||||
|
||||
'router' => [
|
||||
'fastroute' => [
|
||||
FastRouteRouter::CONFIG_CACHE_ENABLED => true,
|
||||
FastRouteRouter::CONFIG_CACHE_FILE => 'data/cache/fastroute_cached_routes.php',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
12
config/autoload/router.local.php.dist
Normal file
12
config/autoload/router.local.php.dist
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
use Zend\Expressive\Router\FastRouteRouter;
|
||||
|
||||
return [
|
||||
|
||||
'router' => [
|
||||
'fastroute' => [
|
||||
FastRouteRouter::CONFIG_CACHE_ENABLED => false,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -4,7 +4,7 @@ use Shlinkio\Shlink\CLI;
|
||||
use Shlinkio\Shlink\Common;
|
||||
use Shlinkio\Shlink\Core;
|
||||
use Shlinkio\Shlink\Rest;
|
||||
use Zend\Expressive\ConfigManager;
|
||||
use Zend\ConfigAggregator;
|
||||
|
||||
/**
|
||||
* Configuration files are loaded in a specific order. First ``global.php``, then ``*.global.php``.
|
||||
@@ -15,11 +15,11 @@ use Zend\Expressive\ConfigManager;
|
||||
* Obviously, if you use closures in your config you can't cache it.
|
||||
*/
|
||||
|
||||
return (new ConfigManager\ConfigManager([
|
||||
return (new ConfigAggregator\ConfigAggregator([
|
||||
ExpressiveErrorHandler\ConfigProvider::class,
|
||||
Common\ConfigProvider::class,
|
||||
Core\ConfigProvider::class,
|
||||
CLI\ConfigProvider::class,
|
||||
Rest\ConfigProvider::class,
|
||||
new ConfigManager\ZendConfigProvider('config/{autoload/{{,*.}global,{,*.}local},params/generated_config}.php'),
|
||||
new ConfigAggregator\ZendConfigProvider('config/{autoload/{{,*.}global,{,*.}local},params/generated_config}.php'),
|
||||
], 'data/cache/app_config.php'))->getMergedConfig();
|
||||
|
||||
2
data/infra/database/.gitignore
vendored
Normal file
2
data/infra/database/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
6
data/infra/db.Dockerfile
Normal file
6
data/infra/db.Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
||||
FROM mysql:5.7
|
||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||
|
||||
# Enable remote access (default is localhost only, we change this
|
||||
# otherwise our database would not be reachable from outside the container)
|
||||
RUN sed -i -e"s/^bind-address\s*=\s*127.0.0.1/bind-address = 0.0.0.0/" /etc/mysql/my.cnf
|
||||
5
data/infra/nginx.Dockerfile
Normal file
5
data/infra/nginx.Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM nginx:1.11.6-alpine
|
||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||
|
||||
# Delete default nginx vhost
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
2
data/infra/nginx/.gitignore
vendored
Normal file
2
data/infra/nginx/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
87
data/infra/php.Dockerfile
Normal file
87
data/infra/php.Dockerfile
Normal file
@@ -0,0 +1,87 @@
|
||||
FROM php:7.1-fpm-alpine
|
||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||
|
||||
RUN apk update
|
||||
|
||||
# Install common php extensions
|
||||
RUN docker-php-ext-install pdo_mysql
|
||||
RUN docker-php-ext-install iconv
|
||||
RUN docker-php-ext-install mbstring
|
||||
RUN docker-php-ext-install calendar
|
||||
|
||||
RUN apk add --no-cache --virtual sqlite-libs
|
||||
RUN apk add --no-cache --virtual sqlite-dev
|
||||
RUN docker-php-ext-install pdo_sqlite
|
||||
|
||||
RUN apk add --no-cache --virtual icu-dev
|
||||
RUN docker-php-ext-install intl
|
||||
|
||||
RUN apk add --no-cache --virtual zlib-dev
|
||||
RUN docker-php-ext-install zip
|
||||
|
||||
RUN apk add --no-cache --virtual libmcrypt-dev
|
||||
RUN docker-php-ext-install mcrypt
|
||||
|
||||
RUN apk add --no-cache --virtual libpng-dev
|
||||
RUN docker-php-ext-install gd
|
||||
|
||||
# Install redis extension
|
||||
ADD https://github.com/phpredis/phpredis/archive/php7.tar.gz /tmp/phpredis.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/redis\
|
||||
&& tar xf /tmp/phpredis.tar.gz -C /usr/src/php/ext/redis --strip-components=1
|
||||
# configure and install
|
||||
RUN docker-php-ext-configure redis\
|
||||
&& docker-php-ext-install redis
|
||||
# cleanup
|
||||
RUN rm /tmp/phpredis.tar.gz
|
||||
|
||||
# Install memcached extension
|
||||
RUN apk add --no-cache --virtual cyrus-sasl-dev
|
||||
RUN apk add --no-cache --virtual libmemcached-dev
|
||||
ADD https://github.com/php-memcached-dev/php-memcached/archive/php7.tar.gz /tmp/memcached.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/memcached\
|
||||
&& tar xf /tmp/memcached.tar.gz -C /usr/src/php/ext/memcached --strip-components=1
|
||||
# configure and install
|
||||
RUN docker-php-ext-configure memcached\
|
||||
&& docker-php-ext-install memcached
|
||||
# cleanup
|
||||
RUN rm /tmp/memcached.tar.gz
|
||||
|
||||
# Install APCu extension
|
||||
ADD https://pecl.php.net/get/apcu-5.1.3.tgz /tmp/apcu.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/apcu\
|
||||
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1
|
||||
# configure and install
|
||||
RUN docker-php-ext-configure apcu\
|
||||
&& docker-php-ext-install apcu
|
||||
# cleanup
|
||||
RUN rm /tmp/apcu.tar.gz
|
||||
|
||||
# Install APCu-BC extension
|
||||
ADD https://pecl.php.net/get/apcu_bc-1.0.3.tgz /tmp/apcu_bc.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/apcu-bc\
|
||||
&& tar xf /tmp/apcu_bc.tar.gz -C /usr/src/php/ext/apcu-bc --strip-components=1
|
||||
# configure and install
|
||||
RUN docker-php-ext-configure apcu-bc\
|
||||
&& docker-php-ext-install apcu-bc
|
||||
# cleanup
|
||||
RUN rm /tmp/apcu_bc.tar.gz
|
||||
|
||||
# Load APCU.ini before APC.ini
|
||||
RUN rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini
|
||||
RUN echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||
|
||||
# Install xdebug
|
||||
ADD https://pecl.php.net/get/xdebug-2.5.0 /tmp/xdebug.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/xdebug\
|
||||
&& tar xf /tmp/xdebug.tar.gz -C /usr/src/php/ext/xdebug --strip-components=1
|
||||
# configure and install
|
||||
RUN docker-php-ext-configure xdebug\
|
||||
&& docker-php-ext-install xdebug
|
||||
# cleanup
|
||||
RUN rm /tmp/xdebug.tar.gz
|
||||
|
||||
# Install composer
|
||||
RUN php -r "readfile('https://getcomposer.org/installer');" | php
|
||||
RUN chmod +x composer.phar
|
||||
RUN mv composer.phar /usr/local/bin/composer
|
||||
1
data/infra/php.ini
Normal file
1
data/infra/php.ini
Normal file
@@ -0,0 +1 @@
|
||||
date.timezone = Europe/Madrid
|
||||
21
data/infra/vhost.conf
Normal file
21
data/infra/vhost.conf
Normal file
@@ -0,0 +1,21 @@
|
||||
server {
|
||||
listen 80 default_server;
|
||||
server_name shlink.local;
|
||||
root /home/shlink/www/public;
|
||||
index index.php;
|
||||
|
||||
charset utf-8;
|
||||
error_log /home/shlink/www/data/infra/nginx/shlink.error.log;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
root /home/shlink/www/public;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass shlink_php:9000;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi.conf;
|
||||
}
|
||||
}
|
||||
0
data/proxies/.gitignore
vendored
Normal file → Executable file
0
data/proxies/.gitignore
vendored
Normal file → Executable file
41
docker-compose.yml
Normal file
41
docker-compose.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
shlink_nginx:
|
||||
container_name: shlink_nginx
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./data/infra/nginx.Dockerfile
|
||||
ports:
|
||||
- "8000:80"
|
||||
volumes:
|
||||
- ./:/home/shlink/www
|
||||
- ./docs:/home/shlink/www/public/docs
|
||||
- ./data/infra/vhost.conf:/etc/nginx/conf.d/shlink-vhost.conf
|
||||
links:
|
||||
- shlink_php
|
||||
|
||||
shlink_php:
|
||||
container_name: shlink_php
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./data/infra/php.Dockerfile
|
||||
volumes:
|
||||
- ./:/home/shlink/www
|
||||
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
|
||||
links:
|
||||
- shlink_db
|
||||
|
||||
shlink_db:
|
||||
container_name: shlink_db
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./data/infra/db.Dockerfile
|
||||
ports:
|
||||
- "3307:3306"
|
||||
volumes:
|
||||
- ./:/home/shlink/www
|
||||
- ./data/infra/database:/var/lib/mysql
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: shlink
|
||||
13
docs/swagger/definitions/Error.json
Normal file
13
docs/swagger/definitions/Error.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"description": "A machine unique code"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "A human-friendly error message"
|
||||
}
|
||||
}
|
||||
}
|
||||
13
docs/swagger/definitions/Pagination.json
Normal file
13
docs/swagger/definitions/Pagination.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"currentPage": {
|
||||
"type": "integer",
|
||||
"description": "The number of current page being displayed."
|
||||
},
|
||||
"pagesCount": {
|
||||
"type": "integer",
|
||||
"description": "The total number of pages that can be displayed."
|
||||
}
|
||||
}
|
||||
}
|
||||
29
docs/swagger/definitions/ShortUrl.json
Normal file
29
docs/swagger/definitions/ShortUrl.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"shortCode": {
|
||||
"type": "string",
|
||||
"description": "The short code for this short URL."
|
||||
},
|
||||
"originalUrl": {
|
||||
"type": "string",
|
||||
"description": "The original long URL."
|
||||
},
|
||||
"dateCreated": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "The date in which the short URL was created in ISO format."
|
||||
},
|
||||
"visitsCount": {
|
||||
"type": "integer",
|
||||
"description": "The number of visits that this short URL has recieved."
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "A list of tags applied to this short URL"
|
||||
}
|
||||
}
|
||||
}
|
||||
18
docs/swagger/definitions/Visit.json
Normal file
18
docs/swagger/definitions/Visit.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"referer": {
|
||||
"type": "string"
|
||||
},
|
||||
"date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"remoteAddr": {
|
||||
"type": "string"
|
||||
},
|
||||
"userAgent": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
7
docs/swagger/parameters/Authorization.json
Normal file
7
docs/swagger/parameters/Authorization.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "The authorization token with Bearer type",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
50
docs/swagger/paths/v1_authenticate.json
Normal file
50
docs/swagger/paths/v1_authenticate.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"post": {
|
||||
"tags": [
|
||||
"Authentication"
|
||||
],
|
||||
"summary": "Perform authentication",
|
||||
"description": "Performs an authentication",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "apiKey",
|
||||
"in": "formData",
|
||||
"description": "The API key to authenticate with",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The authentication worked.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string",
|
||||
"description": "The authentication token that needs to be sent in the Authorization header"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "An API key was not provided.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The API key is incorrect, is disabled or has expired.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
144
docs/swagger/paths/v1_short-codes.json
Normal file
144
docs/swagger/paths/v1_short-codes.json
Normal file
@@ -0,0 +1,144 @@
|
||||
{
|
||||
"get": {
|
||||
"tags": [
|
||||
"ShortCodes"
|
||||
],
|
||||
"summary": "List short URLs",
|
||||
"description": "Returns the list of short codes",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "page",
|
||||
"in": "query",
|
||||
"description": "The page to be displayed. Defaults to 1",
|
||||
"required": false,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "searchTerm",
|
||||
"in": "query",
|
||||
"description": "A query used to filter results by searching for it on the longUrl and shortCode fields. (Since v1.3.0)",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"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)",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orderBy",
|
||||
"in": "query",
|
||||
"description": "The field from which you want to order the result. (Since v1.3.0)",
|
||||
"enum": [
|
||||
"originalUrl",
|
||||
"shortCode",
|
||||
"dateCreated",
|
||||
"visits"
|
||||
],
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"$ref": "../parameters/Authorization.json"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The list of short URLs",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"shortUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "../definitions/ShortUrl.json"
|
||||
}
|
||||
},
|
||||
"pagination": {
|
||||
"$ref": "../definitions/Pagination.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"ShortCodes"
|
||||
],
|
||||
"summary": "Create short URL",
|
||||
"description": "Creates a new short code",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "longUrl",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The result of parsing the long URL",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"longUrl": {
|
||||
"type": "string",
|
||||
"description": "The original long URL that has been parsed"
|
||||
},
|
||||
"shortUrl": {
|
||||
"type": "string",
|
||||
"description": "The generated short URL"
|
||||
},
|
||||
"shortCode": {
|
||||
"type": "string",
|
||||
"description": "the short code that is being used in the short URL"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "The long URL was not provided or is invalid.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
docs/swagger/paths/v1_short-codes_{shortCode}.json
Normal file
53
docs/swagger/paths/v1_short-codes_{shortCode}.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"get": {
|
||||
"tags": [
|
||||
"ShortCodes"
|
||||
],
|
||||
"summary": "Parse short code",
|
||||
"description": "Get the long URL behind a short code.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "shortCode",
|
||||
"in": "path",
|
||||
"type": "string",
|
||||
"description": "The short code to resolve.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"$ref": "../parameters/Authorization.json"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The long URL behind a short code.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"longUrl": {
|
||||
"type": "string",
|
||||
"description": "The original long URL behind the short code."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Provided shortCode does not match the character set currently used by the app to generate short codes.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "No URL was found for provided short code.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
docs/swagger/paths/v1_short-codes_{shortCode}_tags.json
Normal file
66
docs/swagger/paths/v1_short-codes_{shortCode}_tags.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"put": {
|
||||
"tags": [
|
||||
"ShortCodes",
|
||||
"Tags"
|
||||
],
|
||||
"summary": "Edit tags on short URL",
|
||||
"description": "Edit the tags on provided short code.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "shortCode",
|
||||
"in": "path",
|
||||
"type": "string",
|
||||
"description": "The shortCode in which we want to edit tags.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"in": "formData",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "The list of tags to set to the short URL.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"$ref": "../parameters/Authorization.json"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of tags.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "The request body does not contain a \"tags\" param with array type.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "No short URL was found for provided short code.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
docs/swagger/paths/v1_short-codes_{shortCode}_visits.json
Normal file
55
docs/swagger/paths/v1_short-codes_{shortCode}_visits.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"get": {
|
||||
"tags": [
|
||||
"ShortCodes",
|
||||
"Visits"
|
||||
],
|
||||
"summary": "List visits for short URL",
|
||||
"description": "Get the list of visits on provided short code.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "shortCode",
|
||||
"in": "path",
|
||||
"type": "string",
|
||||
"description": "The shortCode from which we want to get the visits.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"$ref": "../parameters/Authorization.json"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of visits.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"visits": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "../definitions/Visit.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "The short code does not belong to any short URL.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
docs/swagger/swagger.json
Normal file
38
docs/swagger/swagger.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "Shlink",
|
||||
"description": "Shlink, the self-hosted URL shortener",
|
||||
"version": "1.2.0"
|
||||
},
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"basePath": "/rest",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"consumes": [
|
||||
"application/x-www-form-urlencoded",
|
||||
"application/json"
|
||||
],
|
||||
|
||||
"paths": {
|
||||
"/v1/authenticate": {
|
||||
"$ref": "paths/v1_authenticate.json"
|
||||
},
|
||||
"/v1/short-codes": {
|
||||
"$ref": "paths/v1_short-codes.json"
|
||||
},
|
||||
"/v1/short-codes/{shortCode}": {
|
||||
"$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": {
|
||||
"$ref": "paths/v1_short-codes_{shortCode}_tags.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
2
indocker
Executable file
2
indocker
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
docker exec -it shlink_php /bin/sh -c "cd /home/shlink/www && $*"
|
||||
Binary file not shown.
@@ -1,8 +1,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Shlink 1.0\n"
|
||||
"POT-Creation-Date: 2016-08-21 18:16+0200\n"
|
||||
"PO-Revision-Date: 2016-08-21 18:16+0200\n"
|
||||
"POT-Creation-Date: 2016-10-22 23:12+0200\n"
|
||||
"PO-Revision-Date: 2016-10-22 23:13+0200\n"
|
||||
"Last-Translator: Alejandro Celaya <alejandro@alejandrocelaya.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: es_ES\n"
|
||||
@@ -162,6 +162,22 @@ msgstr "Listar todas las URLs cortas"
|
||||
msgid "The first page to list (%s items per page)"
|
||||
msgstr "La primera página a listar (%s elementos por página)"
|
||||
|
||||
msgid ""
|
||||
"A query used to filter results by searching for it on the longUrl and "
|
||||
"shortCode fields"
|
||||
msgstr ""
|
||||
"Una consulta usada para filtrar el resultado buscándola en los campos "
|
||||
"longUrl y shortCode"
|
||||
|
||||
msgid "A comma-separated list of tags to filter results"
|
||||
msgstr "Una lista de etiquetas separadas por coma para filtrar el resultado"
|
||||
|
||||
msgid ""
|
||||
"The field from which we want to order by. Pass ASC or DESC separated by a "
|
||||
"comma"
|
||||
msgstr ""
|
||||
"El campo por el cual queremos ordernar. Pasa ASC o DESC separado por una coma"
|
||||
|
||||
msgid "Whether to display the tags or not"
|
||||
msgstr "Si se desea mostrar las etiquetas o no"
|
||||
|
||||
|
||||
@@ -73,12 +73,15 @@ class ListKeysCommand extends Command
|
||||
$key = $row->getKey();
|
||||
$expiration = $row->getExpirationDate();
|
||||
$rowData = [];
|
||||
$formatMethod = ! $row->isEnabled()
|
||||
? 'getErrorString'
|
||||
: ($row->isExpired() ? 'getWarningString' : 'getSuccessString');
|
||||
|
||||
if ($enabledOnly) {
|
||||
$rowData[] = $key;
|
||||
$rowData[] = $this->{$formatMethod}($key);
|
||||
} else {
|
||||
$rowData[] = $row->isEnabled() ? $this->getSuccessString($key) : $this->getErrorString($key);
|
||||
$rowData[] = $row->isEnabled() ? $this->getSuccessString('+++') : $this->getErrorString('---');
|
||||
$rowData[] = $this->{$formatMethod}($key);
|
||||
$rowData[] = $this->{$formatMethod}($this->getEnabledSymbol($row));
|
||||
}
|
||||
|
||||
$rowData[] = isset($expiration) ? $expiration->format(\DateTime::ISO8601) : '-';
|
||||
@@ -105,4 +108,22 @@ class ListKeysCommand extends Command
|
||||
{
|
||||
return sprintf('<info>%s</info>', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $string
|
||||
* @return string
|
||||
*/
|
||||
protected function getWarningString($string)
|
||||
{
|
||||
return sprintf('<comment>%s</comment>', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ApiKey $apiKey
|
||||
* @return string
|
||||
*/
|
||||
protected function getEnabledSymbol(ApiKey $apiKey)
|
||||
{
|
||||
return ! $apiKey->isEnabled() || $apiKey->isExpired() ? '---' : '+++';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,11 +135,18 @@ class InstallCommand extends Command
|
||||
$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');
|
||||
@@ -272,6 +279,14 @@ class InstallCommand extends Command
|
||||
$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',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $config;
|
||||
|
||||
@@ -56,9 +56,31 @@ class ListShortcodesCommand extends Command
|
||||
),
|
||||
1
|
||||
)
|
||||
->addOption(
|
||||
'searchTerm',
|
||||
's',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
$this->translator->translate(
|
||||
'A query used to filter results by searching for it on the longUrl and shortCode fields'
|
||||
)
|
||||
)
|
||||
->addOption(
|
||||
'tags',
|
||||
't',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
$this->translator->translate('A comma-separated list of tags to filter results')
|
||||
)
|
||||
->addOption(
|
||||
'orderBy',
|
||||
'o',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
$this->translator->translate(
|
||||
'The field from which we want to order by. Pass ASC or DESC separated by a comma'
|
||||
)
|
||||
)
|
||||
->addOption(
|
||||
'showTags',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
$this->translator->translate('Whether to display the tags or not')
|
||||
);
|
||||
@@ -67,13 +89,17 @@ class ListShortcodesCommand extends Command
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$page = intval($input->getOption('page'));
|
||||
$showTags = $input->getOption('tags');
|
||||
$searchTerm = $input->getOption('searchTerm');
|
||||
$tags = $input->getOption('tags');
|
||||
$tags = ! empty($tags) ? explode(',', $tags) : [];
|
||||
$showTags = $input->getOption('showTags');
|
||||
$orderBy = $input->getOption('orderBy');
|
||||
|
||||
/** @var QuestionHelper $helper */
|
||||
$helper = $this->getHelper('question');
|
||||
|
||||
do {
|
||||
$result = $this->shortUrlService->listShortUrls($page);
|
||||
$result = $this->shortUrlService->listShortUrls($page, $searchTerm, $tags, $this->processOrderBy($input));
|
||||
$page++;
|
||||
$table = new Table($output);
|
||||
|
||||
@@ -119,4 +145,15 @@ class ListShortcodesCommand extends Command
|
||||
}
|
||||
} while ($continue);
|
||||
}
|
||||
|
||||
protected function processOrderBy(InputInterface $input)
|
||||
{
|
||||
$orderBy = $input->getOption('orderBy');
|
||||
if (empty($orderBy)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$orderBy = explode(',', $orderBy);
|
||||
return count($orderBy) === 1 ? $orderBy[0] : [$orderBy[0] => $orderBy[1]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Api;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Api\DisableKeyCommand;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Api;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Api\GenerateKeyCommand;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Api;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Api\ListKeysCommand;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Config;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\CLI\Command\Config\GenerateCharsetCommand;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Install;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Install\InstallCommand;
|
||||
@@ -47,12 +47,14 @@ class InstallCommandTest extends TestCase
|
||||
|
||||
protected function createInputStream()
|
||||
{
|
||||
$stream = fopen('php://memory', 'r+', false);
|
||||
fputs($stream, <<<CLI_INPUT
|
||||
$stream = fopen('php://memory', 'rb+', false);
|
||||
fwrite($stream, <<<CLI_INPUT
|
||||
|
||||
shlink_db
|
||||
alejandro
|
||||
1234
|
||||
|
||||
|
||||
0
|
||||
doma.in
|
||||
abc123BCA
|
||||
@@ -69,7 +71,7 @@ CLI_INPUT
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function testInputIsProperlyParsed()
|
||||
public function inputIsProperlyParsed()
|
||||
{
|
||||
$this->configWriter->toFile(Argument::any(), [
|
||||
'app_options' => [
|
||||
@@ -81,6 +83,11 @@ CLI_INPUT
|
||||
'dbname' => 'shlink_db',
|
||||
'user' => 'alejandro',
|
||||
'password' => '1234',
|
||||
'host' => 'localhost',
|
||||
'port' => '3306',
|
||||
'driverOptions' => [
|
||||
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
]
|
||||
],
|
||||
],
|
||||
'translator' => [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Shortcode;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Shortcode\GeneratePreviewCommand;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Shortcode;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Shortcode\GenerateShortcodeCommand;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Shortcode;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Shortcode\GetVisitsCommand;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Shortcode;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Shortcode\ListShortcodesCommand;
|
||||
@@ -46,8 +46,8 @@ class ListShortcodesCommandTest extends TestCase
|
||||
public function noInputCallsListJustOnce()
|
||||
{
|
||||
$this->questionHelper->setInputStream($this->getInputStream('\n'));
|
||||
$this->shortUrlService->listShortUrls(1)->willReturn(new Paginator(new ArrayAdapter()))
|
||||
->shouldBeCalledTimes(1);
|
||||
$this->shortUrlService->listShortUrls(1, null, [], null)->willReturn(new Paginator(new ArrayAdapter()))
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute(['command' => 'shortcode:list']);
|
||||
}
|
||||
@@ -66,7 +66,11 @@ class ListShortcodesCommandTest extends TestCase
|
||||
|
||||
$questionHelper = $this->questionHelper;
|
||||
$that = $this;
|
||||
$this->shortUrlService->listShortUrls(Argument::any())->will(function () use (&$data, $questionHelper, $that) {
|
||||
$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);
|
||||
@@ -86,8 +90,8 @@ class ListShortcodesCommandTest extends TestCase
|
||||
}
|
||||
|
||||
$this->questionHelper->setInputStream($this->getInputStream('n'));
|
||||
$this->shortUrlService->listShortUrls(Argument::any())->willReturn(new Paginator(new ArrayAdapter($data)))
|
||||
->shouldBeCalledTimes(1);
|
||||
$this->shortUrlService->listShortUrls(Argument::cetera())->willReturn(new Paginator(new ArrayAdapter($data)))
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute(['command' => 'shortcode:list']);
|
||||
}
|
||||
@@ -99,8 +103,8 @@ class ListShortcodesCommandTest extends TestCase
|
||||
{
|
||||
$page = 5;
|
||||
$this->questionHelper->setInputStream($this->getInputStream('\n'));
|
||||
$this->shortUrlService->listShortUrls($page)->willReturn(new Paginator(new ArrayAdapter()))
|
||||
->shouldBeCalledTimes(1);
|
||||
$this->shortUrlService->listShortUrls($page, null, [], null)->willReturn(new Paginator(new ArrayAdapter()))
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'shortcode:list',
|
||||
@@ -114,12 +118,12 @@ class ListShortcodesCommandTest extends TestCase
|
||||
public function ifTagsFlagIsProvidedTagsColumnIsIncluded()
|
||||
{
|
||||
$this->questionHelper->setInputStream($this->getInputStream('\n'));
|
||||
$this->shortUrlService->listShortUrls(1)->willReturn(new Paginator(new ArrayAdapter()))
|
||||
->shouldBeCalledTimes(1);
|
||||
$this->shortUrlService->listShortUrls(1, null, [], null)->willReturn(new Paginator(new ArrayAdapter()))
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'shortcode:list',
|
||||
'--tags' => true,
|
||||
'--showTags' => true,
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$this->assertTrue(strpos($output, 'Tags') > 0);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Shortcode;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Shortcode\ResolveUrlCommand;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Visit;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Visit\ProcessVisitsCommand;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\CLI\ConfigProvider;
|
||||
|
||||
class ConfigProviderTest extends TestCase
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Factory;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\CLI\Factory\ApplicationFactory;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
use Shlinkio\Shlink\Common\Middleware;
|
||||
|
||||
return [
|
||||
|
||||
'middleware_pipeline' => [
|
||||
'pre-routing' => [
|
||||
'middleware' => [
|
||||
Middleware\LocaleMiddleware::class,
|
||||
],
|
||||
'priority' => 5,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -2,10 +2,11 @@
|
||||
namespace Shlinkio\Shlink\Common\Middleware;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Interop\Http\ServerMiddleware\MiddlewareInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
use Zend\Stratigility\MiddlewareInterface;
|
||||
|
||||
class LocaleMiddleware implements MiddlewareInterface
|
||||
{
|
||||
@@ -25,40 +26,26 @@ class LocaleMiddleware implements MiddlewareInterface
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Process an incoming request and/or response.
|
||||
*
|
||||
* Accepts a server-side request and a response instance, and does
|
||||
* something with them.
|
||||
*
|
||||
* If the response is not complete and/or further processing would not
|
||||
* interfere with the work done in the middleware, or if the middleware
|
||||
* wants to delegate to another process, it can use the `$out` callable
|
||||
* if present.
|
||||
*
|
||||
* If the middleware does not return a value, execution of the current
|
||||
* request is considered complete, and the response instance provided will
|
||||
* be considered the response to return.
|
||||
*
|
||||
* Alternately, the middleware may return a response instance.
|
||||
*
|
||||
* Often, middleware will `return $out();`, with the assumption that a
|
||||
* later middleware will return a response.
|
||||
* Process an incoming server request and return a response, optionally delegating
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param null|callable $out
|
||||
* @return null|Response
|
||||
* @param DelegateInterface $delegate
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function __invoke(Request $request, Response $response, callable $out = null)
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
{
|
||||
if (! $request->hasHeader('Accept-Language')) {
|
||||
return $out($request, $response);
|
||||
return $delegate->process($request);
|
||||
}
|
||||
|
||||
$locale = $request->getHeaderLine('Accept-Language');
|
||||
$this->translator->setLocale($this->normalizeLocale($locale));
|
||||
return $out($request, $response);
|
||||
return $delegate->process($request);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,19 +13,28 @@ class PaginableRepositoryAdapter implements AdapterInterface
|
||||
*/
|
||||
private $paginableRepository;
|
||||
/**
|
||||
* @var null
|
||||
* @var null|string
|
||||
*/
|
||||
private $searchTerm;
|
||||
/**
|
||||
* @var null
|
||||
* @var null|array|string
|
||||
*/
|
||||
private $orderBy;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $tags;
|
||||
|
||||
public function __construct(PaginableRepositoryInterface $paginableRepository, $searchTerm = null, $orderBy = null)
|
||||
{
|
||||
public function __construct(
|
||||
PaginableRepositoryInterface $paginableRepository,
|
||||
$searchTerm = null,
|
||||
array $tags = [],
|
||||
$orderBy = null
|
||||
) {
|
||||
$this->paginableRepository = $paginableRepository;
|
||||
$this->searchTerm = $searchTerm;
|
||||
$this->searchTerm = trim(strip_tags($searchTerm));
|
||||
$this->orderBy = $orderBy;
|
||||
$this->tags = $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +46,13 @@ class PaginableRepositoryAdapter implements AdapterInterface
|
||||
*/
|
||||
public function getItems($offset, $itemCountPerPage)
|
||||
{
|
||||
return $this->paginableRepository->findList($itemCountPerPage, $offset, $this->searchTerm, $this->orderBy);
|
||||
return $this->paginableRepository->findList(
|
||||
$itemCountPerPage,
|
||||
$offset,
|
||||
$this->searchTerm,
|
||||
$this->tags,
|
||||
$this->orderBy
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,6 +66,6 @@ class PaginableRepositoryAdapter implements AdapterInterface
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return $this->paginableRepository->countList($this->searchTerm);
|
||||
return $this->paginableRepository->countList($this->searchTerm, $this->tags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,16 +9,18 @@ interface PaginableRepositoryInterface
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
* @param string|null $searchTerm
|
||||
* @param array $tags
|
||||
* @param string|array|null $orderBy
|
||||
* @return array
|
||||
*/
|
||||
public function findList($limit = null, $offset = null, $searchTerm = null, $orderBy = null);
|
||||
public function findList($limit = null, $offset = null, $searchTerm = null, array $tags = [], $orderBy = null);
|
||||
|
||||
/**
|
||||
* Counts the number of elements in a list using provided filtering data
|
||||
*
|
||||
* @param null $searchTerm
|
||||
* @param array $tags
|
||||
* @return int
|
||||
*/
|
||||
public function countList($searchTerm = null);
|
||||
public function countList($searchTerm = null, array $tags = []);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\ConfigProvider;
|
||||
|
||||
class ConfigProviderTest extends TestCase
|
||||
@@ -23,7 +23,6 @@ class ConfigProviderTest extends TestCase
|
||||
{
|
||||
$config = $this->configProvider->__invoke();
|
||||
|
||||
$this->assertArrayHasKey('middleware_pipeline', $config);
|
||||
$this->assertArrayHasKey('dependencies', $config);
|
||||
$this->assertArrayHasKey('twig', $config);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Cache\FilesystemCache;
|
||||
use Doctrine\Common\Cache\MemcachedCache;
|
||||
use Doctrine\Common\Cache\RedisCache;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Factory\CacheFactory;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
namespace ShlinkioTest\Shlink\Common\Factory;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Factory\EntityManagerFactory;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
namespace ShlinkioTest\Shlink\Common\Factory;
|
||||
|
||||
use Monolog\Logger;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Common\Factory\LoggerFactory;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\Factory;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Factory\TranslatorFactory;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\Image;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Image\ImageBuilder;
|
||||
use Shlinkio\Shlink\Common\Image\ImageBuilderFactory;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
namespace ShlinkioTest\Shlink\Common\Image;
|
||||
|
||||
use mikehaertl\wkhtmlto\Image;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Image\ImageFactory;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\Middleware;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Middleware\LocaleMiddleware;
|
||||
use Zend\Diactoros\Response;
|
||||
use ShlinkioTest\Shlink\Common\Util\TestUtils;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
@@ -30,9 +30,7 @@ class LocaleMiddlewareTest extends TestCase
|
||||
public function whenNoHeaderIsPresentLocaleIsNotChanged()
|
||||
{
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
$this->middleware->__invoke(ServerRequestFactory::fromGlobals(), new Response(), function ($req, $resp) {
|
||||
return $resp;
|
||||
});
|
||||
$this->middleware->process(ServerRequestFactory::fromGlobals(), TestUtils::createDelegateMock()->reveal());
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
}
|
||||
|
||||
@@ -43,9 +41,7 @@ class LocaleMiddlewareTest extends TestCase
|
||||
{
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
$request = ServerRequestFactory::fromGlobals()->withHeader('Accept-Language', 'es');
|
||||
$this->middleware->__invoke($request, new Response(), function ($req, $resp) {
|
||||
return $resp;
|
||||
});
|
||||
$this->middleware->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$this->assertEquals('es', $this->translator->getLocale());
|
||||
}
|
||||
|
||||
@@ -54,18 +50,16 @@ class LocaleMiddlewareTest extends TestCase
|
||||
*/
|
||||
public function localeGetsNormalized()
|
||||
{
|
||||
$delegate = TestUtils::createDelegateMock();
|
||||
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withHeader('Accept-Language', 'es_ES');
|
||||
$this->middleware->__invoke($request, new Response(), function ($req, $resp) {
|
||||
return $resp;
|
||||
});
|
||||
$this->middleware->process($request, $delegate->reveal());
|
||||
$this->assertEquals('es', $this->translator->getLocale());
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withHeader('Accept-Language', 'en-US');
|
||||
$this->middleware->__invoke($request, new Response(), function ($req, $resp) {
|
||||
return $resp;
|
||||
});
|
||||
$this->middleware->process($request, $delegate->reveal());
|
||||
$this->assertEquals('en', $this->translator->getLocale());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\Paginator;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter;
|
||||
use Shlinkio\Shlink\Common\Repository\PaginableRepositoryInterface;
|
||||
@@ -20,7 +20,7 @@ class PaginableRepositoryAdapterTest extends TestCase
|
||||
public function setUp()
|
||||
{
|
||||
$this->repo = $this->prophesize(PaginableRepositoryInterface::class);
|
||||
$this->adapter = new PaginableRepositoryAdapter($this->repo->reveal(), 'search', 'order');
|
||||
$this->adapter = new PaginableRepositoryAdapter($this->repo->reveal(), 'search', ['foo', 'bar'], 'order');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,7 +28,7 @@ class PaginableRepositoryAdapterTest extends TestCase
|
||||
*/
|
||||
public function getItemsFallbacksToFindList()
|
||||
{
|
||||
$this->repo->findList(10, 5, 'search', 'order')->shouldBeCalledTimes(1);
|
||||
$this->repo->findList(10, 5, 'search', ['foo', 'bar'], 'order')->shouldBeCalledTimes(1);
|
||||
$this->adapter->getItems(5, 10);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class PaginableRepositoryAdapterTest extends TestCase
|
||||
*/
|
||||
public function countFallbacksToCountList()
|
||||
{
|
||||
$this->repo->countList('search')->shouldBeCalledTimes(1);
|
||||
$this->repo->countList('search', ['foo', 'bar'])->shouldBeCalledTimes(1);
|
||||
$this->adapter->count();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace ShlinkioTest\Shlink\Common\Service;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\TransferException;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Service\IpLocationResolver;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
namespace ShlinkioTest\Shlink\Common\Service;
|
||||
|
||||
use mikehaertl\wkhtmlto\Image;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Image\ImageBuilder;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\Twig\Extension;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Twig\Extension\TranslatorExtension;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\Util;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
|
||||
class DateRangeTest extends TestCase
|
||||
|
||||
35
module/Common/test/Util/TestUtils.php
Normal file
35
module/Common/test/Util/TestUtils.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\Util;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophet;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Zend\Diactoros\Response;
|
||||
|
||||
class TestUtils
|
||||
{
|
||||
private static $prophet;
|
||||
|
||||
public static function createDelegateMock(ResponseInterface $response = null, RequestInterface $request = null)
|
||||
{
|
||||
$argument = $request ?: Argument::any();
|
||||
$delegate = static::getProphet()->prophesize(DelegateInterface::class);
|
||||
$delegate->process($argument)->willReturn($response ?: new Response());
|
||||
|
||||
return $delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Prophet
|
||||
*/
|
||||
private static function getProphet()
|
||||
{
|
||||
if (static::$prophet === null) {
|
||||
static::$prophet = new Prophet();
|
||||
}
|
||||
|
||||
return static::$prophet;
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,16 @@
|
||||
namespace Shlinkio\Shlink\Core\Action;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Interop\Http\ServerMiddleware\MiddlewareInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
|
||||
use Shlinkio\Shlink\Common\Service\PreviewGenerator;
|
||||
use Shlinkio\Shlink\Common\Service\PreviewGeneratorInterface;
|
||||
use Shlinkio\Shlink\Common\Util\ResponseUtilsTrait;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Zend\Stratigility\MiddlewareInterface;
|
||||
|
||||
class PreviewAction implements MiddlewareInterface
|
||||
{
|
||||
@@ -40,46 +40,28 @@ class PreviewAction implements MiddlewareInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming request and/or response.
|
||||
*
|
||||
* Accepts a server-side request and a response instance, and does
|
||||
* something with them.
|
||||
*
|
||||
* If the response is not complete and/or further processing would not
|
||||
* interfere with the work done in the middleware, or if the middleware
|
||||
* wants to delegate to another process, it can use the `$out` callable
|
||||
* if present.
|
||||
*
|
||||
* If the middleware does not return a value, execution of the current
|
||||
* request is considered complete, and the response instance provided will
|
||||
* be considered the response to return.
|
||||
*
|
||||
* Alternately, the middleware may return a response instance.
|
||||
*
|
||||
* Often, middleware will `return $out();`, with the assumption that a
|
||||
* later middleware will return a response.
|
||||
* Process an incoming server request and return a response, optionally delegating
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param null|callable $out
|
||||
* @return null|Response
|
||||
* @param DelegateInterface $delegate
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function __invoke(Request $request, Response $response, callable $out = null)
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
{
|
||||
$shortCode = $request->getAttribute('shortCode');
|
||||
|
||||
try {
|
||||
$url = $this->urlShortener->shortCodeToUrl($shortCode);
|
||||
if (! isset($url)) {
|
||||
return $out($request, $response->withStatus(404), 'Not found');
|
||||
return $delegate->process($request);
|
||||
}
|
||||
|
||||
$imagePath = $this->previewGenerator->generatePreview($url);
|
||||
return $this->generateImageResponse($imagePath);
|
||||
} catch (InvalidShortCodeException $e) {
|
||||
return $out($request, $response->withStatus(404), 'Not found');
|
||||
} catch (PreviewGenerationException $e) {
|
||||
return $out($request, $response->withStatus(500), 'Preview generation error');
|
||||
return $delegate->process($request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ namespace Shlinkio\Shlink\Core\Action;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Endroid\QrCode\QrCode;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Interop\Http\ServerMiddleware\MiddlewareInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -12,7 +14,6 @@ use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Zend\Expressive\Router\RouterInterface;
|
||||
use Zend\Stratigility\MiddlewareInterface;
|
||||
|
||||
class QrCodeAction implements MiddlewareInterface
|
||||
{
|
||||
@@ -48,42 +49,26 @@ class QrCodeAction implements MiddlewareInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming request and/or response.
|
||||
*
|
||||
* Accepts a server-side request and a response instance, and does
|
||||
* something with them.
|
||||
*
|
||||
* If the response is not complete and/or further processing would not
|
||||
* interfere with the work done in the middleware, or if the middleware
|
||||
* wants to delegate to another process, it can use the `$out` callable
|
||||
* if present.
|
||||
*
|
||||
* If the middleware does not return a value, execution of the current
|
||||
* request is considered complete, and the response instance provided will
|
||||
* be considered the response to return.
|
||||
*
|
||||
* Alternately, the middleware may return a response instance.
|
||||
*
|
||||
* Often, middleware will `return $out();`, with the assumption that a
|
||||
* later middleware will return a response.
|
||||
* Process an incoming server request and return a response, optionally delegating
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param null|callable $out
|
||||
* @return null|Response
|
||||
* @param DelegateInterface $delegate
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function __invoke(Request $request, Response $response, callable $out = null)
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
{
|
||||
// Make sure the short URL exists for this short code
|
||||
$shortCode = $request->getAttribute('shortCode');
|
||||
try {
|
||||
$shortUrl = $this->urlShortener->shortCodeToUrl($shortCode);
|
||||
if (! isset($shortUrl)) {
|
||||
return $out($request, $response->withStatus(404), 'Not Found');
|
||||
if ($shortUrl === null) {
|
||||
return $delegate->process($request);
|
||||
}
|
||||
} catch (InvalidShortCodeException $e) {
|
||||
$this->logger->warning('Tried to create a QR code with an invalid short code' . PHP_EOL . $e);
|
||||
return $out($request, $response->withStatus(404), 'Not Found');
|
||||
return $delegate->process($request);
|
||||
}
|
||||
|
||||
$path = $this->router->generateUri('long-url-redirect', ['shortCode' => $shortCode]);
|
||||
@@ -101,13 +86,11 @@ class QrCodeAction implements MiddlewareInterface
|
||||
*/
|
||||
protected function getSizeParam(Request $request)
|
||||
{
|
||||
$size = intval($request->getAttribute('size', 300));
|
||||
$size = (int) $request->getAttribute('size', 300);
|
||||
if ($size < 50) {
|
||||
return 50;
|
||||
} elseif ($size > 1000) {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
return $size;
|
||||
return $size > 1000 ? 1000 : $size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
namespace Shlinkio\Shlink\Core\Action;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Interop\Http\ServerMiddleware\MiddlewareInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -11,7 +13,6 @@ use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Zend\Diactoros\Response\RedirectResponse;
|
||||
use Zend\Stratigility\MiddlewareInterface;
|
||||
|
||||
class RedirectAction implements MiddlewareInterface
|
||||
{
|
||||
@@ -47,31 +48,15 @@ class RedirectAction implements MiddlewareInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming request and/or response.
|
||||
*
|
||||
* Accepts a server-side request and a response instance, and does
|
||||
* something with them.
|
||||
*
|
||||
* If the response is not complete and/or further processing would not
|
||||
* interfere with the work done in the middleware, or if the middleware
|
||||
* wants to delegate to another process, it can use the `$out` callable
|
||||
* if present.
|
||||
*
|
||||
* If the middleware does not return a value, execution of the current
|
||||
* request is considered complete, and the response instance provided will
|
||||
* be considered the response to return.
|
||||
*
|
||||
* Alternately, the middleware may return a response instance.
|
||||
*
|
||||
* Often, middleware will `return $out();`, with the assumption that a
|
||||
* later middleware will return a response.
|
||||
* Process an incoming server request and return a response, optionally delegating
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param null|callable $out
|
||||
* @return null|Response
|
||||
* @param DelegateInterface $delegate
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function __invoke(Request $request, Response $response, callable $out = null)
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
{
|
||||
$shortCode = $request->getAttribute('shortCode', '');
|
||||
|
||||
@@ -80,8 +65,8 @@ class RedirectAction implements MiddlewareInterface
|
||||
|
||||
// If provided shortCode does not belong to a valid long URL, dispatch next middleware, which will trigger
|
||||
// a not-found error
|
||||
if (! isset($longUrl)) {
|
||||
return $this->notFoundResponse($request, $response, $out);
|
||||
if ($longUrl === null) {
|
||||
return $delegate->process($request);
|
||||
}
|
||||
|
||||
// Track visit to this short code
|
||||
@@ -93,18 +78,7 @@ class RedirectAction implements MiddlewareInterface
|
||||
} catch (\Exception $e) {
|
||||
// In case of error, dispatch 404 error
|
||||
$this->logger->error('Error redirecting to long URL.' . PHP_EOL . $e);
|
||||
return $this->notFoundResponse($request, $response, $out);
|
||||
return $delegate->process($request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param callable $out
|
||||
* @return Response
|
||||
*/
|
||||
protected function notFoundResponse(Request $request, Response $response, callable $out)
|
||||
{
|
||||
return $out($request, $response->withStatus(404), 'Not Found');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ namespace Shlinkio\Shlink\Core\Middleware;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Interop\Http\ServerMiddleware\MiddlewareInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Zend\Stratigility\MiddlewareInterface;
|
||||
use Zend\Diactoros\Response as DiactResp;
|
||||
|
||||
class QrCodeCacheMiddleware implements MiddlewareInterface
|
||||
{
|
||||
@@ -26,44 +28,29 @@ class QrCodeCacheMiddleware implements MiddlewareInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming request and/or response.
|
||||
*
|
||||
* Accepts a server-side request and a response instance, and does
|
||||
* something with them.
|
||||
*
|
||||
* If the response is not complete and/or further processing would not
|
||||
* interfere with the work done in the middleware, or if the middleware
|
||||
* wants to delegate to another process, it can use the `$out` callable
|
||||
* if present.
|
||||
*
|
||||
* If the middleware does not return a value, execution of the current
|
||||
* request is considered complete, and the response instance provided will
|
||||
* be considered the response to return.
|
||||
*
|
||||
* Alternately, the middleware may return a response instance.
|
||||
*
|
||||
* Often, middleware will `return $out();`, with the assumption that a
|
||||
* later middleware will return a response.
|
||||
* Process an incoming server request and return a response, optionally delegating
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param null|callable $out
|
||||
* @return null|Response
|
||||
* @param DelegateInterface $delegate
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function __invoke(Request $request, Response $response, callable $out = null)
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
{
|
||||
$cacheKey = $request->getUri()->getPath();
|
||||
|
||||
// If this QR code is already cached, just return it
|
||||
if ($this->cache->contains($cacheKey)) {
|
||||
$qrData = $this->cache->fetch($cacheKey);
|
||||
$response = new DiactResp();
|
||||
$response->getBody()->write($qrData['body']);
|
||||
return $response->withHeader('Content-Type', $qrData['content-type']);
|
||||
}
|
||||
|
||||
// If not, call the next middleware and cache it
|
||||
/** @var Response $resp */
|
||||
$resp = $out($request, $response);
|
||||
$resp = $delegate->process($request);
|
||||
$this->cache->save($cacheKey, [
|
||||
'body' => $resp->getBody()->__toString(),
|
||||
'content-type' => $resp->getHeaderLine('Content-Type'),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace Shlinkio\Shlink\Core\Repository;
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
|
||||
class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryInterface
|
||||
@@ -10,31 +11,55 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
* @param string|null $searchTerm
|
||||
* @param array $tags
|
||||
* @param string|array|null $orderBy
|
||||
* @return ShortUrl[]
|
||||
* @return \Shlinkio\Shlink\Core\Entity\ShortUrl[]
|
||||
*/
|
||||
public function findList($limit = null, $offset = null, $searchTerm = null, $orderBy = null)
|
||||
public function findList($limit = null, $offset = null, $searchTerm = null, array $tags = [], $orderBy = null)
|
||||
{
|
||||
$qb = $this->createQueryBuilder('s');
|
||||
$qb = $this->createListQueryBuilder($searchTerm, $tags);
|
||||
$qb->select('s');
|
||||
|
||||
if (isset($limit)) {
|
||||
// Set limit and offset
|
||||
if ($limit !== null) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
if (isset($offset)) {
|
||||
if ($offset !== null) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
if (isset($searchTerm)) {
|
||||
// TODO
|
||||
|
||||
// In case the ordering has been specified, the query could be more complex. Process it
|
||||
if ($orderBy !== null) {
|
||||
return $this->processOrderByForList($qb, $orderBy);
|
||||
}
|
||||
if (isset($orderBy)) {
|
||||
if (is_string($orderBy)) {
|
||||
$qb->orderBy($orderBy);
|
||||
} elseif (is_array($orderBy)) {
|
||||
$key = key($orderBy);
|
||||
$qb->orderBy($key, $orderBy[$key]);
|
||||
}
|
||||
} else {
|
||||
$qb->orderBy('s.dateCreated');
|
||||
|
||||
// With no order by, order by date and just return the list of ShortUrls
|
||||
$qb->orderBy('s.dateCreated');
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
protected function processOrderByForList(QueryBuilder $qb, $orderBy)
|
||||
{
|
||||
$fieldName = is_array($orderBy) ? key($orderBy) : $orderBy;
|
||||
$order = is_array($orderBy) ? $orderBy[$fieldName] : 'ASC';
|
||||
|
||||
if (in_array($fieldName, [
|
||||
'visits',
|
||||
'visitsCount',
|
||||
'visitCount',
|
||||
], true)) {
|
||||
$qb->addSelect('COUNT(v) AS totalVisits')
|
||||
->leftJoin('s.visits', 'v')
|
||||
->groupBy('s')
|
||||
->orderBy('totalVisits', $order);
|
||||
|
||||
return array_column($qb->getQuery()->getResult(), 0);
|
||||
} elseif (in_array($fieldName, [
|
||||
'originalUrl',
|
||||
'shortCode',
|
||||
'dateCreated',
|
||||
], true)) {
|
||||
$qb->orderBy('s.' . $fieldName, $order);
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
@@ -43,19 +68,51 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
|
||||
/**
|
||||
* Counts the number of elements in a list using provided filtering data
|
||||
*
|
||||
* @param null $searchTerm
|
||||
* @param null|string $searchTerm
|
||||
* @param array $tags
|
||||
* @return int
|
||||
*/
|
||||
public function countList($searchTerm = null)
|
||||
public function countList($searchTerm = null, array $tags = [])
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->select('COUNT(s)')
|
||||
->from(ShortUrl::class, 's');
|
||||
|
||||
if (isset($searchTerm)) {
|
||||
// TODO
|
||||
}
|
||||
$qb = $this->createListQueryBuilder($searchTerm, $tags);
|
||||
$qb->select('COUNT(s)');
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|string $searchTerm
|
||||
* @param array $tags
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
protected function createListQueryBuilder($searchTerm = null, array $tags = [])
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->from(ShortUrl::class, 's');
|
||||
$qb->where('1=1');
|
||||
|
||||
// Apply search term to every searchable field if not empty
|
||||
if (! empty($searchTerm)) {
|
||||
$qb->join('s.tags', 't');
|
||||
|
||||
$conditions = [
|
||||
$qb->expr()->like('s.originalUrl', ':searchPattern'),
|
||||
$qb->expr()->like('s.shortCode', ':searchPattern'),
|
||||
$qb->expr()->like('t.name', ':searchPattern'),
|
||||
];
|
||||
|
||||
// Unpack and apply search conditions
|
||||
$qb->andWhere($qb->expr()->orX(...$conditions));
|
||||
$searchTerm = '%' . $searchTerm . '%';
|
||||
$qb->setParameter('searchPattern', $searchTerm);
|
||||
}
|
||||
|
||||
// Filter by tags if provided
|
||||
if (! empty($tags)) {
|
||||
$qb->join('s.tags', 't')
|
||||
->andWhere($qb->expr()->in('t.name', $tags));
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,12 +29,6 @@ class VisitRepository extends EntityRepository implements VisitRepositoryInterfa
|
||||
$shortUrl = $shortUrl instanceof ShortUrl
|
||||
? $shortUrl
|
||||
: $this->getEntityManager()->find(ShortUrl::class, $shortUrl);
|
||||
if (! isset($dateRange) || $dateRange->isEmpty()) {
|
||||
$startDate = $shortUrl->getDateCreated();
|
||||
$endDate = clone $startDate;
|
||||
$endDate->add(new \DateInterval('P2D'));
|
||||
$dateRange = new DateRange($startDate, $endDate);
|
||||
}
|
||||
|
||||
$qb = $this->createQueryBuilder('v');
|
||||
$qb->where($qb->expr()->eq('v.shortUrl', ':shortUrl'))
|
||||
|
||||
@@ -32,13 +32,16 @@ class ShortUrlService implements ShortUrlServiceInterface
|
||||
|
||||
/**
|
||||
* @param int $page
|
||||
* @return Paginator|ShortUrl[]
|
||||
* @param string $searchQuery
|
||||
* @param array $tags
|
||||
* @param null $orderBy
|
||||
* @return ShortUrl[]|Paginator
|
||||
*/
|
||||
public function listShortUrls($page = 1)
|
||||
public function listShortUrls($page = 1, $searchQuery = null, array $tags = [], $orderBy = null)
|
||||
{
|
||||
/** @var ShortUrlRepository $repo */
|
||||
$repo = $this->em->getRepository(ShortUrl::class);
|
||||
$paginator = new Paginator(new PaginableRepositoryAdapter($repo));
|
||||
$paginator = new Paginator(new PaginableRepositoryAdapter($repo, $searchQuery, $tags, $orderBy));
|
||||
$paginator->setItemCountPerPage(PaginableRepositoryAdapter::ITEMS_PER_PAGE)
|
||||
->setCurrentPageNumber($page);
|
||||
|
||||
|
||||
@@ -9,9 +9,12 @@ interface ShortUrlServiceInterface
|
||||
{
|
||||
/**
|
||||
* @param int $page
|
||||
* @param string $searchQuery
|
||||
* @param array $tags
|
||||
* @param null $orderBy
|
||||
* @return ShortUrl[]|Paginator
|
||||
*/
|
||||
public function listShortUrls($page = 1);
|
||||
public function listShortUrls($page = 1, $searchQuery = null, array $tags = [], $orderBy = null);
|
||||
|
||||
/**
|
||||
* @param string $shortCode
|
||||
|
||||
@@ -117,7 +117,9 @@ class UrlShortener implements UrlShortenerInterface
|
||||
protected function checkUrlExists(UriInterface $url)
|
||||
{
|
||||
try {
|
||||
$this->httpClient->request('GET', $url);
|
||||
$this->httpClient->request('GET', $url, ['allow_redirects' => [
|
||||
'max' => 15,
|
||||
]]);
|
||||
} catch (GuzzleException $e) {
|
||||
throw InvalidUrlException::fromUrl($url, $e);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Core\Action;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
|
||||
use Shlinkio\Shlink\Common\Service\PreviewGenerator;
|
||||
use Shlinkio\Shlink\Core\Action\PreviewAction;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Zend\Diactoros\Response;
|
||||
use ShlinkioTest\Shlink\Common\Util\TestUtils;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
|
||||
class PreviewActionTest extends TestCase
|
||||
@@ -36,20 +37,17 @@ class PreviewActionTest extends TestCase
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function invalidShortCodeFallbacksToNextMiddlewareWithStatusNotFound()
|
||||
public function invalidShortCodeFallsBackToNextMiddleware()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn(null)->shouldBeCalledTimes(1);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate->process(Argument::cetera())->shouldBeCalledTimes(1);
|
||||
|
||||
$resp = $this->action->__invoke(
|
||||
$this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode),
|
||||
new Response(),
|
||||
function ($req, $resp) {
|
||||
return $resp;
|
||||
}
|
||||
$delegate->reveal()
|
||||
);
|
||||
|
||||
$this->assertEquals(404, $resp->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,9 +61,9 @@ class PreviewActionTest extends TestCase
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn($url)->shouldBeCalledTimes(1);
|
||||
$this->previewGenerator->generatePreview($url)->willReturn($path)->shouldBeCalledTimes(1);
|
||||
|
||||
$resp = $this->action->__invoke(
|
||||
$resp = $this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode),
|
||||
new Response()
|
||||
TestUtils::createDelegateMock()->reveal()
|
||||
);
|
||||
|
||||
$this->assertEquals(filesize($path), $resp->getHeaderLine('Content-length'));
|
||||
@@ -75,40 +73,18 @@ class PreviewActionTest extends TestCase
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function invalidShortcodeExceptionReturnsNotFound()
|
||||
public function invalidShortCodeExceptionFallsBackToNextMiddleware()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willThrow(InvalidShortCodeException::class)
|
||||
->shouldBeCalledTimes(1);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
|
||||
$resp = $this->action->__invoke(
|
||||
$this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode),
|
||||
new Response(),
|
||||
function ($req, $resp) {
|
||||
return $resp;
|
||||
}
|
||||
$delegate->reveal()
|
||||
);
|
||||
|
||||
$this->assertEquals(404, $resp->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function previewExceptionReturnsNotFound()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willThrow(PreviewGenerationException::class)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$resp = $this->action->__invoke(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode),
|
||||
new Response(),
|
||||
function ($req, $resp) {
|
||||
return $resp;
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertEquals(500, $resp->getStatusCode());
|
||||
$delegate->process(Argument::any())->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Core\Action;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Response\QrCodeResponse;
|
||||
@@ -9,7 +10,6 @@ use Shlinkio\Shlink\Core\Action\QrCodeAction;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
use Zend\Expressive\Router\RouterInterface;
|
||||
|
||||
@@ -37,19 +37,18 @@ class QrCodeActionTest extends TestCase
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function aNonexistentShortCodeWillReturnNotFoundResponse()
|
||||
public function aNotFoundShortCodeWillDelegateIntoNextMiddleware()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn(null)->shouldBeCalledTimes(1);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
|
||||
$resp = $this->action->__invoke(
|
||||
$this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode),
|
||||
new Response(),
|
||||
function ($req, $resp) {
|
||||
return $resp;
|
||||
}
|
||||
$delegate->reveal()
|
||||
);
|
||||
$this->assertEquals(404, $resp->getStatusCode());
|
||||
|
||||
$delegate->process(Argument::any())->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,15 +59,14 @@ class QrCodeActionTest extends TestCase
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willThrow(InvalidShortCodeException::class)
|
||||
->shouldBeCalledTimes(1);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
|
||||
$resp = $this->action->__invoke(
|
||||
$this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode),
|
||||
new Response(),
|
||||
function ($req, $resp) {
|
||||
return $resp;
|
||||
}
|
||||
$delegate->reveal()
|
||||
);
|
||||
$this->assertEquals(404, $resp->getStatusCode());
|
||||
|
||||
$delegate->process(Argument::any())->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,16 +76,15 @@ class QrCodeActionTest extends TestCase
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn(new ShortUrl())->shouldBeCalledTimes(1);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
|
||||
$resp = $this->action->__invoke(
|
||||
$resp = $this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode),
|
||||
new Response(),
|
||||
function ($req, $resp) {
|
||||
return $resp;
|
||||
}
|
||||
$delegate->reveal()
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(QrCodeResponse::class, $resp);
|
||||
$this->assertEquals(200, $resp->getStatusCode());
|
||||
$delegate->process(Argument::any())->shouldHaveBeenCalledTimes(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Core\Action;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Shlinkio\Shlink\Core\Action\RedirectAction;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
use ShlinkioTest\Shlink\Common\Util\TestUtils;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
|
||||
@@ -42,7 +42,7 @@ class RedirectActionTest extends TestCase
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode);
|
||||
$response = $this->action->__invoke($request, new Response());
|
||||
$response = $this->action->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
|
||||
$this->assertInstanceOf(Response\RedirectResponse::class, $response);
|
||||
$this->assertEquals(302, $response->getStatusCode());
|
||||
@@ -53,52 +53,32 @@ class RedirectActionTest extends TestCase
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function nextErrorMiddlewareIsInvokedIfLongUrlIsNotFound()
|
||||
public function nextMiddlewareIsInvokedIfLongUrlIsNotFound()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn(null)
|
||||
->shouldBeCalledTimes(1);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode);
|
||||
$originalResponse = new Response();
|
||||
$test = $this;
|
||||
$this->action->__invoke($request, $originalResponse, function (
|
||||
ServerRequestInterface $req,
|
||||
ResponseInterface $resp,
|
||||
$error
|
||||
) use (
|
||||
$test,
|
||||
$request
|
||||
) {
|
||||
$test->assertSame($request, $req);
|
||||
$test->assertEquals(404, $resp->getStatusCode());
|
||||
$test->assertEquals('Not Found', $error);
|
||||
});
|
||||
$this->action->process($request, $delegate->reveal());
|
||||
|
||||
$delegate->process($request)->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function nextErrorMiddlewareIsInvokedIfAnExceptionIsThrown()
|
||||
public function nextMiddlewareIsInvokedIfAnExceptionIsThrown()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willThrow(\Exception::class)
|
||||
->shouldBeCalledTimes(1);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode);
|
||||
$originalResponse = new Response();
|
||||
$test = $this;
|
||||
$this->action->__invoke($request, $originalResponse, function (
|
||||
ServerRequestInterface $req,
|
||||
ResponseInterface $resp,
|
||||
$error
|
||||
) use (
|
||||
$test,
|
||||
$request
|
||||
) {
|
||||
$test->assertSame($request, $req);
|
||||
$test->assertEquals(404, $resp->getStatusCode());
|
||||
$test->assertEquals('Not Found', $error);
|
||||
});
|
||||
$this->action->process($request, $delegate->reveal());
|
||||
|
||||
$delegate->process($request)->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Core;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Core\ConfigProvider;
|
||||
|
||||
class ConfigProviderTest extends TestCase
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Core\Entity;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||
|
||||
class TagTest extends TestCase
|
||||
|
||||
@@ -3,7 +3,9 @@ namespace ShlinkioTest\Shlink\Core\Middleware;
|
||||
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Shlinkio\Shlink\Core\Middleware\QrCodeCacheMiddleware;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
@@ -29,18 +31,16 @@ class QrCodeCacheMiddlewareTest extends TestCase
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function noCachedPathFallbacksToNextMiddleware()
|
||||
public function noCachedPathFallsBackToNextMiddleware()
|
||||
{
|
||||
$isCalled = false;
|
||||
$this->middleware->__invoke(
|
||||
ServerRequestFactory::fromGlobals(),
|
||||
new Response(),
|
||||
function ($req, $resp) use (&$isCalled) {
|
||||
$isCalled = true;
|
||||
return $resp;
|
||||
}
|
||||
);
|
||||
$this->assertTrue($isCalled);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate->process(Argument::any())->willReturn(new Response())->shouldBeCalledTimes(1);
|
||||
|
||||
$this->middleware->process(ServerRequestFactory::fromGlobals()->withUri(
|
||||
new Uri('/foo/bar')
|
||||
), $delegate->reveal());
|
||||
|
||||
$this->assertTrue($this->cache->contains('/foo/bar'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,19 +51,17 @@ class QrCodeCacheMiddlewareTest extends TestCase
|
||||
$isCalled = false;
|
||||
$uri = (new Uri())->withPath('/foo');
|
||||
$this->cache->save('/foo', ['body' => 'the body', 'content-type' => 'image/png']);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
|
||||
$resp = $this->middleware->__invoke(
|
||||
$resp = $this->middleware->process(
|
||||
ServerRequestFactory::fromGlobals()->withUri($uri),
|
||||
new Response(),
|
||||
function ($req, $resp) use (&$isCalled) {
|
||||
$isCalled = true;
|
||||
return $resp;
|
||||
}
|
||||
$delegate->reveal()
|
||||
);
|
||||
|
||||
$this->assertFalse($isCalled);
|
||||
$resp->getBody()->rewind();
|
||||
$this->assertEquals('the body', $resp->getBody()->getContents());
|
||||
$this->assertEquals('image/png', $resp->getHeaderLine('Content-Type'));
|
||||
$delegate->process(Argument::any())->shouldHaveBeenCalledTimes(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Core\Options;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptionsFactory;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace ShlinkioTest\Shlink\Core\Service;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
|
||||
@@ -10,7 +10,7 @@ use Doctrine\ORM\ORMException;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
namespace ShlinkioTest\Shlink\Core\Service;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Repository\VisitRepository;
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace ShlinkioTest\Shlink\Core\Service;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
|
||||
@@ -22,6 +22,7 @@ return [
|
||||
|
||||
Middleware\BodyParserMiddleware::class => AnnotatedFactory::class,
|
||||
Middleware\CrossDomainMiddleware::class => InvokableFactory::class,
|
||||
Middleware\PathVersionMiddleware::class => InvokableFactory::class,
|
||||
Middleware\CheckAuthenticationMiddleware::class => AnnotatedFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
use Shlinkio\Shlink\Rest\ErrorHandler\JsonErrorHandler;
|
||||
use Shlinkio\Shlink\Rest\ErrorHandler\JsonErrorResponseGenerator;
|
||||
|
||||
return [
|
||||
|
||||
'error_handler' => [
|
||||
'plugins' => [
|
||||
'invokables' => [
|
||||
'application/json' => JsonErrorHandler::class,
|
||||
'application/json' => JsonErrorResponseGenerator::class,
|
||||
],
|
||||
'aliases' => [
|
||||
'application/x-json' => 'application/json',
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
use Shlinkio\Shlink\Rest\Middleware;
|
||||
|
||||
return [
|
||||
|
||||
'middleware_pipeline' => [
|
||||
'rest' => [
|
||||
'path' => '/rest',
|
||||
'middleware' => [
|
||||
Middleware\CrossDomainMiddleware::class,
|
||||
Middleware\BodyParserMiddleware::class,
|
||||
Middleware\CheckAuthenticationMiddleware::class,
|
||||
],
|
||||
'priority' => 5,
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -6,37 +6,37 @@ return [
|
||||
'routes' => [
|
||||
[
|
||||
'name' => 'rest-authenticate',
|
||||
'path' => '/rest/authenticate',
|
||||
'path' => '/rest/v{version:1}/authenticate',
|
||||
'middleware' => Action\AuthenticateAction::class,
|
||||
'allowed_methods' => ['POST', 'OPTIONS'],
|
||||
],
|
||||
[
|
||||
'name' => 'rest-create-shortcode',
|
||||
'path' => '/rest/short-codes',
|
||||
'path' => '/rest/v{version:1}/short-codes',
|
||||
'middleware' => Action\CreateShortcodeAction::class,
|
||||
'allowed_methods' => ['POST', 'OPTIONS'],
|
||||
],
|
||||
[
|
||||
'name' => 'rest-resolve-url',
|
||||
'path' => '/rest/short-codes/{shortCode}',
|
||||
'path' => '/rest/v{version:1}/short-codes/{shortCode}',
|
||||
'middleware' => Action\ResolveUrlAction::class,
|
||||
'allowed_methods' => ['GET', 'OPTIONS'],
|
||||
],
|
||||
[
|
||||
'name' => 'rest-list-shortened-url',
|
||||
'path' => '/rest/short-codes',
|
||||
'path' => '/rest/v{version:1}/short-codes',
|
||||
'middleware' => Action\ListShortcodesAction::class,
|
||||
'allowed_methods' => ['GET'],
|
||||
],
|
||||
[
|
||||
'name' => 'rest-get-visits',
|
||||
'path' => '/rest/short-codes/{shortCode}/visits',
|
||||
'path' => '/rest/v{version:1}/short-codes/{shortCode}/visits',
|
||||
'middleware' => Action\GetVisitsAction::class,
|
||||
'allowed_methods' => ['GET', 'OPTIONS'],
|
||||
],
|
||||
[
|
||||
'name' => 'rest-edit-tags',
|
||||
'path' => '/rest/short-codes/{shortCode}/tags',
|
||||
'path' => '/rest/v{version:1}/short-codes/{shortCode}/tags',
|
||||
'middleware' => Action\EditTagsAction::class,
|
||||
'allowed_methods' => ['PUT', 'OPTIONS'],
|
||||
],
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
|
||||
use Fig\Http\Message\RequestMethodInterface;
|
||||
use Fig\Http\Message\StatusCodeInterface;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Interop\Http\ServerMiddleware\MiddlewareInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Zend\Stratigility\MiddlewareInterface;
|
||||
use Zend\Diactoros\Response\EmptyResponse;
|
||||
|
||||
abstract class AbstractRestAction implements MiddlewareInterface
|
||||
abstract class AbstractRestAction implements MiddlewareInterface, RequestMethodInterface, StatusCodeInterface
|
||||
{
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
@@ -20,44 +24,27 @@ abstract class AbstractRestAction implements MiddlewareInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming request and/or response.
|
||||
*
|
||||
* Accepts a server-side request and a response instance, and does
|
||||
* something with them.
|
||||
*
|
||||
* If the response is not complete and/or further processing would not
|
||||
* interfere with the work done in the middleware, or if the middleware
|
||||
* wants to delegate to another process, it can use the `$out` callable
|
||||
* if present.
|
||||
*
|
||||
* If the middleware does not return a value, execution of the current
|
||||
* request is considered complete, and the response instance provided will
|
||||
* be considered the response to return.
|
||||
*
|
||||
* Alternately, the middleware may return a response instance.
|
||||
*
|
||||
* Often, middleware will `return $out();`, with the assumption that a
|
||||
* later middleware will return a response.
|
||||
* Process an incoming server request and return a response, optionally delegating
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param null|callable $out
|
||||
* @return null|Response
|
||||
* @param DelegateInterface $delegate
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function __invoke(Request $request, Response $response, callable $out = null)
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
{
|
||||
if ($request->getMethod() === 'OPTIONS') {
|
||||
return $response;
|
||||
if ($request->getMethod() === self::METHOD_OPTIONS) {
|
||||
return new EmptyResponse();
|
||||
}
|
||||
|
||||
return $this->dispatch($request, $response, $out);
|
||||
return $this->dispatch($request, $delegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param callable|null $out
|
||||
* @param DelegateInterface $delegate
|
||||
* @return null|Response
|
||||
*/
|
||||
abstract protected function dispatch(Request $request, Response $response, callable $out = null);
|
||||
abstract protected function dispatch(Request $request, DelegateInterface $delegate);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Firebase\JWT\JWT;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Rest\Authentication\JWTService;
|
||||
use Shlinkio\Shlink\Rest\Authentication\JWTServiceInterface;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
|
||||
@@ -33,14 +34,17 @@ class AuthenticateAction extends AbstractRestAction
|
||||
* @param ApiKeyServiceInterface|ApiKeyService $apiKeyService
|
||||
* @param JWTServiceInterface|JWTService $jwtService
|
||||
* @param TranslatorInterface $translator
|
||||
* @param LoggerInterface|null $logger
|
||||
*
|
||||
* @Inject({ApiKeyService::class, JWTService::class, "translator"})
|
||||
* @Inject({ApiKeyService::class, JWTService::class, "translator", "Logger_Shlink"})
|
||||
*/
|
||||
public function __construct(
|
||||
ApiKeyServiceInterface $apiKeyService,
|
||||
JWTServiceInterface $jwtService,
|
||||
TranslatorInterface $translator
|
||||
TranslatorInterface $translator,
|
||||
LoggerInterface $logger = null
|
||||
) {
|
||||
parent::__construct($logger);
|
||||
$this->translator = $translator;
|
||||
$this->apiKeyService = $apiKeyService;
|
||||
$this->jwtService = $jwtService;
|
||||
@@ -48,11 +52,10 @@ class AuthenticateAction extends AbstractRestAction
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param callable|null $out
|
||||
* @param DelegateInterface $delegate
|
||||
* @return null|Response
|
||||
*/
|
||||
public function dispatch(Request $request, Response $response, callable $out = null)
|
||||
public function dispatch(Request $request, DelegateInterface $delegate)
|
||||
{
|
||||
$authData = $request->getParsedBody();
|
||||
if (! isset($authData['apiKey'])) {
|
||||
@@ -61,16 +64,16 @@ class AuthenticateAction extends AbstractRestAction
|
||||
'message' => $this->translator->translate(
|
||||
'You have to provide a valid API key under the "apiKey" param name.'
|
||||
),
|
||||
], 400);
|
||||
], self::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
// Authenticate using provided API key
|
||||
$apiKey = $this->apiKeyService->getByKey($authData['apiKey']);
|
||||
if (! $apiKey->isValid()) {
|
||||
if (! isset($apiKey) || ! $apiKey->isValid()) {
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::INVALID_API_KEY_ERROR,
|
||||
'message' => $this->translator->translate('Provided API key does not exist or is invalid.'),
|
||||
], 401);
|
||||
], self::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// Generate a JSON Web Token that will be used for authorization in next requests
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -52,18 +53,17 @@ class CreateShortcodeAction extends AbstractRestAction
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param callable|null $out
|
||||
* @param DelegateInterface $delegate
|
||||
* @return null|Response
|
||||
*/
|
||||
public function dispatch(Request $request, Response $response, callable $out = null)
|
||||
public function dispatch(Request $request, DelegateInterface $delegate)
|
||||
{
|
||||
$postData = $request->getParsedBody();
|
||||
if (! isset($postData['longUrl'])) {
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::INVALID_ARGUMENT_ERROR,
|
||||
'message' => $this->translator->translate('A URL was not provided'),
|
||||
], 400);
|
||||
], self::STATUS_BAD_REQUEST);
|
||||
}
|
||||
$longUrl = $postData['longUrl'];
|
||||
$tags = isset($postData['tags']) && is_array($postData['tags']) ? $postData['tags'] : [];
|
||||
@@ -87,13 +87,13 @@ class CreateShortcodeAction extends AbstractRestAction
|
||||
$this->translator->translate('Provided URL %s is invalid. Try with a different one.'),
|
||||
$longUrl
|
||||
),
|
||||
], 400);
|
||||
], self::STATUS_BAD_REQUEST);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Unexpected error creating shortcode.' . PHP_EOL . $e);
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::UNKNOWN_ERROR,
|
||||
'message' => $this->translator->translate('Unexpected error occurred'),
|
||||
], 500);
|
||||
], self::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -43,11 +44,10 @@ class EditTagsAction extends AbstractRestAction
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param callable|null $out
|
||||
* @param DelegateInterface $delegate
|
||||
* @return null|Response
|
||||
*/
|
||||
protected function dispatch(Request $request, Response $response, callable $out = null)
|
||||
protected function dispatch(Request $request, DelegateInterface $delegate)
|
||||
{
|
||||
$shortCode = $request->getAttribute('shortCode');
|
||||
$bodyParams = $request->getParsedBody();
|
||||
@@ -56,7 +56,7 @@ class EditTagsAction extends AbstractRestAction
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::INVALID_ARGUMENT_ERROR,
|
||||
'message' => $this->translator->translate('A list of tags was not provided'),
|
||||
], 400);
|
||||
], self::STATUS_BAD_REQUEST);
|
||||
}
|
||||
$tags = $bodyParams['tags'];
|
||||
|
||||
@@ -67,7 +67,7 @@ class EditTagsAction extends AbstractRestAction
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::getRestErrorCodeFromException($e),
|
||||
'message' => sprintf($this->translator->translate('No URL found for short code "%s"'), $shortCode),
|
||||
], 404);
|
||||
], self::STATUS_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -44,11 +45,10 @@ class GetVisitsAction extends AbstractRestAction
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param callable|null $out
|
||||
* @param DelegateInterface $delegate
|
||||
* @return null|Response
|
||||
*/
|
||||
public function dispatch(Request $request, Response $response, callable $out = null)
|
||||
public function dispatch(Request $request, DelegateInterface $delegate)
|
||||
{
|
||||
$shortCode = $request->getAttribute('shortCode');
|
||||
$startDate = $this->getDateQueryParam($request, 'startDate');
|
||||
@@ -70,13 +70,13 @@ class GetVisitsAction extends AbstractRestAction
|
||||
$this->translator->translate('Provided short code %s does not exist'),
|
||||
$shortCode
|
||||
),
|
||||
], 404);
|
||||
], self::STATUS_NOT_FOUND);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Unexpected error while parsing short code'. PHP_EOL . $e);
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::UNKNOWN_ERROR,
|
||||
'message' => $this->translator->translate('Unexpected error occurred'),
|
||||
], 500);
|
||||
], self::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -46,22 +47,35 @@ class ListShortcodesAction extends AbstractRestAction
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param callable|null $out
|
||||
* @param DelegateInterface $delegate
|
||||
* @return null|Response
|
||||
*/
|
||||
public function dispatch(Request $request, Response $response, callable $out = null)
|
||||
public function dispatch(Request $request, DelegateInterface $delegate)
|
||||
{
|
||||
try {
|
||||
$query = $request->getQueryParams();
|
||||
$shortUrls = $this->shortUrlService->listShortUrls(isset($query['page']) ? $query['page'] : 1);
|
||||
$params = $this->queryToListParams($request->getQueryParams());
|
||||
$shortUrls = $this->shortUrlService->listShortUrls(...$params);
|
||||
return new JsonResponse(['shortUrls' => $this->serializePaginator($shortUrls)]);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Unexpected error while listing short URLs.' . PHP_EOL . $e);
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::UNKNOWN_ERROR,
|
||||
'message' => $this->translator->translate('Unexpected error occurred'),
|
||||
], 500);
|
||||
], self::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $query
|
||||
* @return string
|
||||
*/
|
||||
public function queryToListParams(array $query)
|
||||
{
|
||||
return [
|
||||
isset($query['page']) ? $query['page'] : 1,
|
||||
isset($query['searchTerm']) ? $query['searchTerm'] : null,
|
||||
isset($query['tags']) ? $query['tags'] : [],
|
||||
isset($query['orderBy']) ? $query['orderBy'] : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user