mirror of
https://github.com/shlinkio/shlink.git
synced 2026-02-28 12:13:13 +08:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
975260f126 | ||
|
|
bd678b41f7 | ||
|
|
66898b6ddc | ||
|
|
5eee683978 | ||
|
|
e92446df9b | ||
|
|
63a69b05a1 | ||
|
|
c79ca1d13c | ||
|
|
87c4851d7e | ||
|
|
a125c93ca3 | ||
|
|
a8465094c1 | ||
|
|
16f7359ac6 | ||
|
|
f9f4817ee2 | ||
|
|
c7e49f223f | ||
|
|
6e79b4ba7b | ||
|
|
f78a7f12a9 | ||
|
|
b3664597b0 | ||
|
|
8cfb4f61ca | ||
|
|
b0dbb2dae4 | ||
|
|
7c6da4985d | ||
|
|
386b0dfb7b | ||
|
|
1437ff48ce | ||
|
|
63294f20ee | ||
|
|
d8acc3c247 | ||
|
|
52d8ffa212 | ||
|
|
98ad2816e8 | ||
|
|
9d890f4227 | ||
|
|
0932d04907 | ||
|
|
1f78b5c524 | ||
|
|
59f10619ba | ||
|
|
334710e92c | ||
|
|
75b8175824 | ||
|
|
8a74ef2a33 | ||
|
|
d05ac5ce9d | ||
|
|
3100fffa2b | ||
|
|
6bbacb1017 | ||
|
|
4403dc5df9 | ||
|
|
fdc637c23d | ||
|
|
b99d662417 | ||
|
|
eb9a964c66 | ||
|
|
e5ef8d7f8c | ||
|
|
28650aee2b | ||
|
|
a2294704e6 | ||
|
|
e5e1aa2ff4 | ||
|
|
2f5290b9d3 | ||
|
|
ef3c4aadf2 | ||
|
|
c9ce56eea5 | ||
|
|
4fee656f96 | ||
|
|
d2a04259f5 | ||
|
|
e504daa1ba | ||
|
|
8793a67ce9 | ||
|
|
b4ded374e9 | ||
|
|
91d350b12f | ||
|
|
b3e25f28fd | ||
|
|
aca89f9abe | ||
|
|
243075dd78 | ||
|
|
7130425896 | ||
|
|
fe9ab20cbb | ||
|
|
6935b2ebe2 | ||
|
|
3dcc510da1 | ||
|
|
2f26c82fa6 | ||
|
|
9ddb60a882 | ||
|
|
210b08b61f | ||
|
|
42fe4bd5ce | ||
|
|
1b2a0820e5 | ||
|
|
6cf0155417 | ||
|
|
9b8be3e5b8 | ||
|
|
a27b01b895 | ||
|
|
16dd1838aa | ||
|
|
f788d6872f | ||
|
|
d0df007812 |
@@ -5,7 +5,6 @@ branches:
|
||||
- /.*/
|
||||
|
||||
php:
|
||||
- 7
|
||||
- 7.1
|
||||
- 7.2
|
||||
|
||||
@@ -16,12 +15,10 @@ before_install:
|
||||
before_script:
|
||||
- composer self-update
|
||||
- composer install --no-interaction
|
||||
- if [[ $TRAVIS_PHP_VERSION = 7.1 ]] || [[ $TRAVIS_PHP_VERSION = 7.2 ]]; then composer global require --dev phpstan/phpstan:0.9.*; fi
|
||||
|
||||
script:
|
||||
- mkdir build
|
||||
- composer check
|
||||
- if [[ $TRAVIS_PHP_VERSION = 7.1 ]] || [[ $TRAVIS_PHP_VERSION = 7.2 ]]; then ~/.composer/vendor/bin/phpstan analyse module/*/src/ --level=6 -c phpstan.neon; fi
|
||||
|
||||
after_script:
|
||||
- vendor/bin/phpcov merge build --clover build/clover.xml
|
||||
|
||||
633
CHANGELOG.md
633
CHANGELOG.md
@@ -1,227 +1,580 @@
|
||||
## CHANGELOG
|
||||
# CHANGELOG
|
||||
|
||||
### 1.7.2
|
||||
## 1.10.0 - 2018-07-09
|
||||
|
||||
**Bugs:**
|
||||
#### Added
|
||||
|
||||
* [135: Fix PathVersionMiddleware being ignored when using expressive 2.2](https://github.com/shlinkio/shlink/issues/135)
|
||||
* [#161](https://github.com/shlinkio/shlink/issues/161) AddED support for shlink to be run with [swoole](https://www.swoole.co.uk/) via [zend-expressive-swoole](https://github.com/zendframework/zend-expressive-swoole) package
|
||||
|
||||
### 1.7.1
|
||||
#### Changed
|
||||
|
||||
**Enhancements:**
|
||||
* [#159](https://github.com/shlinkio/shlink/issues/159) Updated CHANGELOG to follow the [keep-a-changelog](https://keepachangelog.com) format
|
||||
* [#160](https://github.com/shlinkio/shlink/issues/160) Update infection to v0.9 and phpstan to v 0.10
|
||||
|
||||
* [128: Upgrade to expressive 2.2](https://github.com/shlinkio/shlink/issues/128)
|
||||
#### Deprecated
|
||||
|
||||
**Bugs**
|
||||
* *Nothing*
|
||||
|
||||
* [126: Expressive 2.2 causes failures by triggering E_USER_DEPRECATED errors](https://github.com/shlinkio/shlink/issues/126)
|
||||
#### Removed
|
||||
|
||||
### 1.7.0
|
||||
* *Nothing*
|
||||
|
||||
**Features**
|
||||
#### Fixed
|
||||
|
||||
* [88: Allow to disable tracking of the short URL by including a configurable query param](https://github.com/shlinkio/shlink/issues/88)
|
||||
* [108: Allow to edit metadata in created shortcodes](https://github.com/shlinkio/shlink/issues/108)
|
||||
* *Nothing*
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
* [113: Update CLI commands to use SymfonyStyle](https://github.com/shlinkio/shlink/issues/113)
|
||||
* [112: Configure cli commands lazy loading](https://github.com/shlinkio/shlink/issues/112)
|
||||
## 1.9.1 - 2018-06-18
|
||||
|
||||
**Tasks**
|
||||
#### Added
|
||||
|
||||
* [117: Make every module which throws exceptions have its own ExceptionInterface, and make them all extend Throwable](https://github.com/shlinkio/shlink/issues/117)
|
||||
* [115: Add phpstan to build matrix on PHP >=7.1 envs](https://github.com/shlinkio/shlink/issues/115)
|
||||
* [114: Replace vlucas/phpdotenv dev requirement by symfony/env](https://github.com/shlinkio/shlink/issues/114)
|
||||
* [#155](https://github.com/shlinkio/shlink/issues/155) Improved the pagination object returned in lists, including more meaningful properties.
|
||||
|
||||
* Old structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"pagination": {
|
||||
"currentPage": 1,
|
||||
"pagesCount": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* New structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"pagination": {
|
||||
"currentPage": 2,
|
||||
"pagesCount": 13,
|
||||
"itemsPerPage": 10,
|
||||
"itemsInCurrentPage": 10,
|
||||
"totalItems": 126
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.6.2
|
||||
#### Changed
|
||||
|
||||
**Bugs**
|
||||
* *Nothing*
|
||||
|
||||
* [109: Fix installation error due to typo in latest migration](https://github.com/shlinkio/shlink/issues/109)
|
||||
#### Deprecated
|
||||
|
||||
### 1.6.1
|
||||
* *Nothing*
|
||||
|
||||
**Tasks**
|
||||
#### Removed
|
||||
|
||||
* [110: Create gitattributes file to define files to be excluded from distributable package](https://github.com/shlinkio/shlink/issues/110)
|
||||
* *Nothing*
|
||||
|
||||
### 1.6.0
|
||||
#### Fixed
|
||||
|
||||
**Features**
|
||||
* [#154](https://github.com/shlinkio/shlink/issues/154) Fixed sizes of every result page when filtering by searchTerm
|
||||
* [#157](https://github.com/shlinkio/shlink/issues/157) Background commands executed by installation process now respect the originally used php binary
|
||||
|
||||
* [44: Consider allowing to set custom slugs instead of generating a short code](https://github.com/shlinkio/shlink/issues/44)
|
||||
* [47: Allow to limit short codes availability by date range](https://github.com/shlinkio/shlink/issues/47)
|
||||
* [48: Allow to limit the number of visits to a short code](https://github.com/shlinkio/shlink/issues/48)
|
||||
* [105: Added option to enable/disable URL validation by response status code.](https://github.com/shlinkio/shlink/pull/105)
|
||||
|
||||
**Enhancements:**
|
||||
## 1.9.0 - 2018-05-07
|
||||
|
||||
* [27: Add repository functional tests with dbunit](https://github.com/shlinkio/shlink/issues/27)
|
||||
* [86: Drop support for PHP 5](https://github.com/shlinkio/shlink/issues/86)
|
||||
* [101: Make actions just capture very specific exceptions, and let the ErrorHandler catch any other exception](https://github.com/shlinkio/shlink/issues/101)
|
||||
* [104: Use different templates for requested-short-code-does-not-exist and route-could-not-be-match](https://github.com/shlinkio/shlink/issues/104)
|
||||
#### Added
|
||||
|
||||
**Tasks**
|
||||
* [#147](https://github.com/shlinkio/shlink/issues/147) Allowed short URLs to be created on the fly using a single API request, including the API key in a query param.
|
||||
|
||||
* [99: Replace AnnotatedFactory by ConfigAbstractFactory](https://github.com/shlinkio/shlink/issues/99)
|
||||
* [100: Replace twig by plates](https://github.com/shlinkio/shlink/issues/100)
|
||||
* [102: Improve coding standards strictness](https://github.com/shlinkio/shlink/issues/102)
|
||||
This eases integration with third party services.
|
||||
|
||||
With this feature, a simple request to a URL like `https://doma.in/rest/v1/short-codes/shorten?apiKey=[YOUR_API_KEY]&longUrl=[URL_TO_BE_SHORTENED]` would return the shortened one in JSON or plain text format.
|
||||
|
||||
**Bugs**
|
||||
#### Changed
|
||||
|
||||
* [103: Make NotFoundDelegate return proper content types based on accepted content](https://github.com/shlinkio/shlink/issues/103)
|
||||
* *Nothing*
|
||||
|
||||
### 1.5.0
|
||||
#### Deprecated
|
||||
|
||||
**Enhancements:**
|
||||
* *Nothing*
|
||||
|
||||
* [95: Add tags CRUD to CLI](https://github.com/shlinkio/shlink/issues/95)
|
||||
* [59: Add tags CRUD to REST](https://github.com/shlinkio/shlink/issues/59)
|
||||
* [66: Allow to import certain information from older app directory when updating](https://github.com/shlinkio/shlink/issues/66)
|
||||
#### Removed
|
||||
|
||||
**Tasks**
|
||||
* *Nothing*
|
||||
|
||||
* [96: Add namespace to functions](https://github.com/shlinkio/shlink/issues/96)
|
||||
* [76: Add response examples to swagger docs](https://github.com/shlinkio/shlink/issues/76)
|
||||
* [93: Improve cross domain management by using the ImplicitOptionsMiddleware](https://github.com/shlinkio/shlink/issues/93)
|
||||
#### Fixed
|
||||
|
||||
**Bugs**
|
||||
* [#139](https://github.com/shlinkio/shlink/issues/139) Ensured all core actions log exceptions
|
||||
|
||||
* [92: Fix formatted dates, using an ISO compliant format](https://github.com/shlinkio/shlink/issues/92)
|
||||
|
||||
### 1.4.0
|
||||
## 1.8.1 - 2018-04-07
|
||||
|
||||
**Enhancements:**
|
||||
#### Added
|
||||
|
||||
* [89: Update to expressive 2](https://github.com/shlinkio/shlink/issues/89)
|
||||
* *Nothing*
|
||||
|
||||
### 1.3.1
|
||||
#### Changed
|
||||
|
||||
**Tasks**
|
||||
* [#141](https://github.com/shlinkio/shlink/issues/141) Removed workaround used in `PathVersionMiddleware`, since the bug in zend-stratigility has been fixed.
|
||||
|
||||
* [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)
|
||||
#### Deprecated
|
||||
|
||||
**Bugs**
|
||||
* *Nothing*
|
||||
|
||||
* [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)
|
||||
#### Removed
|
||||
|
||||
### 1.3.0
|
||||
* *Nothing*
|
||||
|
||||
**Enhancements:**
|
||||
#### Fixed
|
||||
|
||||
* [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)
|
||||
* [#140](https://github.com/shlinkio/shlink/issues/140) Fixed warning thrown during installation while trying to include doctrine script
|
||||
|
||||
**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.8.0 - 2018-03-29
|
||||
|
||||
### 1.2.2
|
||||
#### Added
|
||||
|
||||
**Bugs**
|
||||
* [#125](https://github.com/shlinkio/shlink/issues/125) Implemented a path which returns a 1px image instead of a redirection.
|
||||
|
||||
Useful to track emails. Just add an image pointing to a URL like `https://doma.in/abc123/track` to any email and an invisible image will be generated tracking every time the email is opened.
|
||||
|
||||
* [#132](https://github.com/shlinkio/shlink/issues/132) Added infection to improve tests
|
||||
|
||||
#### Changed
|
||||
|
||||
* [#130](https://github.com/shlinkio/shlink/issues/130) Updated to Expressive 3
|
||||
* [#137](https://github.com/shlinkio/shlink/issues/137) Updated symfony components to v4
|
||||
|
||||
#### Deprecated
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Removed
|
||||
|
||||
* [#131](https://github.com/shlinkio/shlink/issues/131) Dropped support for PHP 7
|
||||
|
||||
#### Fixed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
|
||||
## 1.7.2 - 2018-03-26
|
||||
|
||||
#### Added
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Changed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Deprecated
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Removed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Fixed
|
||||
|
||||
* [#135](https://github.com/shlinkio/shlink/issues/135) Fixed `PathVersionMiddleware` being ignored when using expressive 2.2
|
||||
|
||||
|
||||
## 1.7.1 - 2018-03-21
|
||||
|
||||
#### Added
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Changed
|
||||
|
||||
* [#128](https://github.com/shlinkio/shlink/issues/128) Upgraded to expressive 2.2
|
||||
|
||||
This will ease the upcoming update to expressive 3
|
||||
|
||||
#### Deprecated
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Removed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Fixed
|
||||
|
||||
* [#126](https://github.com/shlinkio/shlink/issues/126) Fixed `E_USER_DEPRECATED` errors triggered when using Expressive 2.2
|
||||
|
||||
|
||||
## 1.7.0 - 2018-01-21
|
||||
|
||||
#### Added
|
||||
|
||||
* [#88](https://github.com/shlinkio/shlink/issues/88) Allowed tracking of short URLs to be disabled by including a configurable query param
|
||||
* [#108](https://github.com/shlinkio/shlink/issues/108) Allowed metadata to be defined when creating short codes
|
||||
|
||||
#### Changed
|
||||
|
||||
* [#113](https://github.com/shlinkio/shlink/issues/113) Updated CLI commands to use `SymfonyStyle`
|
||||
* [#112](https://github.com/shlinkio/shlink/issues/112) Enabled Lazy loading in CLI commands
|
||||
* [#117](https://github.com/shlinkio/shlink/issues/117) Every module which throws exceptions has now its own `ExceptionInterface` extending `Throwable`
|
||||
* [#115](https://github.com/shlinkio/shlink/issues/115) Added phpstan to build matrix on PHP >=7.1 envs
|
||||
* [#114](https://github.com/shlinkio/shlink/issues/114) Replaced [vlucas/phpdotenv](https://github.com/vlucas/phpdotenv) dev requirement by [symfony/dotenv](https://github.com/symfony/dotenv)
|
||||
|
||||
#### Deprecated
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Removed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Fixed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
|
||||
## 1.6.2 - 2017-10-25
|
||||
|
||||
#### Added
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Changed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Deprecated
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Removed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Fixed
|
||||
|
||||
* [#109](https://github.com/shlinkio/shlink/issues/109) Fixed installation error due to typo in latest migration
|
||||
|
||||
|
||||
## 1.6.1 - 2017-10-24
|
||||
|
||||
#### Added
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Changed
|
||||
|
||||
* [#110](https://github.com/shlinkio/shlink/issues/110) Created `.gitattributes` file to define files to be excluded from distributable package
|
||||
|
||||
#### Deprecated
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Removed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Fixed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
|
||||
## 1.6.0 - 2017-10-23
|
||||
|
||||
#### Added
|
||||
|
||||
* [#44](https://github.com/shlinkio/shlink/issues/44) Now it is possible to set custom slugs for short URLs instead of using a generated short code
|
||||
* [#47](https://github.com/shlinkio/shlink/issues/47) Allowed to limit short URLs availability by date range
|
||||
* [#48](https://github.com/shlinkio/shlink/issues/48) Allowed to limit the number of visits to a short URL
|
||||
* [#105](https://github.com/shlinkio/shlink/pull/105) Added option to enable/disable URL validation by response status code
|
||||
|
||||
#### Changed
|
||||
|
||||
* [#27](https://github.com/shlinkio/shlink/issues/27) Added repository functional tests with dbunit
|
||||
* [#101](https://github.com/shlinkio/shlink/issues/101) Now specific actions just capture very specific exceptions, and let the `ErrorHandler` catch any other unhandled exception
|
||||
* [#104](https://github.com/shlinkio/shlink/issues/104) Used different templates for *requested-short-code-does-not-exist* and *route-could-not-be-match*
|
||||
* [#99](https://github.com/shlinkio/shlink/issues/99) Replaced usages of `AnnotatedFactory` by `ConfigAbstractFactory`
|
||||
* [#100](https://github.com/shlinkio/shlink/issues/100) Updated templates engine. Replaced twig by plates
|
||||
* [#102](https://github.com/shlinkio/shlink/issues/102) Improved coding standards strictness
|
||||
|
||||
#### Deprecated
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Removed
|
||||
|
||||
* [#86](https://github.com/shlinkio/shlink/issues/86) Dropped support for PHP 5
|
||||
|
||||
#### Fixed
|
||||
|
||||
* [#103](https://github.com/shlinkio/shlink/issues/103) `NotFoundDelegate` now returns proper content types based on accepted content
|
||||
|
||||
|
||||
## 1.5.0 - 2017-07-16
|
||||
|
||||
#### Added
|
||||
|
||||
* [#95](https://github.com/shlinkio/shlink/issues/95) Added tags CRUD to CLI
|
||||
* [#59](https://github.com/shlinkio/shlink/issues/59) Added tags CRUD to REST
|
||||
* [#66](https://github.com/shlinkio/shlink/issues/66) Allowed certain information to be imported from and older shlink instance directory when updating
|
||||
|
||||
#### Changed
|
||||
|
||||
* [#96](https://github.com/shlinkio/shlink/issues/96) Added namespace to functions
|
||||
* [#76](https://github.com/shlinkio/shlink/issues/76) Added response examples to swagger docs
|
||||
* [#93](https://github.com/shlinkio/shlink/issues/93) Improved cross domain management by using the `ImplicitOptionsMiddleware`
|
||||
|
||||
#### Deprecated
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Removed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Fixed
|
||||
|
||||
* [#92](https://github.com/shlinkio/shlink/issues/92) Fixed formatted dates, using an ISO compliant format
|
||||
|
||||
|
||||
## 1.4.0 - 2017-03-25
|
||||
|
||||
#### Added
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Changed
|
||||
|
||||
* [#89](https://github.com/shlinkio/shlink/issues/89) Updated to expressive 2
|
||||
|
||||
#### Deprecated
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Removed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Fixed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
|
||||
## 1.3.1 - 2017-01-22
|
||||
|
||||
#### Added
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Changed
|
||||
|
||||
* [#82](https://github.com/shlinkio/shlink/issues/82) Enabled `FastRoute` routes cache
|
||||
* [#85](https://github.com/shlinkio/shlink/issues/85) Updated year in license file
|
||||
* [#81](https://github.com/shlinkio/shlink/issues/81) Added docker containers config
|
||||
|
||||
#### Deprecated
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Removed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Fixed
|
||||
|
||||
* [#83](https://github.com/shlinkio/shlink/issues/83) Fixed short codes list: search in tags when filtering by query string
|
||||
* [#79](https://github.com/shlinkio/shlink/issues/79) Increased the number of followed redirects
|
||||
* [#75](https://github.com/shlinkio/shlink/issues/75) Applied `PathVersionMiddleware` only to rest routes defining it by configuration instead of code
|
||||
* [#77](https://github.com/shlinkio/shlink/issues/77) Allowed defining database server hostname and port
|
||||
|
||||
|
||||
## 1.3.0 - 2016-10-23
|
||||
|
||||
#### Added
|
||||
|
||||
* [#67](https://github.com/shlinkio/shlink/issues/67) Allowed to order the short codes list
|
||||
* [#60](https://github.com/shlinkio/shlink/issues/60) Accepted JSON requests in REST and used a body parser middleware to set the request's `parsedBody`
|
||||
* [#72](https://github.com/shlinkio/shlink/issues/72) When listing API keys from CLI, use yellow color for enabled keys that have expired
|
||||
* [#58](https://github.com/shlinkio/shlink/issues/58) Allowed to filter short URLs by tag
|
||||
* [#69](https://github.com/shlinkio/shlink/issues/69) Allowed to filter short URLs by text query
|
||||
* [#73](https://github.com/shlinkio/shlink/issues/73) Added tag-related endpoints to swagger file
|
||||
* [#63](https://github.com/shlinkio/shlink/issues/63) Added path versioning to REST API routes
|
||||
|
||||
#### Changed
|
||||
|
||||
* [#71](https://github.com/shlinkio/shlink/issues/71) Separated swagger docs into multiple files
|
||||
|
||||
#### Deprecated
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Removed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Fixed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
|
||||
## 1.2.2 - 2016-08-29
|
||||
|
||||
#### Added
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Changed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Deprecated
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Removed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Fixed
|
||||
|
||||
* Fixed minor bugs on CORS requests
|
||||
|
||||
### 1.2.1
|
||||
|
||||
**Bugs**
|
||||
## 1.2.1 - 2016-08-21
|
||||
|
||||
* [62: Fix cross-domain requests in REST API](https://github.com/shlinkio/shlink/issues/62)
|
||||
#### Added
|
||||
|
||||
### 1.2.0
|
||||
* *Nothing*
|
||||
|
||||
**Features**
|
||||
#### Changed
|
||||
|
||||
* [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)
|
||||
* *Nothing*
|
||||
|
||||
**Enhancements:**
|
||||
#### Deprecated
|
||||
|
||||
* [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)
|
||||
* *Nothing*
|
||||
|
||||
**Tasks**
|
||||
#### Removed
|
||||
|
||||
* [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)
|
||||
* *Nothing*
|
||||
|
||||
**Bugs**
|
||||
#### Fixed
|
||||
|
||||
* [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)
|
||||
* [#62](https://github.com/shlinkio/shlink/issues/62) Fixed cross-domain requests in REST API
|
||||
|
||||
### 1.1.0
|
||||
|
||||
**Features**
|
||||
## 1.2.0 - 2016-08-21
|
||||
|
||||
* [46: Define a route that returns a QR code representing the shortened URL](https://github.com/shlinkio/shlink/issues/46)
|
||||
#### Added
|
||||
|
||||
**Enhancements:**
|
||||
* [#45](https://github.com/shlinkio/shlink/issues/45) Allowed to define tags on short codes, to improve filtering and classification
|
||||
* [#7](https://github.com/shlinkio/shlink/issues/7) Added website previews while listing available URLs
|
||||
* [#57](https://github.com/shlinkio/shlink/issues/57) Added database migrations system to improve updating between versions
|
||||
* [#31](https://github.com/shlinkio/shlink/issues/31) Added support for other database management systems by improving the `EntityManager` factory
|
||||
* [#51](https://github.com/shlinkio/shlink/issues/51) Generated build process to create app package and ease distribution
|
||||
* [#38](https://github.com/shlinkio/shlink/issues/38) Defined installation script. It will request dynamic data on the fly so that there is no need to define env vars
|
||||
* [#55](https://github.com/shlinkio/shlink/issues/55) Created update script which does not try to create a new database
|
||||
|
||||
* [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)
|
||||
#### Changed
|
||||
|
||||
**Tasks**
|
||||
* [#54](https://github.com/shlinkio/shlink/issues/54) Added cache namespace to prevent name collisions with other apps in the same environment
|
||||
* [#29](https://github.com/shlinkio/shlink/issues/29) Used the [acelaya/ze-content-based-error-handler](https://github.com/acelaya/ze-content-based-error-handler) package instead of custom error handler implementation
|
||||
|
||||
* [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)
|
||||
#### Deprecated
|
||||
|
||||
**Bugs**
|
||||
* *Nothing*
|
||||
|
||||
* [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)
|
||||
#### Removed
|
||||
|
||||
### 1.0.0
|
||||
* *Nothing*
|
||||
|
||||
**Enhancements:**
|
||||
#### Fixed
|
||||
|
||||
* [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)
|
||||
* [#53](https://github.com/shlinkio/shlink/issues/53) Fixed entities database interoperability
|
||||
* [#52](https://github.com/shlinkio/shlink/issues/52) Added missing htaccess file for apache environments
|
||||
|
||||
**Tasks**
|
||||
|
||||
* [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)
|
||||
## 1.1.0 - 2016-08-09
|
||||
|
||||
**Bugs**
|
||||
#### Added
|
||||
|
||||
* [24: Prevent duplicated shortcodes errors because of the case insensitive behavior on MySQL](https://github.com/shlinkio/shlink/issues/24)
|
||||
* [#46](https://github.com/shlinkio/shlink/issues/46) Defined a route that returns a QR code representing the shortened URL.
|
||||
|
||||
### 0.2.0
|
||||
In order to get the QR code URL, use a pattern like `https://doma.in/abc123/qr-code`
|
||||
|
||||
**Enhancements:**
|
||||
* [#32](https://github.com/shlinkio/shlink/issues/32) Added support for other cache adapters by improving the Cache factory
|
||||
* [#14](https://github.com/shlinkio/shlink/issues/14) Added logger and enabled errors logging
|
||||
* [#13](https://github.com/shlinkio/shlink/issues/13) Improved REST authentication
|
||||
|
||||
* [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)
|
||||
#### Changed
|
||||
|
||||
**Tasks**
|
||||
* [#41](https://github.com/shlinkio/shlink/issues/41) Cached the "short code" => "URL" map to prevent extra DB hits
|
||||
* [#39](https://github.com/shlinkio/shlink/issues/39) Changed copyright from "Alejandro Celaya" to "Shlink" in error pages
|
||||
* [#42](https://github.com/shlinkio/shlink/issues/42) REST endpoints that need to find *something* now return a 404 when it is not found
|
||||
* [#35](https://github.com/shlinkio/shlink/issues/35) Updated CLI commands to use the same PHP namespace as the one used for the command name
|
||||
|
||||
* [5: Create CHANGELOG file](https://github.com/shlinkio/shlink/issues/5)
|
||||
#### Deprecated
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Removed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Fixed
|
||||
|
||||
* [#40](https://github.com/shlinkio/shlink/issues/40) Taken 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
|
||||
|
||||
|
||||
## 1.0.0 - 2016-08-01
|
||||
|
||||
#### Added
|
||||
|
||||
* [#33](https://github.com/shlinkio/shlink/issues/33) Created a command that generates a short code charset by randomizing the default one
|
||||
* [#23](https://github.com/shlinkio/shlink/issues/23) Translated application literals
|
||||
* [#21](https://github.com/shlinkio/shlink/issues/21) Allowed to filter visits by date range
|
||||
* [#4](https://github.com/shlinkio/shlink/issues/4) Added installation steps
|
||||
* [#12](https://github.com/shlinkio/shlink/issues/12) Improved code coverage
|
||||
|
||||
#### Changed
|
||||
|
||||
* [#15](https://github.com/shlinkio/shlink/issues/15) HTTP requests now return JSON/HTML responses for errors (4xx and 5xx) based on `Accept` header
|
||||
* [#22](https://github.com/shlinkio/shlink/issues/22) Now visits locations data is saved on a `visit_locations` table
|
||||
* [#20](https://github.com/shlinkio/shlink/issues/20) Injected cross domain headers in response only if the `Origin` header is present in the request
|
||||
* [#11](https://github.com/shlinkio/shlink/issues/11) Separated code into multiple modules
|
||||
* [#18](https://github.com/shlinkio/shlink/issues/18) Grouped routable middleware in an Action namespace
|
||||
* [#6](https://github.com/shlinkio/shlink/issues/6) Project no longer depends on [zendframework/zend-expressive-helpers](https://github.com/zendframework/zend-expressive-helpers) package
|
||||
* [#30](https://github.com/shlinkio/shlink/issues/30) Replaced the "services" first level config entry by "dependencies", in order to fulfill default Expressive naming
|
||||
* [#25](https://github.com/shlinkio/shlink/issues/25) Replaced "Middleware" suffix on routable middlewares by "Action"
|
||||
* [#19](https://github.com/shlinkio/shlink/issues/19) Changed the vendor and app namespace from `Acelaya\UrlShortener` to `Shlinkio\Shlink`
|
||||
|
||||
#### Deprecated
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Removed
|
||||
|
||||
* [#36](https://github.com/shlinkio/shlink/issues/36) Removed hhvm from the CI matrix since it doesn't support array constants and will fail
|
||||
|
||||
#### Fixed
|
||||
|
||||
* [#24](https://github.com/shlinkio/shlink/issues/24) Prevented duplicated short codes errors because of the case insensitive behavior on MySQL
|
||||
|
||||
|
||||
## 0.2.0 - 2016-08-01
|
||||
|
||||
#### Added
|
||||
|
||||
* [#8](https://github.com/shlinkio/shlink/issues/8) Created a REST API
|
||||
* [#10](https://github.com/shlinkio/shlink/issues/10) Added more CLI functionality
|
||||
* [#5](https://github.com/shlinkio/shlink/issues/5) Created a CHANGELOG file
|
||||
|
||||
#### Changed
|
||||
|
||||
* [#9](https://github.com/shlinkio/shlink/issues/9) Used [symfony/console](https://github.com/symfony/console) to dispatch console requests, instead of trying to integrate the process with expressive
|
||||
|
||||
#### Deprecated
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Removed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
#### Fixed
|
||||
|
||||
* *Nothing*
|
||||
|
||||
4
build.sh
4
build.sh
@@ -33,8 +33,12 @@ rm composer.*
|
||||
rm LICENSE
|
||||
rm indocker
|
||||
rm docker-compose.yml
|
||||
rm docker-compose.override.yml
|
||||
rm docker-compose.override.yml.dist
|
||||
rm func_tests_bootstrap.php
|
||||
rm php*
|
||||
rm README.md
|
||||
rm infection.json
|
||||
rm -rf build
|
||||
rm -ff data/database.sqlite
|
||||
rm -rf data/infra
|
||||
|
||||
@@ -12,33 +12,29 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.0",
|
||||
"acelaya/ze-content-based-error-handler": "^2.0",
|
||||
"php": "^7.1",
|
||||
"acelaya/ze-content-based-error-handler": "^2.2",
|
||||
"cocur/slugify": "^3.0",
|
||||
"doctrine/annotations": "^1.4",
|
||||
"doctrine/cache": "^1.6",
|
||||
"doctrine/collections": "^1.4",
|
||||
"doctrine/common": "^2.7",
|
||||
"doctrine/dbal": "^2.5",
|
||||
"doctrine/migrations": "^1.4",
|
||||
"doctrine/orm": "^2.5",
|
||||
"endroid/qrcode": "^1.7",
|
||||
"endroid/qr-code": "^1.7",
|
||||
"firebase/php-jwt": "^4.0",
|
||||
"guzzlehttp/guzzle": "^6.2",
|
||||
"http-interop/http-middleware": "^0.4.1",
|
||||
"mikehaertl/phpwkhtmltopdf": "^2.2",
|
||||
"monolog/monolog": "^1.21",
|
||||
"roave/security-advisories": "dev-master",
|
||||
"symfony/console": "^3.4",
|
||||
"symfony/filesystem": "^3.0",
|
||||
"symfony/process": "^3.0",
|
||||
"symfony/console": "^4.0",
|
||||
"symfony/filesystem": "^4.0",
|
||||
"symfony/process": "^4.0",
|
||||
"theorchard/monolog-cascade": "^0.4",
|
||||
"zendframework/zend-config": "^3.0",
|
||||
"zendframework/zend-config-aggregator": "^1.0",
|
||||
"zendframework/zend-expressive": "^2.0",
|
||||
"zendframework/zend-expressive-fastroute": "^2.0",
|
||||
"zendframework/zend-expressive-helpers": "^4.2",
|
||||
"zendframework/zend-expressive-platesrenderer": "^1.3",
|
||||
"zendframework/zend-diactoros": "^1.7",
|
||||
"zendframework/zend-expressive": "^3.0",
|
||||
"zendframework/zend-expressive-fastroute": "^3.0",
|
||||
"zendframework/zend-expressive-helpers": "^5.0",
|
||||
"zendframework/zend-expressive-platesrenderer": "^2.0",
|
||||
"zendframework/zend-i18n": "^2.7",
|
||||
"zendframework/zend-inputfilter": "^2.8",
|
||||
"zendframework/zend-paginator": "^2.6",
|
||||
@@ -47,14 +43,16 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"filp/whoops": "^2.0",
|
||||
"phpunit/dbunit": "^3.0",
|
||||
"phpunit/phpcov": "^4.0",
|
||||
"phpunit/phpunit": "^6.0",
|
||||
"infection/infection": "^0.9.0",
|
||||
"phpstan/phpstan": "^0.10.0",
|
||||
"phpunit/phpcov": "^5.0",
|
||||
"phpunit/phpunit": "^7.0",
|
||||
"slevomat/coding-standard": "^4.0",
|
||||
"squizlabs/php_codesniffer": "^3.1 <3.2",
|
||||
"symfony/dotenv": "^3.4",
|
||||
"symfony/var-dumper": "^3.0",
|
||||
"zendframework/zend-expressive-tooling": "^0.4"
|
||||
"squizlabs/php_codesniffer": "^3.2.3",
|
||||
"symfony/dotenv": "^4.0",
|
||||
"symfony/var-dumper": "^4.0",
|
||||
"zendframework/zend-component-installer": "^2.1",
|
||||
"zendframework/zend-expressive-tooling": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -84,8 +82,10 @@
|
||||
"scripts": {
|
||||
"check": [
|
||||
"@cs",
|
||||
"@stan",
|
||||
"@test",
|
||||
"@func-test"
|
||||
"@func-test",
|
||||
"@infect"
|
||||
],
|
||||
"cs": "phpcs",
|
||||
"cs-fix": "phpcbf",
|
||||
@@ -97,13 +97,17 @@
|
||||
"@test",
|
||||
"@func-test",
|
||||
"phpcov merge build --html build/html"
|
||||
]
|
||||
],
|
||||
"stan": "phpstan analyse module/*/src/ --level=6 -c phpstan.neon",
|
||||
"infect": "infection --threads=4 --min-msi=55 --only-covered --log-verbosity=2",
|
||||
"infect-show": "infection --threads=4 --min-msi=55 --only-covered --log-verbosity=2 --show-mutations",
|
||||
"expressive": "expressive"
|
||||
},
|
||||
"config": {
|
||||
"process-timeout": 0,
|
||||
"sort-packages": true,
|
||||
"platform": {
|
||||
"php": "7.0.8"
|
||||
"php": "7.1.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,30 +5,23 @@ use Shlinkio\Shlink\Common\Factory\EmptyResponseImplicitOptionsMiddlewareFactory
|
||||
use Zend\Expressive;
|
||||
use Zend\Expressive\Container;
|
||||
use Zend\Expressive\Helper;
|
||||
use Zend\Expressive\Middleware;
|
||||
use Zend\Expressive\Plates;
|
||||
use Zend\Expressive\Router;
|
||||
use Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware;
|
||||
use Zend\Expressive\Template;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
use Zend\Stratigility\Middleware\ErrorHandler;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
Expressive\Application::class => Container\ApplicationFactory::class,
|
||||
Template\TemplateRendererInterface::class => Plates\PlatesRendererFactory::class,
|
||||
Router\RouterInterface::class => Router\FastRouteRouterFactory::class,
|
||||
ErrorHandler::class => Container\ErrorHandlerFactory::class,
|
||||
ImplicitOptionsMiddleware::class => EmptyResponseImplicitOptionsMiddlewareFactory::class,
|
||||
|
||||
Helper\UrlHelper::class => Helper\UrlHelperFactory::class,
|
||||
Helper\ServerUrlHelper::class => InvokableFactory::class,
|
||||
],
|
||||
|
||||
'aliases' => [
|
||||
Middleware\ImplicitOptionsMiddleware::class => ImplicitOptionsMiddleware::class,
|
||||
'delegators' => [
|
||||
Expressive\Application::class => [
|
||||
Container\ApplicationConfigInjectionDelegator::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Common\Middleware\LocaleMiddleware;
|
||||
use Shlinkio\Shlink\Core\Response\NotFoundHandler;
|
||||
use Shlinkio\Shlink\Rest\Middleware\BodyParserMiddleware;
|
||||
use Shlinkio\Shlink\Rest\Middleware\CheckAuthenticationMiddleware;
|
||||
use Shlinkio\Shlink\Rest\Middleware\CrossDomainMiddleware;
|
||||
@@ -15,12 +16,13 @@ return [
|
||||
'pre-routing' => [
|
||||
'middleware' => [
|
||||
ErrorHandler::class,
|
||||
Expressive\Helper\ContentLengthMiddleware::class,
|
||||
LocaleMiddleware::class,
|
||||
],
|
||||
'priority' => 11,
|
||||
],
|
||||
'pre-routing-rest' => [
|
||||
// 'path' => '/rest',
|
||||
'path' => '/rest',
|
||||
'middleware' => [
|
||||
PathVersionMiddleware::class,
|
||||
],
|
||||
@@ -48,6 +50,7 @@ return [
|
||||
'post-routing' => [
|
||||
'middleware' => [
|
||||
Expressive\Router\Middleware\DispatchMiddleware::class,
|
||||
NotFoundHandler::class,
|
||||
],
|
||||
'priority' => 1,
|
||||
],
|
||||
|
||||
@@ -7,6 +7,7 @@ use Shlinkio\Shlink\Common;
|
||||
use Shlinkio\Shlink\Core;
|
||||
use Shlinkio\Shlink\Rest;
|
||||
use Zend\ConfigAggregator;
|
||||
use Zend\Expressive;
|
||||
|
||||
/**
|
||||
* Configuration files are loaded in a specific order. First ``global.php``, then ``*.global.php``.
|
||||
@@ -18,8 +19,14 @@ use Zend\ConfigAggregator;
|
||||
*/
|
||||
|
||||
return (new ConfigAggregator\ConfigAggregator([
|
||||
Zend\Expressive\ConfigProvider::class,
|
||||
Zend\Expressive\Router\ConfigProvider::class,
|
||||
Expressive\ConfigProvider::class,
|
||||
Expressive\Router\ConfigProvider::class,
|
||||
Expressive\Router\FastRouteRouter\ConfigProvider::class,
|
||||
Expressive\Plates\ConfigProvider::class,
|
||||
Expressive\Helper\ConfigProvider::class,
|
||||
\class_exists(Expressive\Swoole\ConfigProvider::class)
|
||||
? Expressive\Swoole\ConfigProvider::class
|
||||
: new ConfigAggregator\ArrayProvider([]),
|
||||
ExpressiveErrorHandler\ConfigProvider::class,
|
||||
Common\ConfigProvider::class,
|
||||
Core\ConfigProvider::class,
|
||||
|
||||
0
data/infra/database/.gitignore
vendored
Normal file → Executable file
0
data/infra/database/.gitignore
vendored
Normal file → Executable file
0
data/infra/nginx/.gitignore
vendored
Normal file → Executable file
0
data/infra/nginx/.gitignore
vendored
Normal file → Executable file
@@ -3,11 +3,23 @@
|
||||
"properties": {
|
||||
"currentPage": {
|
||||
"type": "integer",
|
||||
"description": "The number of current page being displayed."
|
||||
"description": "The number of current page."
|
||||
},
|
||||
"pagesCount": {
|
||||
"type": "integer",
|
||||
"description": "The total number of pages that can be displayed."
|
||||
"description": "The total number of pages that can be obtained."
|
||||
},
|
||||
"itemsPerPage": {
|
||||
"type": "integer",
|
||||
"description": "The number of items for every page."
|
||||
},
|
||||
"itemsInCurrentPage": {
|
||||
"type": "integer",
|
||||
"description": "The number of items in current page (could be smaller than itemsPerPage)."
|
||||
},
|
||||
"totalItems": {
|
||||
"type": "integer",
|
||||
"description": "The total number of items among all pages."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,10 @@
|
||||
],
|
||||
"pagination": {
|
||||
"currentPage": 5,
|
||||
"pagesCount": 12
|
||||
"pagesCount": 12,
|
||||
"itemsPerPage": 10,
|
||||
"itemsInCurrentPage": 10,
|
||||
"totalItems": 115
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
125
docs/swagger/paths/v1_short-codes_shorten.json
Normal file
125
docs/swagger/paths/v1_short-codes_shorten.json
Normal file
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"get": {
|
||||
"tags": [
|
||||
"ShortCodes"
|
||||
],
|
||||
"summary": "Create a short URL",
|
||||
"description": "Creates a short URL in a single API call. Useful for third party integrations",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "apiKey",
|
||||
"in": "query",
|
||||
"description": "The API key used to authenticate the request",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "longUrl",
|
||||
"in": "query",
|
||||
"description": "The URL to be shortened",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "format",
|
||||
"in": "query",
|
||||
"description": "The format in which you want the response to be returned. You can also use the \"Accept\" header instead of this",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"txt",
|
||||
"json"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The list of short URLs",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"longUrl": {
|
||||
"type": "string",
|
||||
"description": "The original long URL that has been shortened"
|
||||
},
|
||||
"shortUrl": {
|
||||
"type": "string",
|
||||
"description": "The generated short URL"
|
||||
},
|
||||
"shortCode": {
|
||||
"type": "string",
|
||||
"description": "the short code that is being used in the short URL"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"longUrl": "https://github.com/shlinkio/shlink",
|
||||
"shortUrl": "https://dom.ain/abc123",
|
||||
"shortCode": "abc123"
|
||||
},
|
||||
"text/plain": "https://dom.ain/abc123"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "The long URL was not provided or is invalid.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"error": "INVALID_URL",
|
||||
"message": "Provided URL foo is invalid. Try with a different one."
|
||||
},
|
||||
"text/plain": "INVALID_URL"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"error": "UNKNOWN_ERROR",
|
||||
"message": "Unexpected error occurred"
|
||||
},
|
||||
"text/plain": "UNKNOWN_ERROR"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,9 @@
|
||||
"/v1/short-codes": {
|
||||
"$ref": "paths/v1_short-codes.json"
|
||||
},
|
||||
"/v1/short-codes/shorten": {
|
||||
"$ref": "paths/v1_short-codes_shorten.json"
|
||||
},
|
||||
"/v1/short-codes/{shortCode}": {
|
||||
"$ref": "paths/v1_short-codes_{shortCode}.json"
|
||||
},
|
||||
|
||||
22
infection.json
Normal file
22
infection.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"source": {
|
||||
"directories": [
|
||||
"module/*/src"
|
||||
],
|
||||
"excludes": []
|
||||
},
|
||||
"timeout": 10,
|
||||
"logs": {
|
||||
"text": "build/infection/infection-log.txt",
|
||||
"summary": "build/infection/summary-log.txt",
|
||||
"debug": "build/infection/debug-log.txt"
|
||||
},
|
||||
"tmpDir": "build/infection/temp",
|
||||
"phpUnit": {
|
||||
"configDir": "."
|
||||
},
|
||||
"mutators": {
|
||||
"@default": true,
|
||||
"IdenticalEqual": false
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,11 @@ use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class ListKeysCommand extends Command
|
||||
{
|
||||
const NAME = 'api-key:list';
|
||||
private const ERROR_STRING_PATTERN = '<fg=red>%s</>';
|
||||
private const SUCCESS_STRING_PATTERN = '<info>%s</info>';
|
||||
private const WARNING_STRING_PATTERN = '<comment>%s</comment>';
|
||||
|
||||
public const NAME = 'api-key:list';
|
||||
|
||||
/**
|
||||
* @var ApiKeyServiceInterface
|
||||
@@ -55,59 +59,32 @@ class ListKeysCommand extends Command
|
||||
foreach ($list as $row) {
|
||||
$key = $row->getKey();
|
||||
$expiration = $row->getExpirationDate();
|
||||
$formatMethod = $this->determineFormatMethod($row);
|
||||
$messagePattern = $this->determineMessagePattern($row);
|
||||
|
||||
// Set columns for this row
|
||||
$rowData = [$formatMethod($key)];
|
||||
$rowData = [\sprintf($messagePattern, $key)];
|
||||
if (! $enabledOnly) {
|
||||
$rowData[] = $formatMethod($this->getEnabledSymbol($row));
|
||||
$rowData[] = \sprintf($messagePattern, $this->getEnabledSymbol($row));
|
||||
}
|
||||
$rowData[] = $expiration !== null ? $expiration->format(\DateTime::ATOM) : '-';
|
||||
|
||||
$rows[] = $rowData;
|
||||
}
|
||||
|
||||
$io->table(array_filter([
|
||||
$io->table(\array_filter([
|
||||
$this->translator->translate('Key'),
|
||||
! $enabledOnly ? $this->translator->translate('Is enabled') : null,
|
||||
$this->translator->translate('Expiration date'),
|
||||
]), $rows);
|
||||
}
|
||||
|
||||
private function determineFormatMethod(ApiKey $apiKey): callable
|
||||
private function determineMessagePattern(ApiKey $apiKey): string
|
||||
{
|
||||
if (! $apiKey->isEnabled()) {
|
||||
return [$this, 'getErrorString'];
|
||||
return self::ERROR_STRING_PATTERN;
|
||||
}
|
||||
|
||||
return $apiKey->isExpired() ? [$this, 'getWarningString'] : [$this, 'getSuccessString'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
private function getErrorString(string $value): string
|
||||
{
|
||||
return sprintf('<fg=red>%s</>', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
private function getSuccessString(string $value): string
|
||||
{
|
||||
return sprintf('<info>%s</info>', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
private function getWarningString(string $value): string
|
||||
{
|
||||
return sprintf('<comment>%s</comment>', $value);
|
||||
return $apiKey->isExpired() ? self::WARNING_STRING_PATTERN : self::SUCCESS_STRING_PATTERN;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Filesystem\Exception\IOException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Zend\Config\Writer\WriterInterface;
|
||||
|
||||
class InstallCommand extends Command
|
||||
@@ -48,6 +49,14 @@ class InstallCommand extends Command
|
||||
* @var bool
|
||||
*/
|
||||
private $isUpdate;
|
||||
/**
|
||||
* @var PhpExecutableFinder
|
||||
*/
|
||||
private $phpFinder;
|
||||
/**
|
||||
* @var string|bool
|
||||
*/
|
||||
private $phpBinary;
|
||||
|
||||
/**
|
||||
* InstallCommand constructor.
|
||||
@@ -55,22 +64,25 @@ class InstallCommand extends Command
|
||||
* @param Filesystem $filesystem
|
||||
* @param ConfigCustomizerManagerInterface $configCustomizers
|
||||
* @param bool $isUpdate
|
||||
* @param PhpExecutableFinder|null $phpFinder
|
||||
* @throws LogicException
|
||||
*/
|
||||
public function __construct(
|
||||
WriterInterface $configWriter,
|
||||
Filesystem $filesystem,
|
||||
ConfigCustomizerManagerInterface $configCustomizers,
|
||||
$isUpdate = false
|
||||
bool $isUpdate = false,
|
||||
PhpExecutableFinder $phpFinder = null
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->configWriter = $configWriter;
|
||||
$this->isUpdate = $isUpdate;
|
||||
$this->filesystem = $filesystem;
|
||||
$this->configCustomizers = $configCustomizers;
|
||||
$this->phpFinder = $phpFinder ?: new PhpExecutableFinder();
|
||||
}
|
||||
|
||||
public function configure()
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName('shlink:install')
|
||||
@@ -84,7 +96,7 @@ class InstallCommand extends Command
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
protected function execute(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
$this->io = new SymfonyStyle($input, $output);
|
||||
|
||||
@@ -133,8 +145,8 @@ class InstallCommand extends Command
|
||||
// If current command is not update, generate database
|
||||
if (! $this->isUpdate) {
|
||||
$this->io->write('Initializing database...');
|
||||
if (! $this->runCommand(
|
||||
'php vendor/bin/doctrine.php orm:schema-tool:create',
|
||||
if (! $this->runPhpCommand(
|
||||
'vendor/doctrine/orm/bin/doctrine.php orm:schema-tool:create',
|
||||
'Error generating database.',
|
||||
$output
|
||||
)) {
|
||||
@@ -144,8 +156,8 @@ class InstallCommand extends Command
|
||||
|
||||
// Run database migrations
|
||||
$this->io->write('Updating database...');
|
||||
if (! $this->runCommand(
|
||||
'php vendor/bin/doctrine-migrations migrations:migrate',
|
||||
if (! $this->runPhpCommand(
|
||||
'vendor/doctrine/migrations/bin/doctrine-migrations.php migrations:migrate',
|
||||
'Error updating database.',
|
||||
$output
|
||||
)) {
|
||||
@@ -154,8 +166,8 @@ class InstallCommand extends Command
|
||||
|
||||
// Generate proxies
|
||||
$this->io->write('Generating proxies...');
|
||||
if (! $this->runCommand(
|
||||
'php vendor/bin/doctrine.php orm:generate-proxies',
|
||||
if (! $this->runPhpCommand(
|
||||
'vendor/doctrine/orm/bin/doctrine.php orm:generate-proxies',
|
||||
'Error generating proxies.',
|
||||
$output
|
||||
)) {
|
||||
@@ -213,23 +225,27 @@ class InstallCommand extends Command
|
||||
* @throws LogicException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function runCommand($command, $errorMessage, OutputInterface $output): bool
|
||||
private function runPhpCommand($command, $errorMessage, OutputInterface $output): bool
|
||||
{
|
||||
if ($this->processHelper === null) {
|
||||
$this->processHelper = $this->getHelper('process');
|
||||
}
|
||||
|
||||
$process = $this->processHelper->run($output, $command);
|
||||
if ($this->phpBinary === null) {
|
||||
$this->phpBinary = $this->phpFinder->find(false) ?: 'php';
|
||||
}
|
||||
|
||||
$this->io->writeln('Running "' . sprintf('%s %s', $this->phpBinary, $command) . '"');
|
||||
$process = $this->processHelper->run($output, sprintf('%s %s', $this->phpBinary, $command));
|
||||
if ($process->isSuccessful()) {
|
||||
$this->io->writeln(' <info>Success!</info>');
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->io->isVerbose()) {
|
||||
return false;
|
||||
if (! $this->io->isVerbose()) {
|
||||
$this->io->error($errorMessage . ' Run this command with -vvv to see specific error info.');
|
||||
}
|
||||
|
||||
$this->io->error($errorMessage . ' Run this command with -vvv to see specific error info.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use Symfony\Component\Console\Helper\ProcessHelper;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Symfony\Component\Filesystem\Exception\IOException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Zend\Config\Writer\WriterInterface;
|
||||
|
||||
@@ -55,6 +56,9 @@ class InstallCommandTest extends TestCase
|
||||
$configCustomizers = $this->prophesize(ConfigCustomizerManagerInterface::class);
|
||||
$configCustomizers->get(Argument::cetera())->willReturn($configCustomizer->reveal());
|
||||
|
||||
$finder = $this->prophesize(PhpExecutableFinder::class);
|
||||
$finder->find(false)->willReturn('php');
|
||||
|
||||
$app = new Application();
|
||||
$helperSet = $app->getHelperSet();
|
||||
$helperSet->set($processHelper->reveal());
|
||||
@@ -62,7 +66,9 @@ class InstallCommandTest extends TestCase
|
||||
$this->command = new InstallCommand(
|
||||
$this->configWriter->reveal(),
|
||||
$this->filesystem->reveal(),
|
||||
$configCustomizers->reveal()
|
||||
$configCustomizers->reveal(),
|
||||
false,
|
||||
$finder->reveal()
|
||||
);
|
||||
$app->add($this->command);
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ class EmptyResponseImplicitOptionsMiddlewareFactory implements FactoryInterface
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||
{
|
||||
return new ImplicitOptionsMiddleware(new EmptyResponse());
|
||||
return new ImplicitOptionsMiddleware(function () {
|
||||
return new EmptyResponse();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Middleware;
|
||||
|
||||
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\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface as DelegateInterface;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class LocaleMiddleware implements MiddlewareInterface
|
||||
@@ -32,15 +32,15 @@ class LocaleMiddleware implements MiddlewareInterface
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
public function process(Request $request, DelegateInterface $delegate): Response
|
||||
{
|
||||
if (! $request->hasHeader('Accept-Language')) {
|
||||
return $delegate->process($request);
|
||||
return $delegate->handle($request);
|
||||
}
|
||||
|
||||
$locale = $request->getHeaderLine('Accept-Language');
|
||||
$this->translator->setLocale($this->normalizeLocale($locale));
|
||||
return $delegate->process($request);
|
||||
return $delegate->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,7 @@ use Zend\Paginator\Adapter\AdapterInterface;
|
||||
|
||||
class PaginableRepositoryAdapter implements AdapterInterface
|
||||
{
|
||||
const ITEMS_PER_PAGE = 10;
|
||||
public const ITEMS_PER_PAGE = 10;
|
||||
|
||||
/**
|
||||
* @var PaginableRepositoryInterface
|
||||
@@ -34,7 +34,7 @@ class PaginableRepositoryAdapter implements AdapterInterface
|
||||
$orderBy = null
|
||||
) {
|
||||
$this->paginableRepository = $paginableRepository;
|
||||
$this->searchTerm = $searchTerm !== null ? trim(strip_tags($searchTerm)) : null;
|
||||
$this->searchTerm = $searchTerm !== null ? \trim(\strip_tags($searchTerm)) : null;
|
||||
$this->orderBy = $orderBy;
|
||||
$this->tags = $tags;
|
||||
}
|
||||
@@ -46,7 +46,7 @@ class PaginableRepositoryAdapter implements AdapterInterface
|
||||
* @param int $itemCountPerPage Number of items per page
|
||||
* @return array
|
||||
*/
|
||||
public function getItems($offset, $itemCountPerPage)
|
||||
public function getItems($offset, $itemCountPerPage): array
|
||||
{
|
||||
return $this->paginableRepository->findList(
|
||||
$itemCountPerPage,
|
||||
@@ -66,7 +66,7 @@ class PaginableRepositoryAdapter implements AdapterInterface
|
||||
* The return value is cast to an integer.
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function count()
|
||||
public function count(): int
|
||||
{
|
||||
return $this->paginableRepository->countList($this->searchTerm, $this->tags);
|
||||
}
|
||||
|
||||
@@ -8,13 +8,16 @@ use Zend\Stdlib\ArrayUtils;
|
||||
|
||||
trait PaginatorUtilsTrait
|
||||
{
|
||||
protected function serializePaginator(Paginator $paginator)
|
||||
protected function serializePaginator(Paginator $paginator): array
|
||||
{
|
||||
return [
|
||||
'data' => ArrayUtils::iteratorToArray($paginator->getCurrentItems()),
|
||||
'pagination' => [
|
||||
'currentPage' => $paginator->getCurrentPageNumber(),
|
||||
'pagesCount' => $paginator->count(),
|
||||
'itemsPerPage' => $paginator->getItemCountPerPage(),
|
||||
'itemsInCurrentPage' => $paginator->getCurrentItemCount(),
|
||||
'totalItems' => $paginator->getTotalItemCount(),
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -25,7 +28,7 @@ trait PaginatorUtilsTrait
|
||||
* @param Paginator $paginator
|
||||
* @return bool
|
||||
*/
|
||||
protected function isLastPage(Paginator $paginator)
|
||||
protected function isLastPage(Paginator $paginator): bool
|
||||
{
|
||||
return $paginator->getCurrentPageNumber() >= $paginator->count();
|
||||
}
|
||||
|
||||
33
module/Common/src/Response/PixelResponse.php
Normal file
33
module/Common/src/Response/PixelResponse.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Response;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\Stream;
|
||||
|
||||
class PixelResponse extends Response
|
||||
{
|
||||
private const BASE_64_IMAGE = 'R0lGODlhAQABAJAAAP8AAAAAACH5BAUQAAAALAAAAAABAAEAAAICBAEAOw==';
|
||||
private const CONTENT_TYPE = 'image/gif';
|
||||
|
||||
public function __construct(int $status = 200, array $headers = [])
|
||||
{
|
||||
$headers['content-type'] = self::CONTENT_TYPE;
|
||||
parent::__construct($this->createBody(), $status, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the message body.
|
||||
*
|
||||
* @return StreamInterface
|
||||
*/
|
||||
private function createBody(): StreamInterface
|
||||
{
|
||||
$body = new Stream('php://temp', 'wb+');
|
||||
$body->write((string) \base64_decode(self::BASE_64_IMAGE));
|
||||
$body->rewind();
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
@@ -3,40 +3,17 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\DbUnit;
|
||||
|
||||
use Doctrine\DBAL\Driver\PDOConnection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\DbUnit\Database\Connection as DbConn;
|
||||
use PHPUnit\DbUnit\DataSet\IDataSet as DataSet;
|
||||
use PHPUnit\DbUnit\TestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
abstract class DatabaseTestCase extends TestCase
|
||||
{
|
||||
const ENTITIES_TO_EMPTY = [];
|
||||
protected const ENTITIES_TO_EMPTY = [];
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
public static $em;
|
||||
/**
|
||||
* @var DbConn
|
||||
*/
|
||||
private static $conn;
|
||||
|
||||
public function getConnection(): DbConn
|
||||
{
|
||||
if (isset(self::$conn)) {
|
||||
return self::$conn;
|
||||
}
|
||||
|
||||
/** @var PDOConnection $pdo */
|
||||
$pdo = static::$em->getConnection()->getWrappedConnection();
|
||||
return self::$conn = $this->createDefaultDBConnection($pdo, static::$em->getConnection()->getDatabase());
|
||||
}
|
||||
|
||||
public function getDataSet(): DataSet
|
||||
{
|
||||
return $this->createArrayDataSet([]);
|
||||
}
|
||||
|
||||
protected function getEntityManager(): EntityManagerInterface
|
||||
{
|
||||
|
||||
@@ -38,8 +38,8 @@ class EmptyResponseImplicitOptionsMiddlewareFactoryTest extends TestCase
|
||||
$instance = $this->factory->__invoke(new ServiceManager(), '');
|
||||
|
||||
$ref = new \ReflectionObject($instance);
|
||||
$prop = $ref->getProperty('response');
|
||||
$prop = $ref->getProperty('responseFactory');
|
||||
$prop->setAccessible(true);
|
||||
$this->assertInstanceOf(EmptyResponse::class, $prop->getValue($instance));
|
||||
$this->assertInstanceOf(EmptyResponse::class, $prop->getValue($instance)());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class LocaleMiddlewareTest extends TestCase
|
||||
public function whenNoHeaderIsPresentLocaleIsNotChanged()
|
||||
{
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
$this->middleware->process(ServerRequestFactory::fromGlobals(), TestUtils::createDelegateMock()->reveal());
|
||||
$this->middleware->process(ServerRequestFactory::fromGlobals(), TestUtils::createReqHandlerMock()->reveal());
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class LocaleMiddlewareTest extends TestCase
|
||||
{
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
$request = ServerRequestFactory::fromGlobals()->withHeader('Accept-Language', 'es');
|
||||
$this->middleware->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$this->middleware->process($request, TestUtils::createReqHandlerMock()->reveal());
|
||||
$this->assertEquals('es', $this->translator->getLocale());
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class LocaleMiddlewareTest extends TestCase
|
||||
*/
|
||||
public function localeGetsNormalized()
|
||||
{
|
||||
$delegate = TestUtils::createDelegateMock();
|
||||
$delegate = TestUtils::createReqHandlerMock();
|
||||
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
|
||||
|
||||
@@ -3,22 +3,22 @@ declare(strict_types=1);
|
||||
|
||||
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 Psr\Http\Server\RequestHandlerInterface;
|
||||
use Zend\Diactoros\Response;
|
||||
|
||||
class TestUtils
|
||||
{
|
||||
private static $prophet;
|
||||
|
||||
public static function createDelegateMock(ResponseInterface $response = null, RequestInterface $request = null)
|
||||
public static function createReqHandlerMock(ResponseInterface $response = null, RequestInterface $request = null)
|
||||
{
|
||||
$argument = $request ?: Argument::any();
|
||||
$delegate = static::getProphet()->prophesize(DelegateInterface::class);
|
||||
$delegate->process($argument)->willReturn($response ?: new Response());
|
||||
$delegate = static::getProphet()->prophesize(RequestHandlerInterface::class);
|
||||
$delegate->handle($argument)->willReturn($response ?: new Response());
|
||||
|
||||
return $delegate;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use Shlinkio\Shlink\Common\Service\PreviewGenerator;
|
||||
use Shlinkio\Shlink\Core\Action;
|
||||
use Shlinkio\Shlink\Core\Middleware;
|
||||
use Shlinkio\Shlink\Core\Options;
|
||||
use Shlinkio\Shlink\Core\Response\NotFoundDelegate;
|
||||
use Shlinkio\Shlink\Core\Response\NotFoundHandler;
|
||||
use Shlinkio\Shlink\Core\Service;
|
||||
use Zend\Expressive\Router\RouterInterface;
|
||||
use Zend\Expressive\Template\TemplateRendererInterface;
|
||||
@@ -17,7 +17,7 @@ return [
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
Options\AppOptions::class => Options\AppOptionsFactory::class,
|
||||
NotFoundDelegate::class => ConfigAbstractFactory::class,
|
||||
NotFoundHandler::class => ConfigAbstractFactory::class,
|
||||
|
||||
// Services
|
||||
Service\UrlShortener::class => ConfigAbstractFactory::class,
|
||||
@@ -28,18 +28,15 @@ return [
|
||||
|
||||
// Middleware
|
||||
Action\RedirectAction::class => ConfigAbstractFactory::class,
|
||||
Action\PixelAction::class => ConfigAbstractFactory::class,
|
||||
Action\QrCodeAction::class => ConfigAbstractFactory::class,
|
||||
Action\PreviewAction::class => ConfigAbstractFactory::class,
|
||||
Middleware\QrCodeCacheMiddleware::class => ConfigAbstractFactory::class,
|
||||
],
|
||||
|
||||
'aliases' => [
|
||||
'Zend\Expressive\Delegate\DefaultDelegate' => NotFoundDelegate::class,
|
||||
],
|
||||
],
|
||||
|
||||
ConfigAbstractFactory::class => [
|
||||
NotFoundDelegate::class => [TemplateRendererInterface::class],
|
||||
NotFoundHandler::class => [TemplateRendererInterface::class],
|
||||
|
||||
// Services
|
||||
Service\UrlShortener::class => [
|
||||
@@ -59,9 +56,16 @@ return [
|
||||
Service\UrlShortener::class,
|
||||
Service\VisitsTracker::class,
|
||||
Options\AppOptions::class,
|
||||
'Logger_Shlink',
|
||||
],
|
||||
Action\PixelAction::class => [
|
||||
Service\UrlShortener::class,
|
||||
Service\VisitsTracker::class,
|
||||
Options\AppOptions::class,
|
||||
'Logger_Shlink',
|
||||
],
|
||||
Action\QrCodeAction::class => [RouterInterface::class, Service\UrlShortener::class, 'Logger_Shlink'],
|
||||
Action\PreviewAction::class => [PreviewGenerator::class, Service\UrlShortener::class],
|
||||
Action\PreviewAction::class => [PreviewGenerator::class, Service\UrlShortener::class, 'Logger_Shlink'],
|
||||
Middleware\QrCodeCacheMiddleware::class => [Cache::class],
|
||||
],
|
||||
|
||||
|
||||
@@ -13,6 +13,12 @@ return [
|
||||
'middleware' => Action\RedirectAction::class,
|
||||
'allowed_methods' => ['GET'],
|
||||
],
|
||||
[
|
||||
'name' => 'pixel-tracking',
|
||||
'path' => '/{shortCode}/track',
|
||||
'middleware' => Action\PixelAction::class,
|
||||
'allowed_methods' => ['GET'],
|
||||
],
|
||||
[
|
||||
'name' => 'short-url-qr-code',
|
||||
'path' => '/{shortCode}/qr-code[/{size:[0-9]+}]',
|
||||
|
||||
83
module/Core/src/Action/AbstractTrackingAction.php
Normal file
83
module/Core/src/Action/AbstractTrackingAction.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Action;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
|
||||
abstract class AbstractTrackingAction implements MiddlewareInterface
|
||||
{
|
||||
use ErrorResponseBuilderTrait;
|
||||
|
||||
/**
|
||||
* @var UrlShortenerInterface
|
||||
*/
|
||||
private $urlShortener;
|
||||
/**
|
||||
* @var VisitsTrackerInterface
|
||||
*/
|
||||
private $visitTracker;
|
||||
/**
|
||||
* @var AppOptions
|
||||
*/
|
||||
private $appOptions;
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
public function __construct(
|
||||
UrlShortenerInterface $urlShortener,
|
||||
VisitsTrackerInterface $visitTracker,
|
||||
AppOptions $appOptions,
|
||||
LoggerInterface $logger = null
|
||||
) {
|
||||
$this->urlShortener = $urlShortener;
|
||||
$this->visitTracker = $visitTracker;
|
||||
$this->appOptions = $appOptions;
|
||||
$this->logger = $logger ?: new NullLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming server request and return a response, optionally delegating
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$shortCode = $request->getAttribute('shortCode', '');
|
||||
$query = $request->getQueryParams();
|
||||
$disableTrackParam = $this->appOptions->getDisableTrackParam();
|
||||
|
||||
try {
|
||||
$longUrl = $this->urlShortener->shortCodeToUrl($shortCode);
|
||||
|
||||
// Track visit to this short code
|
||||
if ($disableTrackParam === null || ! \array_key_exists($disableTrackParam, $query)) {
|
||||
$this->visitTracker->track($shortCode, $request);
|
||||
}
|
||||
|
||||
return $this->createResp($longUrl);
|
||||
} catch (InvalidShortCodeException | EntityDoesNotExistException $e) {
|
||||
$this->logger->warning('An error occurred while tracking short code.' . PHP_EOL . $e);
|
||||
return $this->buildErrorResponse($request, $handler);
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected function createResp(string $longUrl): ResponseInterface;
|
||||
}
|
||||
15
module/Core/src/Action/PixelAction.php
Normal file
15
module/Core/src/Action/PixelAction.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Action;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Shlinkio\Shlink\Common\Response\PixelResponse;
|
||||
|
||||
class PixelAction extends AbstractTrackingAction
|
||||
{
|
||||
protected function createResp(string $longUrl): ResponseInterface
|
||||
{
|
||||
return new PixelResponse();
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Action;
|
||||
|
||||
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\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
|
||||
use Shlinkio\Shlink\Common\Service\PreviewGeneratorInterface;
|
||||
use Shlinkio\Shlink\Common\Util\ResponseUtilsTrait;
|
||||
@@ -28,11 +30,19 @@ class PreviewAction implements MiddlewareInterface
|
||||
* @var UrlShortenerInterface
|
||||
*/
|
||||
private $urlShortener;
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
public function __construct(PreviewGeneratorInterface $previewGenerator, UrlShortenerInterface $urlShortener)
|
||||
{
|
||||
public function __construct(
|
||||
PreviewGeneratorInterface $previewGenerator,
|
||||
UrlShortenerInterface $urlShortener,
|
||||
LoggerInterface $logger = null
|
||||
) {
|
||||
$this->previewGenerator = $previewGenerator;
|
||||
$this->urlShortener = $urlShortener;
|
||||
$this->logger = $logger ?: new NullLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,11 +50,11 @@ class PreviewAction implements MiddlewareInterface
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param DelegateInterface $delegate
|
||||
* @param RequestHandlerInterface $handler
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||
{
|
||||
$shortCode = $request->getAttribute('shortCode');
|
||||
|
||||
@@ -52,12 +62,9 @@ class PreviewAction implements MiddlewareInterface
|
||||
$url = $this->urlShortener->shortCodeToUrl($shortCode);
|
||||
$imagePath = $this->previewGenerator->generatePreview($url);
|
||||
return $this->generateImageResponse($imagePath);
|
||||
} catch (InvalidShortCodeException $e) {
|
||||
return $this->buildErrorResponse($request, $delegate);
|
||||
} catch (EntityDoesNotExistException $e) {
|
||||
return $this->buildErrorResponse($request, $delegate);
|
||||
} catch (PreviewGenerationException $e) {
|
||||
return $this->buildErrorResponse($request, $delegate);
|
||||
} catch (InvalidShortCodeException | EntityDoesNotExistException | PreviewGenerationException $e) {
|
||||
$this->logger->warning('An error occurred while generating preview image.' . PHP_EOL . $e);
|
||||
return $this->buildErrorResponse($request, $handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Core\Action;
|
||||
|
||||
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\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Shlinkio\Shlink\Common\Response\QrCodeResponse;
|
||||
@@ -15,6 +15,7 @@ use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Zend\Expressive\Router\Exception\RuntimeException;
|
||||
use Zend\Expressive\Router\RouterInterface;
|
||||
|
||||
class QrCodeAction implements MiddlewareInterface
|
||||
@@ -49,22 +50,21 @@ class QrCodeAction implements MiddlewareInterface
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param DelegateInterface $delegate
|
||||
* @param RequestHandlerInterface $handler
|
||||
*
|
||||
* @return Response
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||
{
|
||||
// Make sure the short URL exists for this short code
|
||||
$shortCode = $request->getAttribute('shortCode');
|
||||
try {
|
||||
$this->urlShortener->shortCodeToUrl($shortCode);
|
||||
} catch (InvalidShortCodeException $e) {
|
||||
$this->logger->warning('Tried to create a QR code with an invalid short code' . PHP_EOL . $e);
|
||||
return $this->buildErrorResponse($request, $delegate);
|
||||
} catch (EntityDoesNotExistException $e) {
|
||||
$this->logger->warning('Tried to create a QR code with a not found short code' . PHP_EOL . $e);
|
||||
return $this->buildErrorResponse($request, $delegate);
|
||||
} catch (InvalidShortCodeException | EntityDoesNotExistException $e) {
|
||||
$this->logger->warning('An error occurred while creating QR code' . PHP_EOL . $e);
|
||||
return $this->buildErrorResponse($request, $handler);
|
||||
}
|
||||
|
||||
$path = $this->router->generateUri('long-url-redirect', ['shortCode' => $shortCode]);
|
||||
@@ -80,7 +80,7 @@ class QrCodeAction implements MiddlewareInterface
|
||||
* @param Request $request
|
||||
* @return int
|
||||
*/
|
||||
protected function getSizeParam(Request $request)
|
||||
private function getSizeParam(Request $request): int
|
||||
{
|
||||
$size = (int) $request->getAttribute('size', 300);
|
||||
if ($size < 50) {
|
||||
|
||||
@@ -3,75 +3,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Action;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Interop\Http\ServerMiddleware\MiddlewareInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Zend\Diactoros\Response\RedirectResponse;
|
||||
|
||||
class RedirectAction implements MiddlewareInterface
|
||||
class RedirectAction extends AbstractTrackingAction
|
||||
{
|
||||
use ErrorResponseBuilderTrait;
|
||||
|
||||
/**
|
||||
* @var UrlShortenerInterface
|
||||
*/
|
||||
private $urlShortener;
|
||||
/**
|
||||
* @var VisitsTrackerInterface
|
||||
*/
|
||||
private $visitTracker;
|
||||
/**
|
||||
* @var AppOptions
|
||||
*/
|
||||
private $appOptions;
|
||||
|
||||
public function __construct(
|
||||
UrlShortenerInterface $urlShortener,
|
||||
VisitsTrackerInterface $visitTracker,
|
||||
AppOptions $appOptions
|
||||
) {
|
||||
$this->urlShortener = $urlShortener;
|
||||
$this->visitTracker = $visitTracker;
|
||||
$this->appOptions = $appOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming server request and return a response, optionally delegating
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param DelegateInterface $delegate
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
protected function createResp(string $longUrl): Response
|
||||
{
|
||||
$shortCode = $request->getAttribute('shortCode', '');
|
||||
$query = $request->getQueryParams();
|
||||
$disableTrackParam = $this->appOptions->getDisableTrackParam();
|
||||
|
||||
try {
|
||||
$longUrl = $this->urlShortener->shortCodeToUrl($shortCode);
|
||||
|
||||
// Track visit to this short code
|
||||
if ($disableTrackParam === null || ! \array_key_exists($disableTrackParam, $query)) {
|
||||
$this->visitTracker->track($shortCode, $request);
|
||||
}
|
||||
|
||||
// Return a redirect response to the long URL.
|
||||
// Use a temporary redirect to make sure browsers always hit the server for analytics purposes
|
||||
return new RedirectResponse($longUrl);
|
||||
} catch (InvalidShortCodeException $e) {
|
||||
return $this->buildErrorResponse($request, $delegate);
|
||||
} catch (EntityDoesNotExistException $e) {
|
||||
return $this->buildErrorResponse($request, $delegate);
|
||||
}
|
||||
// Return a redirect response to the long URL.
|
||||
// Use a temporary redirect to make sure browsers always hit the server for analytics purposes
|
||||
return new RedirectResponse($longUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Action\Util;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Shlinkio\Shlink\Core\Response\NotFoundDelegate;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Core\Response\NotFoundHandler;
|
||||
|
||||
trait ErrorResponseBuilderTrait
|
||||
{
|
||||
private function buildErrorResponse(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface
|
||||
{
|
||||
$request = $request->withAttribute(NotFoundDelegate::NOT_FOUND_TEMPLATE, 'ShlinkCore::invalid-short-code');
|
||||
return $delegate->process($request);
|
||||
private function buildErrorResponse(
|
||||
ServerRequestInterface $request,
|
||||
RequestHandlerInterface $handler
|
||||
): ResponseInterface {
|
||||
$request = $request->withAttribute(NotFoundHandler::NOT_FOUND_TEMPLATE, 'ShlinkCore::invalid-short-code');
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ class Visit extends AbstractEntity implements \JsonSerializable
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getReferer()
|
||||
public function getReferer(): string
|
||||
{
|
||||
return $this->referer;
|
||||
}
|
||||
@@ -66,7 +66,7 @@ class Visit extends AbstractEntity implements \JsonSerializable
|
||||
* @param string $referer
|
||||
* @return $this
|
||||
*/
|
||||
public function setReferer($referer)
|
||||
public function setReferer($referer): self
|
||||
{
|
||||
$this->referer = $referer;
|
||||
return $this;
|
||||
@@ -75,7 +75,7 @@ class Visit extends AbstractEntity implements \JsonSerializable
|
||||
/**
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getDate()
|
||||
public function getDate(): \DateTime
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
@@ -84,7 +84,7 @@ class Visit extends AbstractEntity implements \JsonSerializable
|
||||
* @param \DateTime $date
|
||||
* @return $this
|
||||
*/
|
||||
public function setDate($date)
|
||||
public function setDate($date): self
|
||||
{
|
||||
$this->date = $date;
|
||||
return $this;
|
||||
@@ -93,7 +93,7 @@ class Visit extends AbstractEntity implements \JsonSerializable
|
||||
/**
|
||||
* @return ShortUrl
|
||||
*/
|
||||
public function getShortUrl()
|
||||
public function getShortUrl(): ShortUrl
|
||||
{
|
||||
return $this->shortUrl;
|
||||
}
|
||||
@@ -102,7 +102,7 @@ class Visit extends AbstractEntity implements \JsonSerializable
|
||||
* @param ShortUrl $shortUrl
|
||||
* @return $this
|
||||
*/
|
||||
public function setShortUrl($shortUrl)
|
||||
public function setShortUrl($shortUrl): self
|
||||
{
|
||||
$this->shortUrl = $shortUrl;
|
||||
return $this;
|
||||
@@ -111,7 +111,7 @@ class Visit extends AbstractEntity implements \JsonSerializable
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteAddr()
|
||||
public function getRemoteAddr(): string
|
||||
{
|
||||
return $this->remoteAddr;
|
||||
}
|
||||
@@ -120,7 +120,7 @@ class Visit extends AbstractEntity implements \JsonSerializable
|
||||
* @param string $remoteAddr
|
||||
* @return $this
|
||||
*/
|
||||
public function setRemoteAddr($remoteAddr)
|
||||
public function setRemoteAddr($remoteAddr): self
|
||||
{
|
||||
$this->remoteAddr = $remoteAddr;
|
||||
return $this;
|
||||
@@ -129,7 +129,7 @@ class Visit extends AbstractEntity implements \JsonSerializable
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUserAgent()
|
||||
public function getUserAgent(): string
|
||||
{
|
||||
return $this->userAgent;
|
||||
}
|
||||
@@ -138,7 +138,7 @@ class Visit extends AbstractEntity implements \JsonSerializable
|
||||
* @param string $userAgent
|
||||
* @return $this
|
||||
*/
|
||||
public function setUserAgent($userAgent)
|
||||
public function setUserAgent($userAgent): self
|
||||
{
|
||||
$this->userAgent = $userAgent;
|
||||
return $this;
|
||||
@@ -147,7 +147,7 @@ class Visit extends AbstractEntity implements \JsonSerializable
|
||||
/**
|
||||
* @return VisitLocation
|
||||
*/
|
||||
public function getVisitLocation()
|
||||
public function getVisitLocation(): VisitLocation
|
||||
{
|
||||
return $this->visitLocation;
|
||||
}
|
||||
@@ -156,7 +156,7 @@ class Visit extends AbstractEntity implements \JsonSerializable
|
||||
* @param VisitLocation $visitLocation
|
||||
* @return $this
|
||||
*/
|
||||
public function setVisitLocation($visitLocation)
|
||||
public function setVisitLocation($visitLocation): self
|
||||
{
|
||||
$this->visitLocation = $visitLocation;
|
||||
return $this;
|
||||
@@ -165,11 +165,11 @@ class Visit extends AbstractEntity implements \JsonSerializable
|
||||
/**
|
||||
* Specify data which should be serialized to JSON
|
||||
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||
* @return mixed data which can be serialized by <b>json_encode</b>,
|
||||
* @return array data which can be serialized by <b>json_encode</b>,
|
||||
* which is a value of any type other than a resource.
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'referer' => $this->referer,
|
||||
|
||||
@@ -4,10 +4,10 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Core\Middleware;
|
||||
|
||||
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 Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Zend\Diactoros\Response as DiactResp;
|
||||
|
||||
class QrCodeCacheMiddleware implements MiddlewareInterface
|
||||
@@ -27,11 +27,11 @@ class QrCodeCacheMiddleware implements MiddlewareInterface
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param DelegateInterface $delegate
|
||||
* @param RequestHandlerInterface $handler
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||
{
|
||||
$cacheKey = $request->getUri()->getPath();
|
||||
|
||||
@@ -45,7 +45,7 @@ class QrCodeCacheMiddleware implements MiddlewareInterface
|
||||
|
||||
// If not, call the next middleware and cache it
|
||||
/** @var Response $resp */
|
||||
$resp = $delegate->process($request);
|
||||
$resp = $handler->handle($request);
|
||||
$this->cache->save($cacheKey, [
|
||||
'body' => $resp->getBody()->__toString(),
|
||||
'content-type' => $resp->getHeaderLine('Content-Type'),
|
||||
|
||||
56
module/Core/src/Model/CreateShortCodeData.php
Normal file
56
module/Core/src/Model/CreateShortCodeData.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Model;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
final class CreateShortCodeData
|
||||
{
|
||||
/**
|
||||
* @var UriInterface
|
||||
*/
|
||||
private $longUrl;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $tags;
|
||||
/**
|
||||
* @var ShortUrlMeta
|
||||
*/
|
||||
private $meta;
|
||||
|
||||
public function __construct(
|
||||
UriInterface $longUrl,
|
||||
array $tags = [],
|
||||
ShortUrlMeta $meta = null
|
||||
) {
|
||||
$this->longUrl = $longUrl;
|
||||
$this->tags = $tags;
|
||||
$this->meta = $meta ?? ShortUrlMeta::createFromParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UriInterface
|
||||
*/
|
||||
public function getLongUrl(): UriInterface
|
||||
{
|
||||
return $this->longUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getTags(): array
|
||||
{
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShortUrlMeta
|
||||
*/
|
||||
public function getMeta(): ShortUrlMeta
|
||||
{
|
||||
return $this->meta;
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ final class ShortUrlMeta
|
||||
* @param array $data
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate(array $data)
|
||||
private function validate(array $data): void
|
||||
{
|
||||
$inputFilter = new ShortUrlMetaInputFilter($data);
|
||||
if (! $inputFilter->isValid()) {
|
||||
|
||||
@@ -25,7 +25,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
|
||||
$orderBy = null
|
||||
): array {
|
||||
$qb = $this->createListQueryBuilder($searchTerm, $tags);
|
||||
$qb->select('s');
|
||||
$qb->select('DISTINCT s');
|
||||
|
||||
// Set limit and offset
|
||||
if ($limit !== null) {
|
||||
@@ -47,17 +47,17 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
|
||||
|
||||
protected function processOrderByForList(QueryBuilder $qb, $orderBy)
|
||||
{
|
||||
$fieldName = is_array($orderBy) ? key($orderBy) : $orderBy;
|
||||
$order = is_array($orderBy) ? $orderBy[$fieldName] : 'ASC';
|
||||
$fieldName = \is_array($orderBy) ? \key($orderBy) : $orderBy;
|
||||
$order = \is_array($orderBy) ? $orderBy[$fieldName] : 'ASC';
|
||||
|
||||
if (in_array($fieldName, ['visits', 'visitsCount', 'visitCount'], true)) {
|
||||
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)) {
|
||||
return \array_column($qb->getQuery()->getResult(), 0);
|
||||
} elseif (\in_array($fieldName, ['originalUrl', 'shortCode', 'dateCreated'], true)) {
|
||||
$qb->orderBy('s.' . $fieldName, $order);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
|
||||
public function countList(string $searchTerm = null, array $tags = []): int
|
||||
{
|
||||
$qb = $this->createListQueryBuilder($searchTerm, $tags);
|
||||
$qb->select('COUNT(s)');
|
||||
$qb->select('COUNT(DISTINCT s)');
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
@@ -92,7 +92,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
|
||||
|
||||
// Apply search term to every searchable field if not empty
|
||||
if (! empty($searchTerm)) {
|
||||
$qb->join('s.tags', 't');
|
||||
$qb->leftJoin('s.tags', 't');
|
||||
|
||||
$conditions = [
|
||||
$qb->expr()->like('s.originalUrl', ':searchPattern'),
|
||||
@@ -102,8 +102,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
|
||||
|
||||
// Unpack and apply search conditions
|
||||
$qb->andWhere($qb->expr()->orX(...$conditions));
|
||||
$searchTerm = '%' . $searchTerm . '%';
|
||||
$qb->setParameter('searchPattern', $searchTerm);
|
||||
$qb->setParameter('searchPattern', '%' . $searchTerm . '%');
|
||||
}
|
||||
|
||||
// Filter by tags if provided
|
||||
@@ -119,7 +118,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
|
||||
* @param string $shortCode
|
||||
* @return ShortUrl|null
|
||||
*/
|
||||
public function findOneByShortCode(string $shortCode)
|
||||
public function findOneByShortCode(string $shortCode): ?ShortUrl
|
||||
{
|
||||
$now = new \DateTimeImmutable();
|
||||
|
||||
|
||||
@@ -4,15 +4,15 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Core\Response;
|
||||
|
||||
use Fig\Http\Message\StatusCodeInterface;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Expressive\Template\TemplateRendererInterface;
|
||||
|
||||
class NotFoundDelegate implements DelegateInterface
|
||||
class NotFoundHandler implements RequestHandlerInterface
|
||||
{
|
||||
const NOT_FOUND_TEMPLATE = 'notFoundTemplate';
|
||||
public const NOT_FOUND_TEMPLATE = 'notFoundTemplate';
|
||||
|
||||
/**
|
||||
* @var TemplateRendererInterface
|
||||
@@ -37,14 +37,14 @@ class NotFoundDelegate implements DelegateInterface
|
||||
* @return ResponseInterface
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function process(ServerRequestInterface $request): ResponseInterface
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$accepts = explode(',', $request->getHeaderLine('Accept'));
|
||||
$accept = array_shift($accepts);
|
||||
$accepts = \explode(',', $request->getHeaderLine('Accept'));
|
||||
$accept = \array_shift($accepts);
|
||||
$status = StatusCodeInterface::STATUS_NOT_FOUND;
|
||||
|
||||
// If the first accepted type is json, return a json response
|
||||
if (in_array($accept, ['application/json', 'text/json', 'application/x-json'], true)) {
|
||||
if (\in_array($accept, ['application/json', 'text/json', 'application/x-json'], true)) {
|
||||
return new Response\JsonResponse([
|
||||
'error' => 'NOT_FOUND',
|
||||
'message' => 'Not found',
|
||||
@@ -14,7 +14,7 @@ trait TagManagerTrait
|
||||
* @param string[] $tags
|
||||
* @return Collections\Collection|Tag[]
|
||||
*/
|
||||
protected function tagNamesToEntities(EntityManagerInterface $em, array $tags)
|
||||
private function tagNamesToEntities(EntityManagerInterface $em, array $tags)
|
||||
{
|
||||
$entities = [];
|
||||
foreach ($tags as $tagName) {
|
||||
@@ -33,8 +33,8 @@ trait TagManagerTrait
|
||||
* @param string $tagName
|
||||
* @return string
|
||||
*/
|
||||
protected function normalizeTagName($tagName)
|
||||
private function normalizeTagName($tagName): string
|
||||
{
|
||||
return str_replace(' ', '-', strtolower(trim($tagName)));
|
||||
return \str_replace(' ', '-', \strtolower(\trim($tagName)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use Zend\InputFilter\Input;
|
||||
|
||||
trait InputFactoryTrait
|
||||
{
|
||||
public function createInput($name, $required = true): Input
|
||||
private function createInput($name, $required = true): Input
|
||||
{
|
||||
$input = new Input($name);
|
||||
$input->setRequired($required)
|
||||
|
||||
63
module/Core/test/Action/PixelActionTest.php
Normal file
63
module/Core/test/Action/PixelActionTest.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Core\Action;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Response\PixelResponse;
|
||||
use Shlinkio\Shlink\Core\Action\PixelAction;
|
||||
use Shlinkio\Shlink\Core\Action\RedirectAction;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
use ShlinkioTest\Shlink\Common\Util\TestUtils;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
|
||||
class PixelActionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var RedirectAction
|
||||
*/
|
||||
protected $action;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $urlShortener;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $visitTracker;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->urlShortener = $this->prophesize(UrlShortener::class);
|
||||
$this->visitTracker = $this->prophesize(VisitsTracker::class);
|
||||
|
||||
$this->action = new PixelAction(
|
||||
$this->urlShortener->reveal(),
|
||||
$this->visitTracker->reveal(),
|
||||
new AppOptions()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function imageIsReturned()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn('http://domain.com/foo/bar')
|
||||
->shouldBeCalledTimes(1);
|
||||
$this->visitTracker->track(Argument::cetera())->willReturn(null)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode);
|
||||
$response = $this->action->process($request, TestUtils::createReqHandlerMock()->reveal());
|
||||
|
||||
$this->assertInstanceOf(PixelResponse::class, $response);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals('image/gif', $response->getHeaderLine('content-type'));
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Core\Action;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Common\Service\PreviewGenerator;
|
||||
use Shlinkio\Shlink\Core\Action\PreviewAction;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
@@ -47,8 +47,8 @@ class PreviewActionTest extends TestCase
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willThrow(EntityDoesNotExistException::class)
|
||||
->shouldBeCalledTimes(1);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate->process(Argument::cetera())->shouldBeCalledTimes(1)
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
$delegate->handle(Argument::cetera())->shouldBeCalledTimes(1)
|
||||
->willReturn(new Response());
|
||||
|
||||
$this->action->process(
|
||||
@@ -70,7 +70,7 @@ class PreviewActionTest extends TestCase
|
||||
|
||||
$resp = $this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode),
|
||||
TestUtils::createDelegateMock()->reveal()
|
||||
TestUtils::createReqHandlerMock()->reveal()
|
||||
);
|
||||
|
||||
$this->assertEquals(filesize($path), $resp->getHeaderLine('Content-length'));
|
||||
@@ -85,9 +85,9 @@ class PreviewActionTest extends TestCase
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willThrow(InvalidShortCodeException::class)
|
||||
->shouldBeCalledTimes(1);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
/** @var MethodProphecy $process */
|
||||
$process = $delegate->process(Argument::any())->willReturn(new Response());
|
||||
$process = $delegate->handle(Argument::any())->willReturn(new Response());
|
||||
|
||||
$this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode),
|
||||
|
||||
@@ -3,11 +3,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Core\Action;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Common\Response\QrCodeResponse;
|
||||
use Shlinkio\Shlink\Core\Action\QrCodeAction;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
@@ -46,8 +46,8 @@ class QrCodeActionTest extends TestCase
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willThrow(EntityDoesNotExistException::class)
|
||||
->shouldBeCalledTimes(1);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$process = $delegate->process(Argument::any())->willReturn(new Response());
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
$process = $delegate->handle(Argument::any())->willReturn(new Response());
|
||||
|
||||
$this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode),
|
||||
@@ -65,9 +65,9 @@ class QrCodeActionTest extends TestCase
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willThrow(InvalidShortCodeException::class)
|
||||
->shouldBeCalledTimes(1);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
/** @var MethodProphecy $process */
|
||||
$process = $delegate->process(Argument::any())->willReturn(new Response());
|
||||
$process = $delegate->handle(Argument::any())->willReturn(new Response());
|
||||
|
||||
$this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode),
|
||||
@@ -84,7 +84,7 @@ class QrCodeActionTest extends TestCase
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn('')->shouldBeCalledTimes(1);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
|
||||
$resp = $this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode),
|
||||
@@ -93,6 +93,6 @@ class QrCodeActionTest extends TestCase
|
||||
|
||||
$this->assertInstanceOf(QrCodeResponse::class, $resp);
|
||||
$this->assertEquals(200, $resp->getStatusCode());
|
||||
$delegate->process(Argument::any())->shouldHaveBeenCalledTimes(0);
|
||||
$delegate->handle(Argument::any())->shouldHaveBeenCalledTimes(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Core\Action;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Core\Action\RedirectAction;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
@@ -57,7 +57,7 @@ class RedirectActionTest extends TestCase
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode);
|
||||
$response = $this->action->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->action->process($request, TestUtils::createReqHandlerMock()->reveal());
|
||||
|
||||
$this->assertInstanceOf(Response\RedirectResponse::class, $response);
|
||||
$this->assertEquals(302, $response->getStatusCode());
|
||||
@@ -76,9 +76,9 @@ class RedirectActionTest extends TestCase
|
||||
$this->visitTracker->track(Argument::cetera())->willReturn(null)
|
||||
->shouldNotBeCalled();
|
||||
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
/** @var MethodProphecy $process */
|
||||
$process = $delegate->process(Argument::any())->willReturn(new Response());
|
||||
$process = $delegate->handle(Argument::any())->willReturn(new Response());
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode);
|
||||
$this->action->process($request, $delegate->reveal());
|
||||
@@ -100,7 +100,7 @@ class RedirectActionTest extends TestCase
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode)
|
||||
->withQueryParams(['foobar' => true]);
|
||||
$response = $this->action->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->action->process($request, TestUtils::createReqHandlerMock()->reveal());
|
||||
|
||||
$this->assertInstanceOf(Response\RedirectResponse::class, $response);
|
||||
$this->assertEquals(302, $response->getStatusCode());
|
||||
|
||||
@@ -5,9 +5,9 @@ namespace ShlinkioTest\Shlink\Core\Middleware;
|
||||
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Core\Middleware\QrCodeCacheMiddleware;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
@@ -35,8 +35,8 @@ class QrCodeCacheMiddlewareTest extends TestCase
|
||||
*/
|
||||
public function noCachedPathFallsBackToNextMiddleware()
|
||||
{
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate->process(Argument::any())->willReturn(new Response())->shouldBeCalledTimes(1);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
$delegate->handle(Argument::any())->willReturn(new Response())->shouldBeCalledTimes(1);
|
||||
|
||||
$this->middleware->process(ServerRequestFactory::fromGlobals()->withUri(
|
||||
new Uri('/foo/bar')
|
||||
@@ -53,7 +53,7 @@ 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);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
|
||||
$resp = $this->middleware->process(
|
||||
ServerRequestFactory::fromGlobals()->withUri($uri),
|
||||
@@ -64,6 +64,6 @@ class QrCodeCacheMiddlewareTest extends TestCase
|
||||
$resp->getBody()->rewind();
|
||||
$this->assertEquals('the body', $resp->getBody()->getContents());
|
||||
$this->assertEquals('image/png', $resp->getHeaderLine('Content-Type'));
|
||||
$delegate->process(Argument::any())->shouldHaveBeenCalledTimes(0);
|
||||
$delegate->handle(Argument::any())->shouldHaveBeenCalledTimes(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,15 @@ use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Response\NotFoundDelegate;
|
||||
use Shlinkio\Shlink\Core\Response\NotFoundHandler;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
use Zend\Expressive\Template\TemplateRendererInterface;
|
||||
|
||||
class NotFoundDelegateTest extends TestCase
|
||||
class NotFoundHandlerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var NotFoundDelegate
|
||||
* @var NotFoundHandler
|
||||
*/
|
||||
private $delegate;
|
||||
/**
|
||||
@@ -26,7 +26,7 @@ class NotFoundDelegateTest extends TestCase
|
||||
public function setUp()
|
||||
{
|
||||
$this->renderer = $this->prophesize(TemplateRendererInterface::class);
|
||||
$this->delegate = new NotFoundDelegate($this->renderer->reveal());
|
||||
$this->delegate = new NotFoundHandler($this->renderer->reveal());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,7 +43,7 @@ class NotFoundDelegateTest extends TestCase
|
||||
/** @var MethodProphecy $render */
|
||||
$render = $this->renderer->render(Argument::cetera())->willReturn('');
|
||||
|
||||
$resp = $this->delegate->process($request);
|
||||
$resp = $this->delegate->handle($request);
|
||||
|
||||
$this->assertInstanceOf($expectedResponse, $resp);
|
||||
$render->shouldHaveBeenCalledTimes($renderCalls);
|
||||
15
module/Rest/config/auth.config.php
Normal file
15
module/Rest/config/auth.config.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest;
|
||||
|
||||
return [
|
||||
|
||||
'auth' => [
|
||||
'routes_whitelist' => [
|
||||
Action\AuthenticateAction::class,
|
||||
Action\ShortCode\SingleStepCreateShortCodeAction::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -20,12 +20,13 @@ return [
|
||||
ApiKeyService::class => ConfigAbstractFactory::class,
|
||||
|
||||
Action\AuthenticateAction::class => ConfigAbstractFactory::class,
|
||||
Action\CreateShortcodeAction::class => ConfigAbstractFactory::class,
|
||||
Action\EditShortCodeAction::class => ConfigAbstractFactory::class,
|
||||
Action\ResolveUrlAction::class => ConfigAbstractFactory::class,
|
||||
Action\GetVisitsAction::class => ConfigAbstractFactory::class,
|
||||
Action\ListShortcodesAction::class => ConfigAbstractFactory::class,
|
||||
Action\EditShortcodeTagsAction::class => ConfigAbstractFactory::class,
|
||||
Action\ShortCode\CreateShortCodeAction::class => ConfigAbstractFactory::class,
|
||||
Action\ShortCode\SingleStepCreateShortCodeAction::class => ConfigAbstractFactory::class,
|
||||
Action\ShortCode\EditShortCodeAction::class => ConfigAbstractFactory::class,
|
||||
Action\ShortCode\ResolveUrlAction::class => ConfigAbstractFactory::class,
|
||||
Action\Visit\GetVisitsAction::class => ConfigAbstractFactory::class,
|
||||
Action\ShortCode\ListShortCodesAction::class => ConfigAbstractFactory::class,
|
||||
Action\ShortCode\EditShortCodeTagsAction::class => ConfigAbstractFactory::class,
|
||||
Action\Tag\ListTagsAction::class => ConfigAbstractFactory::class,
|
||||
Action\Tag\DeleteTagsAction::class => ConfigAbstractFactory::class,
|
||||
Action\Tag\CreateTagsAction::class => ConfigAbstractFactory::class,
|
||||
@@ -35,6 +36,7 @@ return [
|
||||
Middleware\CrossDomainMiddleware::class => InvokableFactory::class,
|
||||
Middleware\PathVersionMiddleware::class => InvokableFactory::class,
|
||||
Middleware\CheckAuthenticationMiddleware::class => ConfigAbstractFactory::class,
|
||||
Middleware\ShortCode\CreateShortCodeContentNegotiationMiddleware::class => InvokableFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
@@ -43,23 +45,39 @@ return [
|
||||
ApiKeyService::class => ['em'],
|
||||
|
||||
Action\AuthenticateAction::class => [ApiKeyService::class, JWTService::class, 'translator', 'Logger_Shlink'],
|
||||
Action\CreateShortcodeAction::class => [
|
||||
Action\ShortCode\CreateShortCodeAction::class => [
|
||||
Service\UrlShortener::class,
|
||||
'translator',
|
||||
'config.url_shortener.domain',
|
||||
'Logger_Shlink',
|
||||
],
|
||||
Action\EditShortCodeAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink',],
|
||||
Action\ResolveUrlAction::class => [Service\UrlShortener::class, 'translator'],
|
||||
Action\GetVisitsAction::class => [Service\VisitsTracker::class, 'translator', 'Logger_Shlink'],
|
||||
Action\ListShortcodesAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink'],
|
||||
Action\EditShortcodeTagsAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink'],
|
||||
Action\ShortCode\SingleStepCreateShortCodeAction::class => [
|
||||
Service\UrlShortener::class,
|
||||
'translator',
|
||||
ApiKeyService::class,
|
||||
'config.url_shortener.domain',
|
||||
'Logger_Shlink',
|
||||
],
|
||||
Action\ShortCode\EditShortCodeAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink',],
|
||||
Action\ShortCode\ResolveUrlAction::class => [Service\UrlShortener::class, 'translator'],
|
||||
Action\Visit\GetVisitsAction::class => [Service\VisitsTracker::class, 'translator', 'Logger_Shlink'],
|
||||
Action\ShortCode\ListShortCodesAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink'],
|
||||
Action\ShortCode\EditShortCodeTagsAction::class => [
|
||||
Service\ShortUrlService::class,
|
||||
'translator',
|
||||
'Logger_Shlink',
|
||||
],
|
||||
Action\Tag\ListTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class],
|
||||
Action\Tag\DeleteTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class],
|
||||
Action\Tag\CreateTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class],
|
||||
Action\Tag\UpdateTagAction::class => [Service\Tag\TagService::class, Translator::class, LoggerInterface::class],
|
||||
|
||||
Middleware\CheckAuthenticationMiddleware::class => [JWTService::class, 'translator', 'Logger_Shlink'],
|
||||
Middleware\CheckAuthenticationMiddleware::class => [
|
||||
JWTService::class,
|
||||
'translator',
|
||||
'config.auth.routes_whitelist',
|
||||
'Logger_Shlink',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -1,84 +1,35 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Fig\Http\Message\RequestMethodInterface as RequestMethod;
|
||||
namespace Shlinkio\Shlink\Rest;
|
||||
|
||||
use Shlinkio\Shlink\Rest\Action;
|
||||
|
||||
return [
|
||||
|
||||
'routes' => [
|
||||
[
|
||||
'name' => Action\AuthenticateAction::class,
|
||||
'path' => '/authenticate',
|
||||
'middleware' => Action\AuthenticateAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_POST],
|
||||
],
|
||||
Action\AuthenticateAction::getRouteDef(),
|
||||
|
||||
// Short codes
|
||||
[
|
||||
'name' => Action\CreateShortcodeAction::class,
|
||||
'path' => '/short-codes',
|
||||
'middleware' => Action\CreateShortcodeAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_POST],
|
||||
],
|
||||
[
|
||||
'name' => Action\EditShortCodeAction::class,
|
||||
'path' => '/short-codes/{shortCode}',
|
||||
'middleware' => Action\EditShortCodeAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_PUT],
|
||||
],
|
||||
[
|
||||
'name' => Action\ResolveUrlAction::class,
|
||||
'path' => '/short-codes/{shortCode}',
|
||||
'middleware' => Action\ResolveUrlAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_GET],
|
||||
],
|
||||
[
|
||||
'name' => Action\ListShortcodesAction::class,
|
||||
'path' => '/short-codes',
|
||||
'middleware' => Action\ListShortcodesAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_GET],
|
||||
],
|
||||
[
|
||||
'name' => Action\EditShortcodeTagsAction::class,
|
||||
'path' => '/short-codes/{shortCode}/tags',
|
||||
'middleware' => Action\EditShortcodeTagsAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_PUT],
|
||||
],
|
||||
Action\ShortCode\CreateShortCodeAction::getRouteDef([
|
||||
Middleware\ShortCode\CreateShortCodeContentNegotiationMiddleware::class,
|
||||
]),
|
||||
Action\ShortCode\SingleStepCreateShortCodeAction::getRouteDef([
|
||||
Middleware\ShortCode\CreateShortCodeContentNegotiationMiddleware::class,
|
||||
]),
|
||||
Action\ShortCode\EditShortCodeAction::getRouteDef(),
|
||||
Action\ShortCode\ResolveUrlAction::getRouteDef(),
|
||||
Action\ShortCode\ListShortCodesAction::getRouteDef(),
|
||||
Action\ShortCode\EditShortCodeTagsAction::getRouteDef(),
|
||||
|
||||
// Visits
|
||||
[
|
||||
'name' => Action\GetVisitsAction::class,
|
||||
'path' => '/short-codes/{shortCode}/visits',
|
||||
'middleware' => Action\GetVisitsAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_GET],
|
||||
],
|
||||
Action\Visit\GetVisitsAction::getRouteDef(),
|
||||
|
||||
// Tags
|
||||
[
|
||||
'name' => Action\Tag\ListTagsAction::class,
|
||||
'path' => '/tags',
|
||||
'middleware' => Action\Tag\ListTagsAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_GET],
|
||||
],
|
||||
[
|
||||
'name' => Action\Tag\DeleteTagsAction::class,
|
||||
'path' => '/tags',
|
||||
'middleware' => Action\Tag\DeleteTagsAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_DELETE],
|
||||
],
|
||||
[
|
||||
'name' => Action\Tag\CreateTagsAction::class,
|
||||
'path' => '/tags',
|
||||
'middleware' => Action\Tag\CreateTagsAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_POST],
|
||||
],
|
||||
[
|
||||
'name' => Action\Tag\UpdateTagAction::class,
|
||||
'path' => '/tags',
|
||||
'middleware' => Action\Tag\UpdateTagAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_PUT],
|
||||
],
|
||||
Action\Tag\ListTagsAction::getRouteDef(),
|
||||
Action\Tag\DeleteTagsAction::getRouteDef(),
|
||||
Action\Tag\CreateTagsAction::getRouteDef(),
|
||||
Action\Tag\UpdateTagAction::getRouteDef(),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
Binary file not shown.
@@ -1,15 +1,15 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Shlink 1.0\n"
|
||||
"POT-Creation-Date: 2018-01-21 09:40+0100\n"
|
||||
"PO-Revision-Date: 2018-01-21 09:40+0100\n"
|
||||
"POT-Creation-Date: 2018-05-06 12:34+0200\n"
|
||||
"PO-Revision-Date: 2018-05-06 12:35+0200\n"
|
||||
"Last-Translator: Alejandro Celaya <alejandro@alejandrocelaya.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: es_ES\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.0.4\n"
|
||||
"X-Generator: Poedit 2.0.6\n"
|
||||
"X-Poedit-Basepath: ..\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
@@ -25,9 +25,6 @@ msgstr ""
|
||||
msgid "Provided API key does not exist or is invalid."
|
||||
msgstr "La clave de API proporcionada no existe o es inválida."
|
||||
|
||||
msgid "A URL was not provided"
|
||||
msgstr "No se ha proporcionado una URL"
|
||||
|
||||
#, php-format
|
||||
msgid "Provided URL %s is invalid. Try with a different one."
|
||||
msgstr "La URL proporcionada \"%s\" es inválida. Prueba con una diferente."
|
||||
@@ -39,6 +36,9 @@ msgstr "El slug proporcionado \"%s\" ya está en uso. Prueba con uno diferente."
|
||||
msgid "Unexpected error occurred"
|
||||
msgstr "Ocurrió un error inesperado"
|
||||
|
||||
msgid "A URL was not provided"
|
||||
msgstr "No se ha proporcionado una URL"
|
||||
|
||||
#, php-format
|
||||
msgid "No URL found for short code \"%s\""
|
||||
msgstr "No se ha encontrado ninguna URL para el código corto \"%s\""
|
||||
@@ -49,14 +49,13 @@ msgstr "Los datos proporcionados son inválidos."
|
||||
msgid "A list of tags was not provided"
|
||||
msgstr "No se ha proporcionado una lista de etiquetas"
|
||||
|
||||
#, php-format
|
||||
msgid "Provided short code %s does not exist"
|
||||
msgstr "El código corto \"%s\" proporcionado no existe"
|
||||
|
||||
#, php-format
|
||||
msgid "Provided short code \"%s\" has an invalid format"
|
||||
msgstr "El código corto proporcionado \"%s\" tiene un formato no inválido"
|
||||
|
||||
msgid "No API key was provided or it is not valid"
|
||||
msgstr "No se ha proporcionado una clave de API o esta es inválida"
|
||||
|
||||
msgid ""
|
||||
"You have to provide both 'oldName' and 'newName' params in order to properly "
|
||||
"rename the tag"
|
||||
@@ -68,6 +67,10 @@ msgstr ""
|
||||
msgid "It wasn't possible to find a tag with name '%s'"
|
||||
msgstr "No fue posible encontrar una etiqueta con el nombre '%s'"
|
||||
|
||||
#, php-format
|
||||
msgid "Provided short code %s does not exist"
|
||||
msgstr "El código corto \"%s\" proporcionado no existe"
|
||||
|
||||
#, php-format
|
||||
msgid "You need to provide the Bearer type in the %s header."
|
||||
msgstr "Debes proporcionar el typo Bearer en la cabecera %s."
|
||||
|
||||
@@ -5,12 +5,15 @@ namespace Shlinkio\Shlink\Rest\Action;
|
||||
|
||||
use Fig\Http\Message\RequestMethodInterface;
|
||||
use Fig\Http\Message\StatusCodeInterface;
|
||||
use Interop\Http\ServerMiddleware\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
abstract class AbstractRestAction implements MiddlewareInterface, RequestMethodInterface, StatusCodeInterface
|
||||
abstract class AbstractRestAction implements RequestHandlerInterface, RequestMethodInterface, StatusCodeInterface
|
||||
{
|
||||
protected const ROUTE_PATH = '';
|
||||
protected const ROUTE_ALLOWED_METHODS = [];
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
@@ -20,4 +23,14 @@ abstract class AbstractRestAction implements MiddlewareInterface, RequestMethodI
|
||||
{
|
||||
$this->logger = $logger ?: new NullLogger();
|
||||
}
|
||||
|
||||
public static function getRouteDef(array $prevMiddleware = [], array $postMiddleware = []): array
|
||||
{
|
||||
return [
|
||||
'name' => static::class,
|
||||
'middleware' => \array_merge($prevMiddleware, [static::class], $postMiddleware),
|
||||
'path' => static::ROUTE_PATH,
|
||||
'allowed_methods' => static::ROUTE_ALLOWED_METHODS,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -16,6 +15,9 @@ use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class AuthenticateAction extends AbstractRestAction
|
||||
{
|
||||
protected const ROUTE_PATH = '/authenticate';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_POST];
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
@@ -43,11 +45,10 @@ class AuthenticateAction extends AbstractRestAction
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param DelegateInterface $delegate
|
||||
* @return null|Response
|
||||
* @return Response
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
$authData = $request->getParsedBody();
|
||||
if (! isset($authData['apiKey'])) {
|
||||
@@ -61,7 +62,7 @@ class AuthenticateAction extends AbstractRestAction
|
||||
|
||||
// Authenticate using provided API key
|
||||
$apiKey = $this->apiKeyService->getByKey($authData['apiKey']);
|
||||
if (! isset($apiKey) || ! $apiKey->isValid()) {
|
||||
if ($apiKey === null || ! $apiKey->isValid()) {
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::INVALID_API_KEY_ERROR,
|
||||
'message' => $this->translator->translate('Provided API key does not exist or is invalid.'),
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
namespace Shlinkio\Shlink\Rest\Action\ShortCode;
|
||||
|
||||
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\Core\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
||||
use Shlinkio\Shlink\Core\Model\CreateShortCodeData;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
use Zend\Diactoros\Uri;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class CreateShortcodeAction extends AbstractRestAction
|
||||
abstract class AbstractCreateShortCodeAction extends AbstractRestAction
|
||||
{
|
||||
/**
|
||||
* @var UrlShortenerInterface
|
||||
@@ -28,7 +30,7 @@ class CreateShortcodeAction extends AbstractRestAction
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
protected $translator;
|
||||
|
||||
public function __construct(
|
||||
UrlShortenerInterface $urlShortener,
|
||||
@@ -44,37 +46,39 @@ class CreateShortcodeAction extends AbstractRestAction
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param DelegateInterface $delegate
|
||||
* @return null|Response
|
||||
* @return Response
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
$postData = (array) $request->getParsedBody();
|
||||
if (! isset($postData['longUrl'])) {
|
||||
try {
|
||||
$shortCodeData = $this->buildUrlToShortCodeData($request);
|
||||
$shortCodeMeta = $shortCodeData->getMeta();
|
||||
$longUrl = $shortCodeData->getLongUrl();
|
||||
$customSlug = $shortCodeMeta->getCustomSlug();
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$this->logger->warning('Provided data is invalid.' . PHP_EOL . $e);
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::INVALID_ARGUMENT_ERROR,
|
||||
'message' => $this->translator->translate('A URL was not provided'),
|
||||
'message' => $e->getMessage(),
|
||||
], self::STATUS_BAD_REQUEST);
|
||||
}
|
||||
$longUrl = $postData['longUrl'];
|
||||
$customSlug = $postData['customSlug'] ?? null;
|
||||
|
||||
try {
|
||||
$shortCode = $this->urlShortener->urlToShortCode(
|
||||
new Uri($longUrl),
|
||||
(array) ($postData['tags'] ?? []),
|
||||
$this->getOptionalDate($postData, 'validSince'),
|
||||
$this->getOptionalDate($postData, 'validUntil'),
|
||||
$longUrl,
|
||||
$shortCodeData->getTags(),
|
||||
$shortCodeMeta->getValidSince(),
|
||||
$shortCodeMeta->getValidUntil(),
|
||||
$customSlug,
|
||||
isset($postData['maxVisits']) ? (int) $postData['maxVisits'] : null
|
||||
$shortCodeMeta->getMaxVisits()
|
||||
);
|
||||
$shortUrl = (new Uri())->withPath($shortCode)
|
||||
->withScheme($this->domainConfig['schema'])
|
||||
->withHost($this->domainConfig['hostname']);
|
||||
|
||||
return new JsonResponse([
|
||||
'longUrl' => $longUrl,
|
||||
'longUrl' => (string) $longUrl,
|
||||
'shortUrl' => (string) $shortUrl,
|
||||
'shortCode' => $shortCode,
|
||||
]);
|
||||
@@ -82,7 +86,7 @@ class CreateShortcodeAction extends AbstractRestAction
|
||||
$this->logger->warning('Provided Invalid URL.' . PHP_EOL . $e);
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::getRestErrorCodeFromException($e),
|
||||
'message' => sprintf(
|
||||
'message' => \sprintf(
|
||||
$this->translator->translate('Provided URL %s is invalid. Try with a different one.'),
|
||||
$longUrl
|
||||
),
|
||||
@@ -91,7 +95,7 @@ class CreateShortcodeAction extends AbstractRestAction
|
||||
$this->logger->warning('Provided non-unique slug.' . PHP_EOL . $e);
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::getRestErrorCodeFromException($e),
|
||||
'message' => sprintf(
|
||||
'message' => \sprintf(
|
||||
$this->translator->translate('Provided slug %s is already in use. Try with a different one.'),
|
||||
$customSlug
|
||||
),
|
||||
@@ -105,8 +109,10 @@ class CreateShortcodeAction extends AbstractRestAction
|
||||
}
|
||||
}
|
||||
|
||||
private function getOptionalDate(array $postData, string $fieldName)
|
||||
{
|
||||
return isset($postData[$fieldName]) ? new \DateTime($postData[$fieldName]) : null;
|
||||
}
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return CreateShortCodeData
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
abstract protected function buildUrlToShortCodeData(Request $request): CreateShortCodeData;
|
||||
}
|
||||
46
module/Rest/src/Action/ShortCode/CreateShortCodeAction.php
Normal file
46
module/Rest/src/Action/ShortCode/CreateShortCodeAction.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action\ShortCode;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Core\Model\CreateShortCodeData;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Zend\Diactoros\Uri;
|
||||
|
||||
class CreateShortCodeAction extends AbstractCreateShortCodeAction
|
||||
{
|
||||
protected const ROUTE_PATH = '/short-codes';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_POST];
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return CreateShortCodeData
|
||||
* @throws InvalidArgumentException
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function buildUrlToShortCodeData(Request $request): CreateShortCodeData
|
||||
{
|
||||
$postData = (array) $request->getParsedBody();
|
||||
if (! isset($postData['longUrl'])) {
|
||||
throw new InvalidArgumentException($this->translator->translate('A URL was not provided'));
|
||||
}
|
||||
|
||||
return new CreateShortCodeData(
|
||||
new Uri($postData['longUrl']),
|
||||
(array) ($postData['tags'] ?? []),
|
||||
ShortUrlMeta::createFromParams(
|
||||
$this->getOptionalDate($postData, 'validSince'),
|
||||
$this->getOptionalDate($postData, 'validUntil'),
|
||||
$postData['customSlug'] ?? null,
|
||||
isset($postData['maxVisits']) ? (int) $postData['maxVisits'] : null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function getOptionalDate(array $postData, string $fieldName)
|
||||
{
|
||||
return isset($postData[$fieldName]) ? new \DateTime($postData[$fieldName]) : null;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
namespace Shlinkio\Shlink\Rest\Action\ShortCode;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Core\Exception;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
use Zend\Diactoros\Response\EmptyResponse;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
@@ -17,6 +17,9 @@ use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class EditShortCodeAction extends AbstractRestAction
|
||||
{
|
||||
protected const ROUTE_PATH = '/short-codes/{shortCode}';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_PUT];
|
||||
|
||||
/**
|
||||
* @var ShortUrlServiceInterface
|
||||
*/
|
||||
@@ -41,12 +44,11 @@ class EditShortCodeAction extends AbstractRestAction
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param DelegateInterface $delegate
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$postData = (array) $request->getParsedBody();
|
||||
$shortCode = $request->getAttribute('shortCode', '');
|
||||
@@ -1,20 +1,23 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
namespace Shlinkio\Shlink\Rest\Action\ShortCode;
|
||||
|
||||
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\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class EditShortcodeTagsAction extends AbstractRestAction
|
||||
class EditShortCodeTagsAction extends AbstractRestAction
|
||||
{
|
||||
protected const ROUTE_PATH = '/short-codes/{shortCode}/tags';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_PUT];
|
||||
|
||||
/**
|
||||
* @var ShortUrlServiceInterface
|
||||
*/
|
||||
@@ -36,11 +39,10 @@ class EditShortcodeTagsAction extends AbstractRestAction
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param DelegateInterface $delegate
|
||||
* @return null|Response
|
||||
* @return Response
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
$shortCode = $request->getAttribute('shortCode');
|
||||
$bodyParams = $request->getParsedBody();
|
||||
@@ -1,22 +1,25 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
namespace Shlinkio\Shlink\Rest\Action\ShortCode;
|
||||
|
||||
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\Common\Paginator\Util\PaginatorUtilsTrait;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class ListShortcodesAction extends AbstractRestAction
|
||||
class ListShortCodesAction extends AbstractRestAction
|
||||
{
|
||||
use PaginatorUtilsTrait;
|
||||
|
||||
protected const ROUTE_PATH = '/short-codes';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET];
|
||||
|
||||
/**
|
||||
* @var ShortUrlServiceInterface
|
||||
*/
|
||||
@@ -38,11 +41,10 @@ class ListShortcodesAction extends AbstractRestAction
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param DelegateInterface $delegate
|
||||
* @return null|Response
|
||||
* @return Response
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$params = $this->queryToListParams($request->getQueryParams());
|
||||
@@ -61,13 +63,13 @@ class ListShortcodesAction extends AbstractRestAction
|
||||
* @param array $query
|
||||
* @return array
|
||||
*/
|
||||
public function queryToListParams(array $query)
|
||||
private function queryToListParams(array $query): array
|
||||
{
|
||||
return [
|
||||
isset($query['page']) ? $query['page'] : 1,
|
||||
isset($query['searchTerm']) ? $query['searchTerm'] : null,
|
||||
isset($query['tags']) ? $query['tags'] : [],
|
||||
isset($query['orderBy']) ? $query['orderBy'] : null,
|
||||
$query['page'] ?? 1,
|
||||
$query['searchTerm'] ?? null,
|
||||
$query['tags'] ?? [],
|
||||
$query['orderBy'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,24 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
namespace Shlinkio\Shlink\Rest\Action\ShortCode;
|
||||
|
||||
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\Core\Exception\EntityDoesNotExistException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class ResolveUrlAction extends AbstractRestAction
|
||||
{
|
||||
protected const ROUTE_PATH = '/short-codes/{shortCode}';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET];
|
||||
|
||||
/**
|
||||
* @var UrlShortenerInterface
|
||||
*/
|
||||
@@ -37,11 +40,10 @@ class ResolveUrlAction extends AbstractRestAction
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param DelegateInterface $delegate
|
||||
* @return null|Response
|
||||
* @return Response
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
$shortCode = $request->getAttribute('shortCode');
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action\ShortCode;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Core\Model\CreateShortCodeData;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
||||
use Zend\Diactoros\Uri;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class SingleStepCreateShortCodeAction extends AbstractCreateShortCodeAction
|
||||
{
|
||||
protected const ROUTE_PATH = '/short-codes/shorten';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET];
|
||||
|
||||
/**
|
||||
* @var ApiKeyServiceInterface
|
||||
*/
|
||||
private $apiKeyService;
|
||||
|
||||
public function __construct(
|
||||
UrlShortenerInterface $urlShortener,
|
||||
TranslatorInterface $translator,
|
||||
ApiKeyServiceInterface $apiKeyService,
|
||||
array $domainConfig,
|
||||
LoggerInterface $logger = null
|
||||
) {
|
||||
parent::__construct($urlShortener, $translator, $domainConfig, $logger);
|
||||
$this->apiKeyService = $apiKeyService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return CreateShortCodeData
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function buildUrlToShortCodeData(Request $request): CreateShortCodeData
|
||||
{
|
||||
$query = $request->getQueryParams();
|
||||
|
||||
// Check provided API key
|
||||
$apiKey = $this->apiKeyService->getByKey($query['apiKey'] ?? '');
|
||||
if ($apiKey === null || ! $apiKey->isValid()) {
|
||||
throw new InvalidArgumentException(
|
||||
$this->translator->translate('No API key was provided or it is not valid')
|
||||
);
|
||||
}
|
||||
|
||||
if (! isset($query['longUrl'])) {
|
||||
throw new InvalidArgumentException($this->translator->translate('A URL was not provided'));
|
||||
}
|
||||
|
||||
return new CreateShortCodeData(new Uri($query['longUrl']));
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action\Tag;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -13,6 +12,9 @@ use Zend\Diactoros\Response\JsonResponse;
|
||||
|
||||
class CreateTagsAction extends AbstractRestAction
|
||||
{
|
||||
protected const ROUTE_PATH = '/tags';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_POST];
|
||||
|
||||
/**
|
||||
* @var TagServiceInterface
|
||||
*/
|
||||
@@ -29,15 +31,14 @@ class CreateTagsAction extends AbstractRestAction
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param DelegateInterface $delegate
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$body = $request->getParsedBody();
|
||||
$tags = isset($body['tags']) ? $body['tags'] : [];
|
||||
$tags = $body['tags'] ?? [];
|
||||
|
||||
return new JsonResponse([
|
||||
'tags' => [
|
||||
|
||||
@@ -3,7 +3,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action\Tag;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -13,6 +12,9 @@ use Zend\Diactoros\Response\EmptyResponse;
|
||||
|
||||
class DeleteTagsAction extends AbstractRestAction
|
||||
{
|
||||
protected const ROUTE_PATH = '/tags';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_DELETE];
|
||||
|
||||
/**
|
||||
* @var TagServiceInterface
|
||||
*/
|
||||
@@ -29,14 +31,13 @@ class DeleteTagsAction extends AbstractRestAction
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param DelegateInterface $delegate
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$query = $request->getQueryParams();
|
||||
$tags = isset($query['tags']) ? $query['tags'] : [];
|
||||
$tags = $query['tags'] ?? [];
|
||||
|
||||
$this->tagService->deleteTags($tags);
|
||||
return new EmptyResponse();
|
||||
|
||||
@@ -3,7 +3,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action\Tag;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -13,6 +12,9 @@ use Zend\Diactoros\Response\JsonResponse;
|
||||
|
||||
class ListTagsAction extends AbstractRestAction
|
||||
{
|
||||
protected const ROUTE_PATH = '/tags';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET];
|
||||
|
||||
/**
|
||||
* @var TagServiceInterface
|
||||
*/
|
||||
@@ -29,12 +31,11 @@ class ListTagsAction extends AbstractRestAction
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param DelegateInterface $delegate
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
return new JsonResponse([
|
||||
'tags' => [
|
||||
|
||||
@@ -3,7 +3,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action\Tag;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -17,6 +16,9 @@ use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class UpdateTagAction extends AbstractRestAction
|
||||
{
|
||||
protected const ROUTE_PATH = '/tags';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_PUT];
|
||||
|
||||
/**
|
||||
* @var TagServiceInterface
|
||||
*/
|
||||
@@ -41,12 +43,11 @@ class UpdateTagAction extends AbstractRestAction
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param DelegateInterface $delegate
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$body = $request->getParsedBody();
|
||||
if (! isset($body['oldName'], $body['newName'])) {
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
namespace Shlinkio\Shlink\Rest\Action\Visit;
|
||||
|
||||
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\Common\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class GetVisitsAction extends AbstractRestAction
|
||||
{
|
||||
protected const ROUTE_PATH = '/short-codes/{shortCode}/visits';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET];
|
||||
|
||||
/**
|
||||
* @var VisitsTrackerInterface
|
||||
*/
|
||||
@@ -37,11 +40,10 @@ class GetVisitsAction extends AbstractRestAction
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param DelegateInterface $delegate
|
||||
* @return null|Response
|
||||
* @return Response
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
$shortCode = $request->getAttribute('shortCode');
|
||||
$startDate = $this->getDateQueryParam($request, 'startDate');
|
||||
@@ -92,7 +92,7 @@ class JWTService implements JWTServiceInterface
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
protected function encode(array $data): string
|
||||
private function encode(array $data): string
|
||||
{
|
||||
return JWT::encode($data, $this->appOptions->getSecretKey(), self::DEFAULT_ENCRYPTION_ALG);
|
||||
}
|
||||
@@ -101,7 +101,7 @@ class JWTService implements JWTServiceInterface
|
||||
* @param string $jwt
|
||||
* @return array
|
||||
*/
|
||||
protected function decode(string $jwt): array
|
||||
private function decode(string $jwt): array
|
||||
{
|
||||
return (array) JWT::decode($jwt, $this->appOptions->getSecretKey(), [self::DEFAULT_ENCRYPTION_ALG]);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class ApiKey extends AbstractEntity
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
public function getKey(): string
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class ApiKey extends AbstractEntity
|
||||
* @param string $key
|
||||
* @return $this
|
||||
*/
|
||||
public function setKey($key)
|
||||
public function setKey($key): self
|
||||
{
|
||||
$this->key = $key;
|
||||
return $this;
|
||||
@@ -62,7 +62,7 @@ class ApiKey extends AbstractEntity
|
||||
/**
|
||||
* @return \DateTime|null
|
||||
*/
|
||||
public function getExpirationDate()
|
||||
public function getExpirationDate(): ?\DateTime
|
||||
{
|
||||
return $this->expirationDate;
|
||||
}
|
||||
@@ -71,7 +71,7 @@ class ApiKey extends AbstractEntity
|
||||
* @param \DateTime $expirationDate
|
||||
* @return $this
|
||||
*/
|
||||
public function setExpirationDate($expirationDate)
|
||||
public function setExpirationDate($expirationDate): self
|
||||
{
|
||||
$this->expirationDate = $expirationDate;
|
||||
return $this;
|
||||
@@ -80,9 +80,9 @@ class ApiKey extends AbstractEntity
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isExpired()
|
||||
public function isExpired(): bool
|
||||
{
|
||||
if (! isset($this->expirationDate)) {
|
||||
if ($this->expirationDate === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ class ApiKey extends AbstractEntity
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isEnabled()
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
@@ -101,7 +101,7 @@ class ApiKey extends AbstractEntity
|
||||
* @param boolean $enabled
|
||||
* @return $this
|
||||
*/
|
||||
public function setEnabled($enabled)
|
||||
public function setEnabled($enabled): self
|
||||
{
|
||||
$this->enabled = $enabled;
|
||||
return $this;
|
||||
@@ -112,7 +112,7 @@ class ApiKey extends AbstractEntity
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function disable()
|
||||
public function disable(): self
|
||||
{
|
||||
return $this->setEnabled(false);
|
||||
}
|
||||
@@ -122,17 +122,17 @@ class ApiKey extends AbstractEntity
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid()
|
||||
public function isValid(): bool
|
||||
{
|
||||
return $this->isEnabled() && ! $this->isExpired();
|
||||
}
|
||||
|
||||
/**
|
||||
* The string repesentation of an API key is the key itself
|
||||
* The string representation of an API key is the key itself
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class JsonErrorResponseGenerator implements ErrorResponseGeneratorInterface, Sta
|
||||
* @return Response
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __invoke($e, Request $request, Response $response)
|
||||
public function __invoke(?\Throwable $e, Request $request, Response $response)
|
||||
{
|
||||
$status = $response->getStatusCode();
|
||||
$responsePhrase = $status < 400 ? 'Internal Server Error' : $response->getReasonPhrase();
|
||||
@@ -32,8 +32,8 @@ class JsonErrorResponseGenerator implements ErrorResponseGeneratorInterface, Sta
|
||||
], $status);
|
||||
}
|
||||
|
||||
protected function responsePhraseToCode(string $responsePhrase): string
|
||||
private function responsePhraseToCode(string $responsePhrase): string
|
||||
{
|
||||
return strtoupper(str_replace(' ', '_', $responsePhrase));
|
||||
return \strtoupper(\str_replace(' ', '_', $responsePhrase));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Rest\Middleware;
|
||||
|
||||
use Fig\Http\Message\RequestMethodInterface;
|
||||
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\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Rest\Exception\RuntimeException;
|
||||
|
||||
class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterface
|
||||
@@ -17,11 +17,11 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param DelegateInterface $delegate
|
||||
* @param RequestHandlerInterface $handler
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||
{
|
||||
$method = $request->getMethod();
|
||||
$currentParams = $request->getParsedBody();
|
||||
@@ -32,16 +32,16 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac
|
||||
self::METHOD_HEAD,
|
||||
self::METHOD_OPTIONS,
|
||||
], true)) {
|
||||
return $delegate->process($request);
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
// If the accepted content is JSON, try to parse the body from JSON
|
||||
$contentType = $this->getRequestContentType($request);
|
||||
if (\in_array($contentType, ['application/json', 'text/json', 'application/x-json'], true)) {
|
||||
return $delegate->process($this->parseFromJson($request));
|
||||
return $handler->handle($this->parseFromJson($request));
|
||||
}
|
||||
|
||||
return $delegate->process($this->parseFromUrlEncoded($request));
|
||||
return $handler->handle($this->parseFromUrlEncoded($request));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,13 +4,12 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Rest\Middleware;
|
||||
|
||||
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\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Shlinkio\Shlink\Rest\Action\AuthenticateAction;
|
||||
use Shlinkio\Shlink\Rest\Authentication\JWTServiceInterface;
|
||||
use Shlinkio\Shlink\Rest\Exception\AuthenticationException;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
@@ -21,7 +20,7 @@ use Zend\Stdlib\ErrorHandler;
|
||||
|
||||
class CheckAuthenticationMiddleware implements MiddlewareInterface, StatusCodeInterface
|
||||
{
|
||||
const AUTHORIZATION_HEADER = 'Authorization';
|
||||
public const AUTHORIZATION_HEADER = 'Authorization';
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
@@ -35,14 +34,20 @@ class CheckAuthenticationMiddleware implements MiddlewareInterface, StatusCodeIn
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $routesWhitelist;
|
||||
|
||||
public function __construct(
|
||||
JWTServiceInterface $jwtService,
|
||||
TranslatorInterface $translator,
|
||||
array $routesWhitelist,
|
||||
LoggerInterface $logger = null
|
||||
) {
|
||||
$this->translator = $translator;
|
||||
$this->jwtService = $jwtService;
|
||||
$this->routesWhitelist = $routesWhitelist;
|
||||
$this->logger = $logger ?: new NullLogger();
|
||||
}
|
||||
|
||||
@@ -51,23 +56,23 @@ class CheckAuthenticationMiddleware implements MiddlewareInterface, StatusCodeIn
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param DelegateInterface $delegate
|
||||
* @param RequestHandlerInterface $handler
|
||||
*
|
||||
* @return Response
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \ErrorException
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||
{
|
||||
// If current route is the authenticate route or an OPTIONS request, continue to the next middleware
|
||||
/** @var RouteResult|null $routeResult */
|
||||
$routeResult = $request->getAttribute(RouteResult::class);
|
||||
if ($routeResult === null
|
||||
|| $routeResult->isFailure()
|
||||
|| $routeResult->getMatchedRouteName() === AuthenticateAction::class
|
||||
|| $request->getMethod() === 'OPTIONS'
|
||||
|| \in_array($routeResult->getMatchedRouteName(), $this->routesWhitelist, true)
|
||||
) {
|
||||
return $delegate->process($request);
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
// Check that the auth header was provided, and that it belongs to a non-expired token
|
||||
@@ -77,22 +82,22 @@ class CheckAuthenticationMiddleware implements MiddlewareInterface, StatusCodeIn
|
||||
|
||||
// Get token making sure the an authorization type is provided
|
||||
$authToken = $request->getHeaderLine(self::AUTHORIZATION_HEADER);
|
||||
$authTokenParts = explode(' ', $authToken);
|
||||
if (count($authTokenParts) === 1) {
|
||||
$authTokenParts = \explode(' ', $authToken);
|
||||
if (\count($authTokenParts) === 1) {
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::INVALID_AUTHORIZATION_ERROR,
|
||||
'message' => sprintf($this->translator->translate(
|
||||
'message' => \sprintf($this->translator->translate(
|
||||
'You need to provide the Bearer type in the %s header.'
|
||||
), self::AUTHORIZATION_HEADER),
|
||||
], self::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// Make sure the authorization type is Bearer
|
||||
list($authType, $jwt) = $authTokenParts;
|
||||
if (strtolower($authType) !== 'bearer') {
|
||||
[$authType, $jwt] = $authTokenParts;
|
||||
if (\strtolower($authType) !== 'bearer') {
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::INVALID_AUTHORIZATION_ERROR,
|
||||
'message' => sprintf($this->translator->translate(
|
||||
'message' => \sprintf($this->translator->translate(
|
||||
'Provided authorization type %s is not supported. Use Bearer instead.'
|
||||
), $authType),
|
||||
], self::STATUS_UNAUTHORIZED);
|
||||
@@ -107,7 +112,7 @@ class CheckAuthenticationMiddleware implements MiddlewareInterface, StatusCodeIn
|
||||
|
||||
// Update the token expiration and continue to next middleware
|
||||
$jwt = $this->jwtService->refresh($jwt);
|
||||
$response = $delegate->process($request);
|
||||
$response = $handler->handle($request);
|
||||
|
||||
// Return the response with the updated token on it
|
||||
return $response->withHeader(self::AUTHORIZATION_HEADER, 'Bearer ' . $jwt);
|
||||
@@ -119,11 +124,15 @@ class CheckAuthenticationMiddleware implements MiddlewareInterface, StatusCodeIn
|
||||
}
|
||||
}
|
||||
|
||||
protected function createTokenErrorResponse()
|
||||
/**
|
||||
* @return JsonResponse
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function createTokenErrorResponse(): JsonResponse
|
||||
{
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::INVALID_AUTH_TOKEN_ERROR,
|
||||
'message' => sprintf(
|
||||
'message' => \sprintf(
|
||||
$this->translator->translate(
|
||||
'Missing or invalid auth token provided. Perform a new authentication request and send provided '
|
||||
. 'token on every new request on the "%s" header'
|
||||
|
||||
@@ -4,10 +4,10 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Rest\Middleware;
|
||||
|
||||
use Fig\Http\Message\RequestMethodInterface;
|
||||
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\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class CrossDomainMiddleware implements MiddlewareInterface, RequestMethodInterface
|
||||
{
|
||||
@@ -16,15 +16,15 @@ class CrossDomainMiddleware implements MiddlewareInterface, RequestMethodInterfa
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param DelegateInterface $delegate
|
||||
* @param RequestHandlerInterface $handler
|
||||
*
|
||||
* @return Response
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||
{
|
||||
/** @var Response $response */
|
||||
$response = $delegate->process($request);
|
||||
$response = $handler->handle($request);
|
||||
if (! $request->hasHeader('Origin')) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Middleware;
|
||||
|
||||
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\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class PathVersionMiddleware implements MiddlewareInterface
|
||||
{
|
||||
@@ -15,32 +15,21 @@ class PathVersionMiddleware implements MiddlewareInterface
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param DelegateInterface $delegate
|
||||
* @param RequestHandlerInterface $handler
|
||||
*
|
||||
* @return Response
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||
{
|
||||
$uri = $request->getUri();
|
||||
$path = $uri->getPath();
|
||||
|
||||
// TODO Workaround... Do not process the request if it does not start with rest
|
||||
if (\strpos($path, '/rest') !== 0) {
|
||||
return $delegate->process($request);
|
||||
}
|
||||
|
||||
// If the path does not begin with the version number, prepend v1 by default for BC compatibility purposes
|
||||
if (\strpos($path, '/rest/v') !== 0) {
|
||||
$parts = \explode('/', $path);
|
||||
// Remove the first empty part and the rest part
|
||||
\array_shift($parts);
|
||||
\array_shift($parts);
|
||||
// Prepend the version prefix
|
||||
\array_unshift($parts, '/rest/v1');
|
||||
|
||||
$request = $request->withUri($uri->withPath(\implode('/', $parts)));
|
||||
if (\strpos($path, '/v') !== 0) {
|
||||
$request = $request->withUri($uri->withPath('/v1' . $uri->getPath()));
|
||||
}
|
||||
|
||||
return $delegate->process($request);
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Middleware\ShortCode;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
|
||||
class CreateShortCodeContentNegotiationMiddleware implements MiddlewareInterface
|
||||
{
|
||||
private const PLAIN_TEXT = 'text';
|
||||
private const JSON = 'json';
|
||||
|
||||
/**
|
||||
* Process an incoming server request and return a response, optionally delegating
|
||||
* response creation to a handler.
|
||||
* @throws \RuntimeException
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$response = $handler->handle($request);
|
||||
|
||||
// If the response is not JSON, return it as is
|
||||
if (! $response instanceof JsonResponse) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$query = $request->getQueryParams();
|
||||
$acceptedType = isset($query['format'])
|
||||
? $this->determineAcceptTypeFromQuery($query)
|
||||
: $this->determineAcceptTypeFromHeader($request->getHeaderLine('Accept'));
|
||||
|
||||
// If JSON was requested, return the response from next handler as is
|
||||
if ($acceptedType === self::JSON) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
// If requested, return a plain text response containing the short URL only
|
||||
$resp = (new Response())->withHeader('Content-Type', 'text/plain');
|
||||
$body = $resp->getBody();
|
||||
$body->write($this->determineBody($response));
|
||||
$body->rewind();
|
||||
return $resp;
|
||||
}
|
||||
|
||||
private function determineAcceptTypeFromQuery(array $query): string
|
||||
{
|
||||
if (! isset($query['format'])) {
|
||||
return self::JSON;
|
||||
}
|
||||
|
||||
$format = \strtolower((string) $query['format']);
|
||||
return $format === 'txt' ? self::PLAIN_TEXT : self::JSON;
|
||||
}
|
||||
|
||||
private function determineAcceptTypeFromHeader(string $acceptValue): string
|
||||
{
|
||||
$accepts = \explode(',', $acceptValue);
|
||||
$accept = \strtolower(\array_shift($accepts));
|
||||
return \strpos($accept, 'text/plain') !== false ? self::PLAIN_TEXT : self::JSON;
|
||||
}
|
||||
|
||||
private function determineBody(JsonResponse $resp): string
|
||||
{
|
||||
$payload = $resp->getPayload();
|
||||
return $payload['shortUrl'] ?? $payload['error'] ?? '';
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class ApiKeyService implements ApiKeyServiceInterface
|
||||
public function create(\DateTime $expirationDate = null)
|
||||
{
|
||||
$key = new ApiKey();
|
||||
if (isset($expirationDate)) {
|
||||
if ($expirationDate !== null) {
|
||||
$key->setExpirationDate($expirationDate);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class ApiKeyService implements ApiKeyServiceInterface
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function check($key)
|
||||
public function check(string $key)
|
||||
{
|
||||
/** @var ApiKey|null $apiKey */
|
||||
$apiKey = $this->getByKey($key);
|
||||
@@ -58,7 +58,7 @@ class ApiKeyService implements ApiKeyServiceInterface
|
||||
* @return ApiKey
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function disable($key)
|
||||
public function disable(string $key)
|
||||
{
|
||||
/** @var ApiKey|null $apiKey */
|
||||
$apiKey = $this->getByKey($key);
|
||||
@@ -77,7 +77,7 @@ class ApiKeyService implements ApiKeyServiceInterface
|
||||
* @param bool $enabledOnly Tells if only enabled keys should be returned
|
||||
* @return ApiKey[]
|
||||
*/
|
||||
public function listKeys($enabledOnly = false)
|
||||
public function listKeys(bool $enabledOnly = false)
|
||||
{
|
||||
$conditions = $enabledOnly ? ['enabled' => true] : [];
|
||||
return $this->em->getRepository(ApiKey::class)->findBy($conditions);
|
||||
@@ -89,7 +89,7 @@ class ApiKeyService implements ApiKeyServiceInterface
|
||||
* @param string $key
|
||||
* @return ApiKey|null
|
||||
*/
|
||||
public function getByKey($key)
|
||||
public function getByKey(string $key)
|
||||
{
|
||||
/** @var ApiKey|null $apiKey */
|
||||
$apiKey = $this->em->getRepository(ApiKey::class)->findOneBy([
|
||||
|
||||
@@ -22,7 +22,7 @@ interface ApiKeyServiceInterface
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function check($key);
|
||||
public function check(string $key);
|
||||
|
||||
/**
|
||||
* Disables provided api key
|
||||
@@ -31,7 +31,7 @@ interface ApiKeyServiceInterface
|
||||
* @return ApiKey
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function disable($key);
|
||||
public function disable(string $key);
|
||||
|
||||
/**
|
||||
* Lists all existing api keys
|
||||
@@ -39,7 +39,7 @@ interface ApiKeyServiceInterface
|
||||
* @param bool $enabledOnly Tells if only enabled keys should be returned
|
||||
* @return ApiKey[]
|
||||
*/
|
||||
public function listKeys($enabledOnly = false);
|
||||
public function listKeys(bool $enabledOnly = false);
|
||||
|
||||
/**
|
||||
* Tries to find one API key by its key string
|
||||
@@ -47,5 +47,5 @@ interface ApiKeyServiceInterface
|
||||
* @param string $key
|
||||
* @return ApiKey|null
|
||||
*/
|
||||
public function getByKey($key);
|
||||
public function getByKey(string $key);
|
||||
}
|
||||
|
||||
@@ -9,16 +9,16 @@ use Shlinkio\Shlink\Rest\Exception as Rest;
|
||||
|
||||
class RestUtils
|
||||
{
|
||||
const INVALID_SHORTCODE_ERROR = 'INVALID_SHORTCODE';
|
||||
const INVALID_URL_ERROR = 'INVALID_URL';
|
||||
const INVALID_ARGUMENT_ERROR = 'INVALID_ARGUMENT';
|
||||
const INVALID_SLUG_ERROR = 'INVALID_SLUG';
|
||||
const INVALID_CREDENTIALS_ERROR = 'INVALID_CREDENTIALS';
|
||||
const INVALID_AUTH_TOKEN_ERROR = 'INVALID_AUTH_TOKEN';
|
||||
const INVALID_AUTHORIZATION_ERROR = 'INVALID_AUTHORIZATION';
|
||||
const INVALID_API_KEY_ERROR = 'INVALID_API_KEY';
|
||||
const NOT_FOUND_ERROR = 'NOT_FOUND';
|
||||
const UNKNOWN_ERROR = 'UNKNOWN_ERROR';
|
||||
public const INVALID_SHORTCODE_ERROR = 'INVALID_SHORTCODE';
|
||||
public const INVALID_URL_ERROR = 'INVALID_URL';
|
||||
public const INVALID_ARGUMENT_ERROR = 'INVALID_ARGUMENT';
|
||||
public const INVALID_SLUG_ERROR = 'INVALID_SLUG';
|
||||
public const INVALID_CREDENTIALS_ERROR = 'INVALID_CREDENTIALS';
|
||||
public const INVALID_AUTH_TOKEN_ERROR = 'INVALID_AUTH_TOKEN';
|
||||
public const INVALID_AUTHORIZATION_ERROR = 'INVALID_AUTHORIZATION';
|
||||
public const INVALID_API_KEY_ERROR = 'INVALID_API_KEY';
|
||||
public const NOT_FOUND_ERROR = 'NOT_FOUND';
|
||||
public const UNKNOWN_ERROR = 'UNKNOWN_ERROR';
|
||||
|
||||
public static function getRestErrorCodeFromException(\Throwable $e)
|
||||
{
|
||||
@@ -30,6 +30,7 @@ class RestUtils
|
||||
case $e instanceof Core\NonUniqueSlugException:
|
||||
return self::INVALID_SLUG_ERROR;
|
||||
case $e instanceof Common\InvalidArgumentException:
|
||||
case $e instanceof Core\InvalidArgumentException:
|
||||
case $e instanceof Core\ValidationException:
|
||||
return self::INVALID_ARGUMENT_ERROR;
|
||||
case $e instanceof Rest\AuthenticationException:
|
||||
|
||||
@@ -10,7 +10,6 @@ use Shlinkio\Shlink\Rest\Action\AuthenticateAction;
|
||||
use Shlinkio\Shlink\Rest\Authentication\JWTService;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
|
||||
use ShlinkioTest\Shlink\Common\Util\TestUtils;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
@@ -47,7 +46,7 @@ class AuthenticateActionTest extends TestCase
|
||||
*/
|
||||
public function notProvidingAuthDataReturnsError()
|
||||
{
|
||||
$resp = $this->action->process(ServerRequestFactory::fromGlobals(), TestUtils::createDelegateMock()->reveal());
|
||||
$resp = $this->action->handle(ServerRequestFactory::fromGlobals());
|
||||
$this->assertEquals(400, $resp->getStatusCode());
|
||||
}
|
||||
|
||||
@@ -62,7 +61,7 @@ class AuthenticateActionTest extends TestCase
|
||||
$request = ServerRequestFactory::fromGlobals()->withParsedBody([
|
||||
'apiKey' => 'foo',
|
||||
]);
|
||||
$response = $this->action->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->action->handle($request);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$response->getBody()->rewind();
|
||||
@@ -80,7 +79,7 @@ class AuthenticateActionTest extends TestCase
|
||||
$request = ServerRequestFactory::fromGlobals()->withParsedBody([
|
||||
'apiKey' => 'foo',
|
||||
]);
|
||||
$response = $this->action->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->action->handle($request);
|
||||
$this->assertEquals(401, $response->getStatusCode());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Action;
|
||||
namespace ShlinkioTest\Shlink\Rest\Action\ShortCode;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
@@ -9,17 +9,16 @@ use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\Rest\Action\CreateShortcodeAction;
|
||||
use Shlinkio\Shlink\Rest\Action\ShortCode\CreateShortCodeAction;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
use ShlinkioTest\Shlink\Common\Util\TestUtils;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
use Zend\Diactoros\Uri;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class CreateShortcodeActionTest extends TestCase
|
||||
class CreateShortCodeActionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var CreateShortcodeAction
|
||||
* @var CreateShortCodeAction
|
||||
*/
|
||||
protected $action;
|
||||
/**
|
||||
@@ -30,7 +29,7 @@ class CreateShortcodeActionTest extends TestCase
|
||||
public function setUp()
|
||||
{
|
||||
$this->urlShortener = $this->prophesize(UrlShortener::class);
|
||||
$this->action = new CreateShortcodeAction($this->urlShortener->reveal(), Translator::factory([]), [
|
||||
$this->action = new CreateShortCodeAction($this->urlShortener->reveal(), Translator::factory([]), [
|
||||
'schema' => 'http',
|
||||
'hostname' => 'foo.com',
|
||||
]);
|
||||
@@ -41,10 +40,7 @@ class CreateShortcodeActionTest extends TestCase
|
||||
*/
|
||||
public function missingLongUrlParamReturnsError()
|
||||
{
|
||||
$response = $this->action->process(
|
||||
ServerRequestFactory::fromGlobals(),
|
||||
TestUtils::createDelegateMock()->reveal()
|
||||
);
|
||||
$response = $this->action->handle(ServerRequestFactory::fromGlobals());
|
||||
$this->assertEquals(400, $response->getStatusCode());
|
||||
}
|
||||
|
||||
@@ -60,7 +56,7 @@ class CreateShortcodeActionTest extends TestCase
|
||||
$request = ServerRequestFactory::fromGlobals()->withParsedBody([
|
||||
'longUrl' => 'http://www.domain.com/foo/bar',
|
||||
]);
|
||||
$response = $this->action->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->action->handle($request);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertTrue(strpos($response->getBody()->getContents(), 'http://foo.com/abc123') > 0);
|
||||
}
|
||||
@@ -77,7 +73,7 @@ class CreateShortcodeActionTest extends TestCase
|
||||
$request = ServerRequestFactory::fromGlobals()->withParsedBody([
|
||||
'longUrl' => 'http://www.domain.com/foo/bar',
|
||||
]);
|
||||
$response = $this->action->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->action->handle($request);
|
||||
$this->assertEquals(400, $response->getStatusCode());
|
||||
$this->assertTrue(strpos($response->getBody()->getContents(), RestUtils::INVALID_URL_ERROR) > 0);
|
||||
}
|
||||
@@ -100,7 +96,7 @@ class CreateShortcodeActionTest extends TestCase
|
||||
'longUrl' => 'http://www.domain.com/foo/bar',
|
||||
'customSlug' => 'foo',
|
||||
]);
|
||||
$response = $this->action->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->action->handle($request);
|
||||
$this->assertEquals(400, $response->getStatusCode());
|
||||
$this->assertContains(RestUtils::INVALID_SLUG_ERROR, (string) $response->getBody());
|
||||
}
|
||||
@@ -117,7 +113,7 @@ class CreateShortcodeActionTest extends TestCase
|
||||
$request = ServerRequestFactory::fromGlobals()->withParsedBody([
|
||||
'longUrl' => 'http://www.domain.com/foo/bar',
|
||||
]);
|
||||
$response = $this->action->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->action->handle($request);
|
||||
$this->assertEquals(500, $response->getStatusCode());
|
||||
$this->assertTrue(strpos($response->getBody()->getContents(), RestUtils::UNKNOWN_ERROR) > 0);
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Action;
|
||||
namespace ShlinkioTest\Shlink\Rest\Action\ShortCode;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\EditShortCodeAction;
|
||||
use Shlinkio\Shlink\Rest\Action\ShortCode\EditShortCodeAction;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
@@ -43,7 +42,7 @@ class EditShortCodeActionTest extends TestCase
|
||||
]);
|
||||
|
||||
/** @var JsonResponse $resp */
|
||||
$resp = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal());
|
||||
$resp = $this->action->handle($request);
|
||||
$payload = $resp->getPayload();
|
||||
|
||||
$this->assertEquals(400, $resp->getStatusCode());
|
||||
@@ -65,7 +64,7 @@ class EditShortCodeActionTest extends TestCase
|
||||
);
|
||||
|
||||
/** @var JsonResponse $resp */
|
||||
$resp = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal());
|
||||
$resp = $this->action->handle($request);
|
||||
$payload = $resp->getPayload();
|
||||
|
||||
$this->assertEquals(404, $resp->getStatusCode());
|
||||
@@ -85,7 +84,7 @@ class EditShortCodeActionTest extends TestCase
|
||||
]);
|
||||
$updateMeta = $this->shortUrlService->updateMetadataByShortCode(Argument::cetera())->willReturn(new ShortUrl());
|
||||
|
||||
$resp = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal());
|
||||
$resp = $this->action->handle($request);
|
||||
|
||||
$this->assertEquals(204, $resp->getStatusCode());
|
||||
$updateMeta->shouldHaveBeenCalled();
|
||||
@@ -1,22 +1,21 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Action;
|
||||
namespace ShlinkioTest\Shlink\Rest\Action\ShortCode;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
||||
use Shlinkio\Shlink\Rest\Action\EditShortcodeTagsAction;
|
||||
use ShlinkioTest\Shlink\Common\Util\TestUtils;
|
||||
use Shlinkio\Shlink\Rest\Action\ShortCode\EditShortCodeTagsAction;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class EditShortcodeTagsActionTest extends TestCase
|
||||
class EditShortCodeTagsActionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var EditShortcodeTagsAction
|
||||
* @var EditShortCodeTagsAction
|
||||
*/
|
||||
protected $action;
|
||||
/**
|
||||
@@ -27,7 +26,7 @@ class EditShortcodeTagsActionTest extends TestCase
|
||||
public function setUp()
|
||||
{
|
||||
$this->shortUrlService = $this->prophesize(ShortUrlService::class);
|
||||
$this->action = new EditShortcodeTagsAction($this->shortUrlService->reveal(), Translator::factory([]));
|
||||
$this->action = new EditShortCodeTagsAction($this->shortUrlService->reveal(), Translator::factory([]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,10 +34,7 @@ class EditShortcodeTagsActionTest extends TestCase
|
||||
*/
|
||||
public function notProvidingTagsReturnsError()
|
||||
{
|
||||
$response = $this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', 'abc123'),
|
||||
TestUtils::createDelegateMock()->reveal()
|
||||
);
|
||||
$response = $this->action->handle(ServerRequestFactory::fromGlobals()->withAttribute('shortCode', 'abc123'));
|
||||
$this->assertEquals(400, $response->getStatusCode());
|
||||
}
|
||||
|
||||
@@ -51,10 +47,9 @@ class EditShortcodeTagsActionTest extends TestCase
|
||||
$this->shortUrlService->setTagsByShortCode($shortCode, [])->willThrow(InvalidShortCodeException::class)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$response = $this->action->process(
|
||||
$response = $this->action->handle(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', 'abc123')
|
||||
->withParsedBody(['tags' => []]),
|
||||
TestUtils::createDelegateMock()->reveal()
|
||||
->withParsedBody(['tags' => []])
|
||||
);
|
||||
$this->assertEquals(404, $response->getStatusCode());
|
||||
}
|
||||
@@ -68,10 +63,9 @@ class EditShortcodeTagsActionTest extends TestCase
|
||||
$this->shortUrlService->setTagsByShortCode($shortCode, [])->willReturn(new ShortUrl())
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$response = $this->action->process(
|
||||
$response = $this->action->handle(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', 'abc123')
|
||||
->withParsedBody(['tags' => []]),
|
||||
TestUtils::createDelegateMock()->reveal()
|
||||
->withParsedBody(['tags' => []])
|
||||
);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Action;
|
||||
namespace ShlinkioTest\Shlink\Rest\Action\ShortCode;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
||||
use Shlinkio\Shlink\Rest\Action\ListShortcodesAction;
|
||||
use ShlinkioTest\Shlink\Common\Util\TestUtils;
|
||||
use Shlinkio\Shlink\Rest\Action\ShortCode\ListShortCodesAction;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
use Zend\Paginator\Adapter\ArrayAdapter;
|
||||
@@ -16,7 +15,7 @@ use Zend\Paginator\Paginator;
|
||||
class ListShortCodesActionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ListShortcodesAction
|
||||
* @var ListShortCodesAction
|
||||
*/
|
||||
protected $action;
|
||||
/**
|
||||
@@ -27,7 +26,7 @@ class ListShortCodesActionTest extends TestCase
|
||||
public function setUp()
|
||||
{
|
||||
$this->service = $this->prophesize(ShortUrlService::class);
|
||||
$this->action = new ListShortcodesAction($this->service->reveal(), Translator::factory([]));
|
||||
$this->action = new ListShortCodesAction($this->service->reveal(), Translator::factory([]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,12 +38,9 @@ class ListShortCodesActionTest extends TestCase
|
||||
$this->service->listShortUrls($page, null, [], null)->willReturn(new Paginator(new ArrayAdapter()))
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$response = $this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withQueryParams([
|
||||
'page' => $page,
|
||||
]),
|
||||
TestUtils::createDelegateMock()->reveal()
|
||||
);
|
||||
$response = $this->action->handle(ServerRequestFactory::fromGlobals()->withQueryParams([
|
||||
'page' => $page,
|
||||
]));
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
@@ -57,12 +53,9 @@ class ListShortCodesActionTest extends TestCase
|
||||
$this->service->listShortUrls($page, null, [], null)->willThrow(\Exception::class)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$response = $this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withQueryParams([
|
||||
'page' => $page,
|
||||
]),
|
||||
TestUtils::createDelegateMock()->reveal()
|
||||
);
|
||||
$response = $this->action->handle(ServerRequestFactory::fromGlobals()->withQueryParams([
|
||||
'page' => $page,
|
||||
]));
|
||||
$this->assertEquals(500, $response->getStatusCode());
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Action;
|
||||
namespace ShlinkioTest\Shlink\Rest\Action\ShortCode;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\Rest\Action\ResolveUrlAction;
|
||||
use Shlinkio\Shlink\Rest\Action\ShortCode\ResolveUrlAction;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
use ShlinkioTest\Shlink\Common\Util\TestUtils;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
@@ -41,7 +40,7 @@ class ResolveUrlActionTest extends TestCase
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode);
|
||||
$response = $this->action->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->action->handle($request);
|
||||
$this->assertEquals(404, $response->getStatusCode());
|
||||
$this->assertTrue(strpos($response->getBody()->getContents(), RestUtils::INVALID_ARGUMENT_ERROR) > 0);
|
||||
}
|
||||
@@ -56,7 +55,7 @@ class ResolveUrlActionTest extends TestCase
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode);
|
||||
$response = $this->action->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->action->handle($request);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertTrue(strpos($response->getBody()->getContents(), 'http://domain.com/foo/bar') > 0);
|
||||
}
|
||||
@@ -71,7 +70,7 @@ class ResolveUrlActionTest extends TestCase
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode);
|
||||
$response = $this->action->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->action->handle($request);
|
||||
$this->assertEquals(400, $response->getStatusCode());
|
||||
$this->assertTrue(strpos($response->getBody()->getContents(), RestUtils::INVALID_SHORTCODE_ERROR) > 0);
|
||||
}
|
||||
@@ -86,7 +85,7 @@ class ResolveUrlActionTest extends TestCase
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode);
|
||||
$response = $this->action->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->action->handle($request);
|
||||
$this->assertEquals(500, $response->getStatusCode());
|
||||
$this->assertTrue(strpos($response->getBody()->getContents(), RestUtils::UNKNOWN_ERROR) > 0);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Action\ShortCode;
|
||||
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\ShortCode\SingleStepCreateShortCodeAction;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class SingleStepCreateShortCodeActionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var SingleStepCreateShortCodeAction
|
||||
*/
|
||||
private $action;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
private $urlShortener;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
private $apiKeyService;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->urlShortener = $this->prophesize(UrlShortenerInterface::class);
|
||||
$this->apiKeyService = $this->prophesize(ApiKeyServiceInterface::class);
|
||||
|
||||
$this->action = new SingleStepCreateShortCodeAction(
|
||||
$this->urlShortener->reveal(),
|
||||
Translator::factory([]),
|
||||
$this->apiKeyService->reveal(),
|
||||
[
|
||||
'schema' => 'http',
|
||||
'hostname' => 'foo.com',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideInvalidApiKeys
|
||||
*/
|
||||
public function errorResponseIsReturnedIfInvalidApiKeyIsProvided(?ApiKey $apiKey)
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals()->withQueryParams(['apiKey' => 'abc123']);
|
||||
$findApiKey = $this->apiKeyService->getByKey('abc123')->willReturn($apiKey);
|
||||
|
||||
/** @var JsonResponse $resp */
|
||||
$resp = $this->action->handle($request);
|
||||
$payload = $resp->getPayload();
|
||||
|
||||
$this->assertEquals(400, $resp->getStatusCode());
|
||||
$this->assertEquals('INVALID_ARGUMENT', $payload['error']);
|
||||
$this->assertEquals('No API key was provided or it is not valid', $payload['message']);
|
||||
$findApiKey->shouldHaveBeenCalled();
|
||||
}
|
||||
|
||||
public function provideInvalidApiKeys(): array
|
||||
{
|
||||
return [
|
||||
[null],
|
||||
[(new ApiKey())->disable()],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function errorResponseIsReturnedIfNoUrlIsProvided()
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals()->withQueryParams(['apiKey' => 'abc123']);
|
||||
$findApiKey = $this->apiKeyService->getByKey('abc123')->willReturn(new ApiKey());
|
||||
|
||||
/** @var JsonResponse $resp */
|
||||
$resp = $this->action->handle($request);
|
||||
$payload = $resp->getPayload();
|
||||
|
||||
$this->assertEquals(400, $resp->getStatusCode());
|
||||
$this->assertEquals('INVALID_ARGUMENT', $payload['error']);
|
||||
$this->assertEquals('A URL was not provided', $payload['message']);
|
||||
$findApiKey->shouldHaveBeenCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function properDataIsPassedWhenGeneratingShortCode()
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals()->withQueryParams([
|
||||
'apiKey' => 'abc123',
|
||||
'longUrl' => 'http://foobar.com',
|
||||
]);
|
||||
$findApiKey = $this->apiKeyService->getByKey('abc123')->willReturn(new ApiKey());
|
||||
$generateShortCode = $this->urlShortener->urlToShortCode(
|
||||
Argument::that(function (UriInterface $argument) {
|
||||
Assert::assertEquals('http://foobar.com', (string) $argument);
|
||||
return $argument;
|
||||
}),
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
$resp = $this->action->handle($request);
|
||||
|
||||
$this->assertEquals(200, $resp->getStatusCode());
|
||||
$findApiKey->shouldHaveBeenCalled();
|
||||
$generateShortCode->shouldHaveBeenCalled();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
namespace ShlinkioTest\Shlink\Rest\Action\Tag;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
@@ -40,7 +39,7 @@ class CreateTagsActionTest extends TestCase
|
||||
/** @var MethodProphecy $deleteTags */
|
||||
$deleteTags = $this->tagService->createTags($tags ?: [])->willReturn(new ArrayCollection());
|
||||
|
||||
$response = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal());
|
||||
$response = $this->action->handle($request);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$deleteTags->shouldHaveBeenCalled();
|
||||
|
||||
@@ -3,7 +3,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Action\Tag;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
@@ -39,7 +38,7 @@ class DeleteTagsActionTest extends TestCase
|
||||
/** @var MethodProphecy $deleteTags */
|
||||
$deleteTags = $this->tagService->deleteTags($tags ?: []);
|
||||
|
||||
$response = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal());
|
||||
$response = $this->action->handle($request);
|
||||
|
||||
$this->assertEquals(204, $response->getStatusCode());
|
||||
$deleteTags->shouldHaveBeenCalled();
|
||||
|
||||
@@ -3,7 +3,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Action\Tag;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
@@ -37,10 +36,7 @@ class ListTagsActionTest extends TestCase
|
||||
/** @var MethodProphecy $listTags */
|
||||
$listTags = $this->tagService->listTags()->willReturn([new Tag('foo'), new Tag('bar')]);
|
||||
|
||||
$resp = $this->action->process(
|
||||
ServerRequestFactory::fromGlobals(),
|
||||
$this->prophesize(DelegateInterface::class)->reveal()
|
||||
);
|
||||
$resp = $this->action->handle(ServerRequestFactory::fromGlobals());
|
||||
|
||||
$this->assertEquals([
|
||||
'tags' => [
|
||||
|
||||
@@ -3,7 +3,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Action\Tag;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
@@ -39,7 +38,7 @@ class UpdateTagActionTest extends TestCase
|
||||
public function whenInvalidParamsAreProvidedAnErrorIsReturned(array $bodyParams)
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals()->withParsedBody($bodyParams);
|
||||
$resp = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal());
|
||||
$resp = $this->action->handle($request);
|
||||
|
||||
$this->assertEquals(400, $resp->getStatusCode());
|
||||
}
|
||||
@@ -65,7 +64,7 @@ class UpdateTagActionTest extends TestCase
|
||||
/** @var MethodProphecy $rename */
|
||||
$rename = $this->tagService->renameTag('foo', 'bar')->willThrow(EntityDoesNotExistException::class);
|
||||
|
||||
$resp = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal());
|
||||
$resp = $this->action->handle($request);
|
||||
|
||||
$this->assertEquals(404, $resp->getStatusCode());
|
||||
$rename->shouldHaveBeenCalled();
|
||||
@@ -83,7 +82,7 @@ class UpdateTagActionTest extends TestCase
|
||||
/** @var MethodProphecy $rename */
|
||||
$rename = $this->tagService->renameTag('foo', 'bar')->willReturn(new Tag());
|
||||
|
||||
$resp = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal());
|
||||
$resp = $this->action->handle($request);
|
||||
|
||||
$this->assertEquals(204, $resp->getStatusCode());
|
||||
$rename->shouldHaveBeenCalled();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Action;
|
||||
namespace ShlinkioTest\Shlink\Rest\Action\Visit;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
@@ -9,8 +9,7 @@ use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
use Shlinkio\Shlink\Rest\Action\GetVisitsAction;
|
||||
use ShlinkioTest\Shlink\Common\Util\TestUtils;
|
||||
use Shlinkio\Shlink\Rest\Action\Visit\GetVisitsAction;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
@@ -40,10 +39,7 @@ class GetVisitsActionTest extends TestCase
|
||||
$this->visitsTracker->info($shortCode, Argument::type(DateRange::class))->willReturn([])
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$response = $this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode),
|
||||
TestUtils::createDelegateMock()->reveal()
|
||||
);
|
||||
$response = $this->action->handle(ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode));
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
@@ -57,10 +53,7 @@ class GetVisitsActionTest extends TestCase
|
||||
InvalidArgumentException::class
|
||||
)->shouldBeCalledTimes(1);
|
||||
|
||||
$response = $this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode),
|
||||
TestUtils::createDelegateMock()->reveal()
|
||||
);
|
||||
$response = $this->action->handle(ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode));
|
||||
$this->assertEquals(404, $response->getStatusCode());
|
||||
}
|
||||
|
||||
@@ -74,10 +67,7 @@ class GetVisitsActionTest extends TestCase
|
||||
\Exception::class
|
||||
)->shouldBeCalledTimes(1);
|
||||
|
||||
$response = $this->action->process(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode),
|
||||
TestUtils::createDelegateMock()->reveal()
|
||||
);
|
||||
$response = $this->action->handle(ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode));
|
||||
$this->assertEquals(500, $response->getStatusCode());
|
||||
}
|
||||
|
||||
@@ -91,10 +81,9 @@ class GetVisitsActionTest extends TestCase
|
||||
->willReturn([])
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$response = $this->action->process(
|
||||
$response = $this->action->handle(
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode)
|
||||
->withQueryParams(['endDate' => '2016-01-01 00:00:00']),
|
||||
TestUtils::createDelegateMock()->reveal()
|
||||
->withQueryParams(['endDate' => '2016-01-01 00:00:00'])
|
||||
);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
@@ -3,11 +3,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Middleware;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Rest\Middleware\BodyParserMiddleware;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
@@ -31,9 +31,9 @@ class BodyParserMiddlewareTest extends TestCase
|
||||
public function requestsFromOtherMethodsJustFallbackToNextMiddleware()
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals()->withMethod('GET');
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
/** @var MethodProphecy $process */
|
||||
$process = $delegate->process($request)->willReturn(new Response());
|
||||
$process = $delegate->handle($request)->willReturn(new Response());
|
||||
|
||||
$this->middleware->process($request, $delegate->reveal());
|
||||
|
||||
@@ -51,9 +51,9 @@ class BodyParserMiddlewareTest extends TestCase
|
||||
$request = ServerRequestFactory::fromGlobals()->withMethod('PUT')
|
||||
->withBody($body)
|
||||
->withHeader('content-type', 'application/json');
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
/** @var MethodProphecy $process */
|
||||
$process = $delegate->process(Argument::type(ServerRequestInterface::class))->will(
|
||||
$process = $delegate->handle(Argument::type(ServerRequestInterface::class))->will(
|
||||
function (array $args) use ($test) {
|
||||
/** @var ServerRequestInterface $req */
|
||||
$req = array_shift($args);
|
||||
@@ -82,9 +82,9 @@ class BodyParserMiddlewareTest extends TestCase
|
||||
$body->write('foo=bar&bar[]=one&bar[]=5');
|
||||
$request = ServerRequestFactory::fromGlobals()->withMethod('PUT')
|
||||
->withBody($body);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
/** @var MethodProphecy $process */
|
||||
$process = $delegate->process(Argument::type(ServerRequestInterface::class))->will(
|
||||
$process = $delegate->handle(Argument::type(ServerRequestInterface::class))->will(
|
||||
function (array $args) use ($test) {
|
||||
/** @var ServerRequestInterface $req */
|
||||
$req = array_shift($args);
|
||||
|
||||
@@ -3,10 +3,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Middleware;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\AuthenticateAction;
|
||||
use Shlinkio\Shlink\Rest\Authentication\JWTService;
|
||||
use Shlinkio\Shlink\Rest\Middleware\CheckAuthenticationMiddleware;
|
||||
@@ -16,7 +16,6 @@ use Zend\Diactoros\ServerRequestFactory;
|
||||
use Zend\Expressive\Router\Route;
|
||||
use Zend\Expressive\Router\RouteResult;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
use function Zend\Stratigility\middleware;
|
||||
|
||||
class CheckAuthenticationMiddlewareTest extends TestCase
|
||||
@@ -38,9 +37,11 @@ class CheckAuthenticationMiddlewareTest extends TestCase
|
||||
public function setUp()
|
||||
{
|
||||
$this->jwtService = $this->prophesize(JWTService::class);
|
||||
$this->middleware = new CheckAuthenticationMiddleware($this->jwtService->reveal(), Translator::factory([]));
|
||||
$this->dummyMiddleware = middleware(function ($request, $handler) {
|
||||
return new Response\EmptyResponse;
|
||||
$this->middleware = new CheckAuthenticationMiddleware($this->jwtService->reveal(), Translator::factory([]), [
|
||||
AuthenticateAction::class,
|
||||
]);
|
||||
$this->dummyMiddleware = middleware(function () {
|
||||
return new Response\EmptyResponse();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -50,9 +51,9 @@ class CheckAuthenticationMiddlewareTest extends TestCase
|
||||
public function someWhiteListedSituationsFallbackToNextMiddleware()
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals();
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
/** @var MethodProphecy $process */
|
||||
$process = $delegate->process($request)->willReturn(new Response());
|
||||
$process = $delegate->handle($request)->willReturn(new Response());
|
||||
|
||||
$this->middleware->process($request, $delegate->reveal());
|
||||
$process->shouldHaveBeenCalledTimes(1);
|
||||
@@ -61,9 +62,9 @@ class CheckAuthenticationMiddlewareTest extends TestCase
|
||||
RouteResult::class,
|
||||
RouteResult::fromRouteFailure(['GET'])
|
||||
);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
/** @var MethodProphecy $process */
|
||||
$process = $delegate->process($request)->willReturn(new Response());
|
||||
$process = $delegate->handle($request)->willReturn(new Response());
|
||||
$this->middleware->process($request, $delegate->reveal());
|
||||
$process->shouldHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -76,9 +77,9 @@ class CheckAuthenticationMiddlewareTest extends TestCase
|
||||
AuthenticateAction::class
|
||||
))
|
||||
);
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
/** @var MethodProphecy $process */
|
||||
$process = $delegate->process($request)->willReturn(new Response());
|
||||
$process = $delegate->handle($request)->willReturn(new Response());
|
||||
$this->middleware->process($request, $delegate->reveal());
|
||||
$process->shouldHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -86,9 +87,9 @@ class CheckAuthenticationMiddlewareTest extends TestCase
|
||||
RouteResult::class,
|
||||
RouteResult::fromRoute(new Route('bar', $this->dummyMiddleware), [])
|
||||
)->withMethod('OPTIONS');
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
/** @var MethodProphecy $process */
|
||||
$process = $delegate->process($request)->willReturn(new Response());
|
||||
$process = $delegate->handle($request)->willReturn(new Response());
|
||||
$this->middleware->process($request, $delegate->reveal());
|
||||
$process->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
@@ -102,7 +103,7 @@ class CheckAuthenticationMiddlewareTest extends TestCase
|
||||
RouteResult::class,
|
||||
RouteResult::fromRoute(new Route('bar', $this->dummyMiddleware), [])
|
||||
);
|
||||
$response = $this->middleware->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->middleware->process($request, TestUtils::createReqHandlerMock()->reveal());
|
||||
$this->assertEquals(401, $response->getStatusCode());
|
||||
}
|
||||
|
||||
@@ -117,7 +118,7 @@ class CheckAuthenticationMiddlewareTest extends TestCase
|
||||
RouteResult::fromRoute(new Route('bar', $this->dummyMiddleware), [])
|
||||
)->withHeader(CheckAuthenticationMiddleware::AUTHORIZATION_HEADER, $authToken);
|
||||
|
||||
$response = $this->middleware->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->middleware->process($request, TestUtils::createReqHandlerMock()->reveal());
|
||||
|
||||
$this->assertEquals(401, $response->getStatusCode());
|
||||
$this->assertTrue(strpos($response->getBody()->getContents(), 'You need to provide the Bearer type') > 0);
|
||||
@@ -134,7 +135,7 @@ class CheckAuthenticationMiddlewareTest extends TestCase
|
||||
RouteResult::fromRoute(new Route('bar', $this->dummyMiddleware), [])
|
||||
)->withHeader(CheckAuthenticationMiddleware::AUTHORIZATION_HEADER, 'Basic ' . $authToken);
|
||||
|
||||
$response = $this->middleware->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->middleware->process($request, TestUtils::createReqHandlerMock()->reveal());
|
||||
|
||||
$this->assertEquals(401, $response->getStatusCode());
|
||||
$this->assertTrue(
|
||||
@@ -154,7 +155,7 @@ class CheckAuthenticationMiddlewareTest extends TestCase
|
||||
)->withHeader(CheckAuthenticationMiddleware::AUTHORIZATION_HEADER, 'Bearer ' . $authToken);
|
||||
$this->jwtService->verify($authToken)->willReturn(false)->shouldBeCalledTimes(1);
|
||||
|
||||
$response = $this->middleware->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
$response = $this->middleware->process($request, TestUtils::createReqHandlerMock()->reveal());
|
||||
$this->assertEquals(401, $response->getStatusCode());
|
||||
}
|
||||
|
||||
@@ -171,9 +172,9 @@ class CheckAuthenticationMiddlewareTest extends TestCase
|
||||
$this->jwtService->verify($authToken)->willReturn(true)->shouldBeCalledTimes(1);
|
||||
$this->jwtService->refresh($authToken)->willReturn($authToken)->shouldBeCalledTimes(1);
|
||||
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
/** @var MethodProphecy $process */
|
||||
$process = $delegate->process($request)->willReturn(new Response());
|
||||
$process = $delegate->handle($request)->willReturn(new Response());
|
||||
$resp = $this->middleware->process($request, $delegate->reveal());
|
||||
|
||||
$process->shouldHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -3,10 +3,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Middleware;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Rest\Middleware\CrossDomainMiddleware;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
@@ -25,7 +25,7 @@ class CrossDomainMiddlewareTest extends TestCase
|
||||
public function setUp()
|
||||
{
|
||||
$this->middleware = new CrossDomainMiddleware();
|
||||
$this->delegate = $this->prophesize(DelegateInterface::class);
|
||||
$this->delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,7 +34,7 @@ class CrossDomainMiddlewareTest extends TestCase
|
||||
public function nonCrossDomainRequestsAreNotAffected()
|
||||
{
|
||||
$originalResponse = new Response();
|
||||
$this->delegate->process(Argument::any())->willReturn($originalResponse)->shouldbeCalledTimes(1);
|
||||
$this->delegate->handle(Argument::any())->willReturn($originalResponse)->shouldbeCalledTimes(1);
|
||||
|
||||
$response = $this->middleware->process(ServerRequestFactory::fromGlobals(), $this->delegate->reveal());
|
||||
$this->assertSame($originalResponse, $response);
|
||||
@@ -50,7 +50,7 @@ class CrossDomainMiddlewareTest extends TestCase
|
||||
public function anyRequestIncludesTheAllowAccessHeader()
|
||||
{
|
||||
$originalResponse = new Response();
|
||||
$this->delegate->process(Argument::any())->willReturn($originalResponse)->shouldbeCalledTimes(1);
|
||||
$this->delegate->handle(Argument::any())->willReturn($originalResponse)->shouldbeCalledTimes(1);
|
||||
|
||||
$response = $this->middleware->process(
|
||||
ServerRequestFactory::fromGlobals()->withHeader('Origin', 'local'),
|
||||
@@ -70,7 +70,7 @@ class CrossDomainMiddlewareTest extends TestCase
|
||||
{
|
||||
$originalResponse = new Response();
|
||||
$request = ServerRequestFactory::fromGlobals()->withMethod('OPTIONS')->withHeader('Origin', 'local');
|
||||
$this->delegate->process(Argument::any())->willReturn($originalResponse)->shouldbeCalledTimes(1);
|
||||
$this->delegate->handle(Argument::any())->willReturn($originalResponse)->shouldbeCalledTimes(1);
|
||||
|
||||
$response = $this->middleware->process($request, $this->delegate->reveal());
|
||||
$this->assertNotSame($originalResponse, $response);
|
||||
|
||||
@@ -3,11 +3,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Middleware;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Rest\Middleware\PathVersionMiddleware;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
@@ -30,25 +30,10 @@ class PathVersionMiddlewareTest extends TestCase
|
||||
*/
|
||||
public function whenVersionIsProvidedRequestRemainsUnchanged()
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals()->withUri(new Uri('/rest/v2/foo'));
|
||||
$request = ServerRequestFactory::fromGlobals()->withUri(new Uri('/v2/foo'));
|
||||
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$process = $delegate->process($request)->willReturn(new Response());
|
||||
|
||||
$this->middleware->process($request, $delegate->reveal());
|
||||
|
||||
$process->shouldHaveBeenCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function whenPathDoesNotStartWithRestRemainsUnchanged()
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals()->withUri(new Uri('/foo'));
|
||||
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$process = $delegate->process($request)->willReturn(new Response());
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
$process = $delegate->handle($request)->willReturn(new Response());
|
||||
|
||||
$this->middleware->process($request, $delegate->reveal());
|
||||
|
||||
@@ -60,14 +45,14 @@ class PathVersionMiddlewareTest extends TestCase
|
||||
*/
|
||||
public function versionOneIsPrependedWhenNoVersionIsDefined()
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals()->withUri(new Uri('/rest/bar/baz'));
|
||||
$request = ServerRequestFactory::fromGlobals()->withUri(new Uri('/bar/baz'));
|
||||
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
$delegate->process(Argument::type(Request::class))->will(function (array $args) use ($request) {
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
$delegate->handle(Argument::type(Request::class))->will(function (array $args) use ($request) {
|
||||
$req = \array_shift($args);
|
||||
|
||||
Assert::assertNotSame($request, $req);
|
||||
Assert::assertEquals('/rest/v1/bar/baz', $req->getUri()->getPath());
|
||||
Assert::assertEquals('/v1/bar/baz', $req->getUri()->getPath());
|
||||
return new Response();
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Middleware\ShortCode;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Rest\Middleware\ShortCode\CreateShortCodeContentNegotiationMiddleware;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
|
||||
class CreateShortCodeContentNegotiationMiddlewareTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var CreateShortCodeContentNegotiationMiddleware
|
||||
*/
|
||||
private $middleware;
|
||||
/**
|
||||
* @var RequestHandlerInterface
|
||||
*/
|
||||
private $requestHandler;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->middleware = new CreateShortCodeContentNegotiationMiddleware();
|
||||
$this->requestHandler = $this->prophesize(RequestHandlerInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function whenNoJsonResponseIsReturnedNoFurtherOperationsArePerformed()
|
||||
{
|
||||
$expectedResp = new Response();
|
||||
$this->requestHandler->handle(Argument::type(ServerRequestInterface::class))->willReturn($expectedResp);
|
||||
|
||||
$resp = $this->middleware->process(ServerRequestFactory::fromGlobals(), $this->requestHandler->reveal());
|
||||
|
||||
$this->assertSame($expectedResp, $resp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideData
|
||||
* @param array $query
|
||||
*/
|
||||
public function properResponseIsReturned(?string $accept, array $query, string $expectedContentType)
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals()->withQueryParams($query);
|
||||
if ($accept !== null) {
|
||||
$request = $request->withHeader('Accept', $accept);
|
||||
}
|
||||
|
||||
$handle = $this->requestHandler->handle(Argument::type(ServerRequestInterface::class))->willReturn(
|
||||
new JsonResponse(['shortUrl' => 'http://doma.in/foo'])
|
||||
);
|
||||
|
||||
$response = $this->middleware->process($request, $this->requestHandler->reveal());
|
||||
|
||||
$this->assertEquals($expectedContentType, $response->getHeaderLine('Content-type'));
|
||||
$handle->shouldHaveBeenCalled();
|
||||
}
|
||||
|
||||
public function provideData(): array
|
||||
{
|
||||
return [
|
||||
[null, [], 'application/json'],
|
||||
[null, ['format' => 'json'], 'application/json'],
|
||||
[null, ['format' => 'invalid'], 'application/json'],
|
||||
[null, ['format' => 'txt'], 'text/plain'],
|
||||
['application/json', [], 'application/json'],
|
||||
['application/xml', [], 'application/json'],
|
||||
['text/plain', [], 'text/plain'],
|
||||
['application/json', ['format' => 'txt'], 'text/plain'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideTextBodies
|
||||
* @param array $json
|
||||
*/
|
||||
public function properBodyIsReturnedInPlainTextResponses(array $json, string $expectedBody)
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals()->withQueryParams(['format' => 'txt']);
|
||||
|
||||
$handle = $this->requestHandler->handle(Argument::type(ServerRequestInterface::class))->willReturn(
|
||||
new JsonResponse($json)
|
||||
);
|
||||
|
||||
$response = $this->middleware->process($request, $this->requestHandler->reveal());
|
||||
|
||||
$this->assertEquals($expectedBody, (string) $response->getBody());
|
||||
$handle->shouldHaveBeenCalled();
|
||||
}
|
||||
|
||||
public function provideTextBodies(): array
|
||||
{
|
||||
return [
|
||||
[['shortUrl' => 'foobar'], 'foobar'],
|
||||
[['error' => 'FOO_BAR'], 'FOO_BAR'],
|
||||
[[], ''],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
parameters:
|
||||
excludes_analyse:
|
||||
- module/Common/src/Template/Extension/TranslatorExtension.php
|
||||
- module/Rest/src/Util/RestUtils.php
|
||||
ignoreErrors:
|
||||
- '#is not subtype of Throwable#'
|
||||
- '#Cannot access offset#'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Zend\Expressive\Application;
|
||||
|
||||
/** @var ContainerInterface $container */
|
||||
$container = include __DIR__ . '/../config/container.php';
|
||||
$app = $container->get(Application::class)->run();
|
||||
$container->get(Application::class)->run();
|
||||
|
||||
Reference in New Issue
Block a user