mirror of
https://github.com/shlinkio/shlink.git
synced 2024-10-23 20:55:35 +03:00
commit
8cfb4f61ca
47 changed files with 857 additions and 179 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,5 +1,15 @@
|
||||||
## CHANGELOG
|
## CHANGELOG
|
||||||
|
|
||||||
|
### 1.9.0
|
||||||
|
|
||||||
|
**Features**
|
||||||
|
|
||||||
|
* [147: Allow short URLs to be created on the fly with query param authentication](https://github.com/shlinkio/shlink/issues/147)
|
||||||
|
|
||||||
|
**Bugs:**
|
||||||
|
|
||||||
|
* [139: Make sure all core actions log exceptions](https://github.com/shlinkio/shlink/issues/139)
|
||||||
|
|
||||||
### 1.8.1
|
### 1.8.1
|
||||||
|
|
||||||
**Tasks**
|
**Tasks**
|
||||||
|
|
4
build.sh
4
build.sh
|
@ -33,8 +33,12 @@ rm composer.*
|
||||||
rm LICENSE
|
rm LICENSE
|
||||||
rm indocker
|
rm indocker
|
||||||
rm docker-compose.yml
|
rm docker-compose.yml
|
||||||
|
rm docker-compose.override.yml
|
||||||
|
rm docker-compose.override.yml.dist
|
||||||
|
rm func_tests_bootstrap.php
|
||||||
rm php*
|
rm php*
|
||||||
rm README.md
|
rm README.md
|
||||||
|
rm infection.json
|
||||||
rm -rf build
|
rm -rf build
|
||||||
rm -ff data/database.sqlite
|
rm -ff data/database.sqlite
|
||||||
rm -rf data/infra
|
rm -rf data/infra
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Shlinkio\Shlink\Common\Middleware\LocaleMiddleware;
|
use Shlinkio\Shlink\Common\Middleware\LocaleMiddleware;
|
||||||
use Shlinkio\Shlink\Core\Response\NotFoundDelegate;
|
use Shlinkio\Shlink\Core\Response\NotFoundHandler;
|
||||||
use Shlinkio\Shlink\Rest\Middleware\BodyParserMiddleware;
|
use Shlinkio\Shlink\Rest\Middleware\BodyParserMiddleware;
|
||||||
use Shlinkio\Shlink\Rest\Middleware\CheckAuthenticationMiddleware;
|
use Shlinkio\Shlink\Rest\Middleware\CheckAuthenticationMiddleware;
|
||||||
use Shlinkio\Shlink\Rest\Middleware\CrossDomainMiddleware;
|
use Shlinkio\Shlink\Rest\Middleware\CrossDomainMiddleware;
|
||||||
|
@ -16,6 +16,7 @@ return [
|
||||||
'pre-routing' => [
|
'pre-routing' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
ErrorHandler::class,
|
ErrorHandler::class,
|
||||||
|
Expressive\Helper\ContentLengthMiddleware::class,
|
||||||
LocaleMiddleware::class,
|
LocaleMiddleware::class,
|
||||||
],
|
],
|
||||||
'priority' => 11,
|
'priority' => 11,
|
||||||
|
@ -49,7 +50,7 @@ return [
|
||||||
'post-routing' => [
|
'post-routing' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
Expressive\Router\Middleware\DispatchMiddleware::class,
|
Expressive\Router\Middleware\DispatchMiddleware::class,
|
||||||
NotFoundDelegate::class,
|
NotFoundHandler::class,
|
||||||
],
|
],
|
||||||
'priority' => 1,
|
'priority' => 1,
|
||||||
],
|
],
|
||||||
|
|
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": {
|
"/v1/short-codes": {
|
||||||
"$ref": "paths/v1_short-codes.json"
|
"$ref": "paths/v1_short-codes.json"
|
||||||
},
|
},
|
||||||
|
"/v1/short-codes/shorten": {
|
||||||
|
"$ref": "paths/v1_short-codes_shorten.json"
|
||||||
|
},
|
||||||
"/v1/short-codes/{shortCode}": {
|
"/v1/short-codes/{shortCode}": {
|
||||||
"$ref": "paths/v1_short-codes_{shortCode}.json"
|
"$ref": "paths/v1_short-codes_{shortCode}.json"
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,7 +6,7 @@ use Shlinkio\Shlink\Common\Service\PreviewGenerator;
|
||||||
use Shlinkio\Shlink\Core\Action;
|
use Shlinkio\Shlink\Core\Action;
|
||||||
use Shlinkio\Shlink\Core\Middleware;
|
use Shlinkio\Shlink\Core\Middleware;
|
||||||
use Shlinkio\Shlink\Core\Options;
|
use Shlinkio\Shlink\Core\Options;
|
||||||
use Shlinkio\Shlink\Core\Response\NotFoundDelegate;
|
use Shlinkio\Shlink\Core\Response\NotFoundHandler;
|
||||||
use Shlinkio\Shlink\Core\Service;
|
use Shlinkio\Shlink\Core\Service;
|
||||||
use Zend\Expressive\Router\RouterInterface;
|
use Zend\Expressive\Router\RouterInterface;
|
||||||
use Zend\Expressive\Template\TemplateRendererInterface;
|
use Zend\Expressive\Template\TemplateRendererInterface;
|
||||||
|
@ -17,7 +17,7 @@ return [
|
||||||
'dependencies' => [
|
'dependencies' => [
|
||||||
'factories' => [
|
'factories' => [
|
||||||
Options\AppOptions::class => Options\AppOptionsFactory::class,
|
Options\AppOptions::class => Options\AppOptionsFactory::class,
|
||||||
NotFoundDelegate::class => ConfigAbstractFactory::class,
|
NotFoundHandler::class => ConfigAbstractFactory::class,
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
Service\UrlShortener::class => ConfigAbstractFactory::class,
|
Service\UrlShortener::class => ConfigAbstractFactory::class,
|
||||||
|
@ -33,14 +33,10 @@ return [
|
||||||
Action\PreviewAction::class => ConfigAbstractFactory::class,
|
Action\PreviewAction::class => ConfigAbstractFactory::class,
|
||||||
Middleware\QrCodeCacheMiddleware::class => ConfigAbstractFactory::class,
|
Middleware\QrCodeCacheMiddleware::class => ConfigAbstractFactory::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
'aliases' => [
|
|
||||||
'Zend\Expressive\Delegate\DefaultDelegate' => NotFoundDelegate::class,
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
|
|
||||||
ConfigAbstractFactory::class => [
|
ConfigAbstractFactory::class => [
|
||||||
NotFoundDelegate::class => [TemplateRendererInterface::class],
|
NotFoundHandler::class => [TemplateRendererInterface::class],
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
Service\UrlShortener::class => [
|
Service\UrlShortener::class => [
|
||||||
|
@ -60,14 +56,16 @@ return [
|
||||||
Service\UrlShortener::class,
|
Service\UrlShortener::class,
|
||||||
Service\VisitsTracker::class,
|
Service\VisitsTracker::class,
|
||||||
Options\AppOptions::class,
|
Options\AppOptions::class,
|
||||||
|
'Logger_Shlink',
|
||||||
],
|
],
|
||||||
Action\PixelAction::class => [
|
Action\PixelAction::class => [
|
||||||
Service\UrlShortener::class,
|
Service\UrlShortener::class,
|
||||||
Service\VisitsTracker::class,
|
Service\VisitsTracker::class,
|
||||||
Options\AppOptions::class,
|
Options\AppOptions::class,
|
||||||
|
'Logger_Shlink',
|
||||||
],
|
],
|
||||||
Action\QrCodeAction::class => [RouterInterface::class, Service\UrlShortener::class, 'Logger_Shlink'],
|
Action\QrCodeAction::class => [RouterInterface::class, Service\UrlShortener::class, 'Logger_Shlink'],
|
||||||
Action\PreviewAction::class => [PreviewGenerator::class, Service\UrlShortener::class],
|
Action\PreviewAction::class => [PreviewGenerator::class, Service\UrlShortener::class, 'Logger_Shlink'],
|
||||||
Middleware\QrCodeCacheMiddleware::class => [Cache::class],
|
Middleware\QrCodeCacheMiddleware::class => [Cache::class],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Server\MiddlewareInterface;
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Psr\Log\NullLogger;
|
||||||
use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait;
|
use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait;
|
||||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||||
|
@ -30,15 +32,21 @@ abstract class AbstractTrackingAction implements MiddlewareInterface
|
||||||
* @var AppOptions
|
* @var AppOptions
|
||||||
*/
|
*/
|
||||||
private $appOptions;
|
private $appOptions;
|
||||||
|
/**
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
private $logger;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
UrlShortenerInterface $urlShortener,
|
UrlShortenerInterface $urlShortener,
|
||||||
VisitsTrackerInterface $visitTracker,
|
VisitsTrackerInterface $visitTracker,
|
||||||
AppOptions $appOptions
|
AppOptions $appOptions,
|
||||||
|
LoggerInterface $logger = null
|
||||||
) {
|
) {
|
||||||
$this->urlShortener = $urlShortener;
|
$this->urlShortener = $urlShortener;
|
||||||
$this->visitTracker = $visitTracker;
|
$this->visitTracker = $visitTracker;
|
||||||
$this->appOptions = $appOptions;
|
$this->appOptions = $appOptions;
|
||||||
|
$this->logger = $logger ?: new NullLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -66,6 +74,7 @@ abstract class AbstractTrackingAction implements MiddlewareInterface
|
||||||
|
|
||||||
return $this->createResp($longUrl);
|
return $this->createResp($longUrl);
|
||||||
} catch (InvalidShortCodeException | EntityDoesNotExistException $e) {
|
} catch (InvalidShortCodeException | EntityDoesNotExistException $e) {
|
||||||
|
$this->logger->warning('An error occurred while tracking short code.' . PHP_EOL . $e);
|
||||||
return $this->buildErrorResponse($request, $handler);
|
return $this->buildErrorResponse($request, $handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
use Psr\Http\Server\MiddlewareInterface;
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Psr\Log\NullLogger;
|
||||||
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
|
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
|
||||||
use Shlinkio\Shlink\Common\Service\PreviewGeneratorInterface;
|
use Shlinkio\Shlink\Common\Service\PreviewGeneratorInterface;
|
||||||
use Shlinkio\Shlink\Common\Util\ResponseUtilsTrait;
|
use Shlinkio\Shlink\Common\Util\ResponseUtilsTrait;
|
||||||
|
@ -28,11 +30,19 @@ class PreviewAction implements MiddlewareInterface
|
||||||
* @var UrlShortenerInterface
|
* @var UrlShortenerInterface
|
||||||
*/
|
*/
|
||||||
private $urlShortener;
|
private $urlShortener;
|
||||||
|
/**
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
private $logger;
|
||||||
|
|
||||||
public function __construct(PreviewGeneratorInterface $previewGenerator, UrlShortenerInterface $urlShortener)
|
public function __construct(
|
||||||
{
|
PreviewGeneratorInterface $previewGenerator,
|
||||||
|
UrlShortenerInterface $urlShortener,
|
||||||
|
LoggerInterface $logger = null
|
||||||
|
) {
|
||||||
$this->previewGenerator = $previewGenerator;
|
$this->previewGenerator = $previewGenerator;
|
||||||
$this->urlShortener = $urlShortener;
|
$this->urlShortener = $urlShortener;
|
||||||
|
$this->logger = $logger ?: new NullLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,6 +63,7 @@ class PreviewAction implements MiddlewareInterface
|
||||||
$imagePath = $this->previewGenerator->generatePreview($url);
|
$imagePath = $this->previewGenerator->generatePreview($url);
|
||||||
return $this->generateImageResponse($imagePath);
|
return $this->generateImageResponse($imagePath);
|
||||||
} catch (InvalidShortCodeException | EntityDoesNotExistException | PreviewGenerationException $e) {
|
} catch (InvalidShortCodeException | EntityDoesNotExistException | PreviewGenerationException $e) {
|
||||||
|
$this->logger->warning('An error occurred while generating preview image.' . PHP_EOL . $e);
|
||||||
return $this->buildErrorResponse($request, $handler);
|
return $this->buildErrorResponse($request, $handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait;
|
||||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||||
|
use Zend\Expressive\Router\Exception\RuntimeException;
|
||||||
use Zend\Expressive\Router\RouterInterface;
|
use Zend\Expressive\Router\RouterInterface;
|
||||||
|
|
||||||
class QrCodeAction implements MiddlewareInterface
|
class QrCodeAction implements MiddlewareInterface
|
||||||
|
@ -52,6 +53,8 @@ class QrCodeAction implements MiddlewareInterface
|
||||||
* @param RequestHandlerInterface $handler
|
* @param RequestHandlerInterface $handler
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
* @throws RuntimeException
|
||||||
*/
|
*/
|
||||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||||
{
|
{
|
||||||
|
@ -59,11 +62,8 @@ class QrCodeAction implements MiddlewareInterface
|
||||||
$shortCode = $request->getAttribute('shortCode');
|
$shortCode = $request->getAttribute('shortCode');
|
||||||
try {
|
try {
|
||||||
$this->urlShortener->shortCodeToUrl($shortCode);
|
$this->urlShortener->shortCodeToUrl($shortCode);
|
||||||
} catch (InvalidShortCodeException $e) {
|
} catch (InvalidShortCodeException | EntityDoesNotExistException $e) {
|
||||||
$this->logger->warning('Tried to create a QR code with an invalid short code' . PHP_EOL . $e);
|
$this->logger->warning('An error occurred while creating QR code' . PHP_EOL . $e);
|
||||||
return $this->buildErrorResponse($request, $handler);
|
|
||||||
} catch (EntityDoesNotExistException $e) {
|
|
||||||
$this->logger->warning('Tried to create a QR code with a not found short code' . PHP_EOL . $e);
|
|
||||||
return $this->buildErrorResponse($request, $handler);
|
return $this->buildErrorResponse($request, $handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace Shlinkio\Shlink\Core\Action\Util;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
use Shlinkio\Shlink\Core\Response\NotFoundDelegate;
|
use Shlinkio\Shlink\Core\Response\NotFoundHandler;
|
||||||
|
|
||||||
trait ErrorResponseBuilderTrait
|
trait ErrorResponseBuilderTrait
|
||||||
{
|
{
|
||||||
|
@ -14,7 +14,7 @@ trait ErrorResponseBuilderTrait
|
||||||
ServerRequestInterface $request,
|
ServerRequestInterface $request,
|
||||||
RequestHandlerInterface $handler
|
RequestHandlerInterface $handler
|
||||||
): ResponseInterface {
|
): ResponseInterface {
|
||||||
$request = $request->withAttribute(NotFoundDelegate::NOT_FOUND_TEMPLATE, 'ShlinkCore::invalid-short-code');
|
$request = $request->withAttribute(NotFoundHandler::NOT_FOUND_TEMPLATE, 'ShlinkCore::invalid-short-code');
|
||||||
return $handler->handle($request);
|
return $handler->handle($request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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
|
* @param array $data
|
||||||
* @throws ValidationException
|
* @throws ValidationException
|
||||||
*/
|
*/
|
||||||
private function validate(array $data)
|
private function validate(array $data): void
|
||||||
{
|
{
|
||||||
$inputFilter = new ShortUrlMetaInputFilter($data);
|
$inputFilter = new ShortUrlMetaInputFilter($data);
|
||||||
if (! $inputFilter->isValid()) {
|
if (! $inputFilter->isValid()) {
|
||||||
|
|
|
@ -6,13 +6,13 @@ namespace Shlinkio\Shlink\Core\Response;
|
||||||
use Fig\Http\Message\StatusCodeInterface;
|
use Fig\Http\Message\StatusCodeInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Server\RequestHandlerInterface as DelegateInterface;
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
use Zend\Diactoros\Response;
|
use Zend\Diactoros\Response;
|
||||||
use Zend\Expressive\Template\TemplateRendererInterface;
|
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
|
* @var TemplateRendererInterface
|
|
@ -7,15 +7,15 @@ use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
use Prophecy\Prophecy\MethodProphecy;
|
use Prophecy\Prophecy\MethodProphecy;
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
use Shlinkio\Shlink\Core\Response\NotFoundDelegate;
|
use Shlinkio\Shlink\Core\Response\NotFoundHandler;
|
||||||
use Zend\Diactoros\Response;
|
use Zend\Diactoros\Response;
|
||||||
use Zend\Diactoros\ServerRequestFactory;
|
use Zend\Diactoros\ServerRequestFactory;
|
||||||
use Zend\Expressive\Template\TemplateRendererInterface;
|
use Zend\Expressive\Template\TemplateRendererInterface;
|
||||||
|
|
||||||
class NotFoundDelegateTest extends TestCase
|
class NotFoundHandlerTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var NotFoundDelegate
|
* @var NotFoundHandler
|
||||||
*/
|
*/
|
||||||
private $delegate;
|
private $delegate;
|
||||||
/**
|
/**
|
||||||
|
@ -26,7 +26,7 @@ class NotFoundDelegateTest extends TestCase
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$this->renderer = $this->prophesize(TemplateRendererInterface::class);
|
$this->renderer = $this->prophesize(TemplateRendererInterface::class);
|
||||||
$this->delegate = new NotFoundDelegate($this->renderer->reveal());
|
$this->delegate = new NotFoundHandler($this->renderer->reveal());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
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,
|
ApiKeyService::class => ConfigAbstractFactory::class,
|
||||||
|
|
||||||
Action\AuthenticateAction::class => ConfigAbstractFactory::class,
|
Action\AuthenticateAction::class => ConfigAbstractFactory::class,
|
||||||
Action\CreateShortcodeAction::class => ConfigAbstractFactory::class,
|
Action\ShortCode\CreateShortCodeAction::class => ConfigAbstractFactory::class,
|
||||||
Action\EditShortCodeAction::class => ConfigAbstractFactory::class,
|
Action\ShortCode\SingleStepCreateShortCodeAction::class => ConfigAbstractFactory::class,
|
||||||
Action\ResolveUrlAction::class => ConfigAbstractFactory::class,
|
Action\ShortCode\EditShortCodeAction::class => ConfigAbstractFactory::class,
|
||||||
Action\GetVisitsAction::class => ConfigAbstractFactory::class,
|
Action\ShortCode\ResolveUrlAction::class => ConfigAbstractFactory::class,
|
||||||
Action\ListShortcodesAction::class => ConfigAbstractFactory::class,
|
Action\Visit\GetVisitsAction::class => ConfigAbstractFactory::class,
|
||||||
Action\EditShortcodeTagsAction::class => ConfigAbstractFactory::class,
|
Action\ShortCode\ListShortCodesAction::class => ConfigAbstractFactory::class,
|
||||||
|
Action\ShortCode\EditShortCodeTagsAction::class => ConfigAbstractFactory::class,
|
||||||
Action\Tag\ListTagsAction::class => ConfigAbstractFactory::class,
|
Action\Tag\ListTagsAction::class => ConfigAbstractFactory::class,
|
||||||
Action\Tag\DeleteTagsAction::class => ConfigAbstractFactory::class,
|
Action\Tag\DeleteTagsAction::class => ConfigAbstractFactory::class,
|
||||||
Action\Tag\CreateTagsAction::class => ConfigAbstractFactory::class,
|
Action\Tag\CreateTagsAction::class => ConfigAbstractFactory::class,
|
||||||
|
@ -35,6 +36,7 @@ return [
|
||||||
Middleware\CrossDomainMiddleware::class => InvokableFactory::class,
|
Middleware\CrossDomainMiddleware::class => InvokableFactory::class,
|
||||||
Middleware\PathVersionMiddleware::class => InvokableFactory::class,
|
Middleware\PathVersionMiddleware::class => InvokableFactory::class,
|
||||||
Middleware\CheckAuthenticationMiddleware::class => ConfigAbstractFactory::class,
|
Middleware\CheckAuthenticationMiddleware::class => ConfigAbstractFactory::class,
|
||||||
|
Middleware\ShortCode\CreateShortCodeContentNegotiationMiddleware::class => InvokableFactory::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -43,23 +45,39 @@ return [
|
||||||
ApiKeyService::class => ['em'],
|
ApiKeyService::class => ['em'],
|
||||||
|
|
||||||
Action\AuthenticateAction::class => [ApiKeyService::class, JWTService::class, 'translator', 'Logger_Shlink'],
|
Action\AuthenticateAction::class => [ApiKeyService::class, JWTService::class, 'translator', 'Logger_Shlink'],
|
||||||
Action\CreateShortcodeAction::class => [
|
Action\ShortCode\CreateShortCodeAction::class => [
|
||||||
Service\UrlShortener::class,
|
Service\UrlShortener::class,
|
||||||
'translator',
|
'translator',
|
||||||
'config.url_shortener.domain',
|
'config.url_shortener.domain',
|
||||||
'Logger_Shlink',
|
'Logger_Shlink',
|
||||||
],
|
],
|
||||||
Action\EditShortCodeAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink',],
|
Action\ShortCode\SingleStepCreateShortCodeAction::class => [
|
||||||
Action\ResolveUrlAction::class => [Service\UrlShortener::class, 'translator'],
|
Service\UrlShortener::class,
|
||||||
Action\GetVisitsAction::class => [Service\VisitsTracker::class, 'translator', 'Logger_Shlink'],
|
'translator',
|
||||||
Action\ListShortcodesAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink'],
|
ApiKeyService::class,
|
||||||
Action\EditShortcodeTagsAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink'],
|
'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\ListTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class],
|
||||||
Action\Tag\DeleteTagsAction::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\CreateTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class],
|
||||||
Action\Tag\UpdateTagAction::class => [Service\Tag\TagService::class, Translator::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
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Fig\Http\Message\RequestMethodInterface as RequestMethod;
|
namespace Shlinkio\Shlink\Rest;
|
||||||
|
|
||||||
use Shlinkio\Shlink\Rest\Action;
|
use Shlinkio\Shlink\Rest\Action;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'routes' => [
|
'routes' => [
|
||||||
[
|
Action\AuthenticateAction::getRouteDef(),
|
||||||
'name' => Action\AuthenticateAction::class,
|
|
||||||
'path' => '/authenticate',
|
|
||||||
'middleware' => Action\AuthenticateAction::class,
|
|
||||||
'allowed_methods' => [RequestMethod::METHOD_POST],
|
|
||||||
],
|
|
||||||
|
|
||||||
// Short codes
|
// Short codes
|
||||||
[
|
Action\ShortCode\CreateShortCodeAction::getRouteDef([
|
||||||
'name' => Action\CreateShortcodeAction::class,
|
Middleware\ShortCode\CreateShortCodeContentNegotiationMiddleware::class,
|
||||||
'path' => '/short-codes',
|
]),
|
||||||
'middleware' => Action\CreateShortcodeAction::class,
|
Action\ShortCode\SingleStepCreateShortCodeAction::getRouteDef([
|
||||||
'allowed_methods' => [RequestMethod::METHOD_POST],
|
Middleware\ShortCode\CreateShortCodeContentNegotiationMiddleware::class,
|
||||||
],
|
]),
|
||||||
[
|
Action\ShortCode\EditShortCodeAction::getRouteDef(),
|
||||||
'name' => Action\EditShortCodeAction::class,
|
Action\ShortCode\ResolveUrlAction::getRouteDef(),
|
||||||
'path' => '/short-codes/{shortCode}',
|
Action\ShortCode\ListShortCodesAction::getRouteDef(),
|
||||||
'middleware' => Action\EditShortCodeAction::class,
|
Action\ShortCode\EditShortCodeTagsAction::getRouteDef(),
|
||||||
'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],
|
|
||||||
],
|
|
||||||
|
|
||||||
// Visits
|
// Visits
|
||||||
[
|
Action\Visit\GetVisitsAction::getRouteDef(),
|
||||||
'name' => Action\GetVisitsAction::class,
|
|
||||||
'path' => '/short-codes/{shortCode}/visits',
|
|
||||||
'middleware' => Action\GetVisitsAction::class,
|
|
||||||
'allowed_methods' => [RequestMethod::METHOD_GET],
|
|
||||||
],
|
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
[
|
Action\Tag\ListTagsAction::getRouteDef(),
|
||||||
'name' => Action\Tag\ListTagsAction::class,
|
Action\Tag\DeleteTagsAction::getRouteDef(),
|
||||||
'path' => '/tags',
|
Action\Tag\CreateTagsAction::getRouteDef(),
|
||||||
'middleware' => Action\Tag\ListTagsAction::class,
|
Action\Tag\UpdateTagAction::getRouteDef(),
|
||||||
'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],
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
Binary file not shown.
|
@ -1,15 +1,15 @@
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Shlink 1.0\n"
|
"Project-Id-Version: Shlink 1.0\n"
|
||||||
"POT-Creation-Date: 2018-01-21 09:40+0100\n"
|
"POT-Creation-Date: 2018-05-06 12:34+0200\n"
|
||||||
"PO-Revision-Date: 2018-01-21 09:40+0100\n"
|
"PO-Revision-Date: 2018-05-06 12:35+0200\n"
|
||||||
"Last-Translator: Alejandro Celaya <alejandro@alejandrocelaya.com>\n"
|
"Last-Translator: Alejandro Celaya <alejandro@alejandrocelaya.com>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"Language: es_ES\n"
|
"Language: es_ES\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Generator: Poedit 2.0.4\n"
|
"X-Generator: Poedit 2.0.6\n"
|
||||||
"X-Poedit-Basepath: ..\n"
|
"X-Poedit-Basepath: ..\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
"X-Poedit-SourceCharset: UTF-8\n"
|
"X-Poedit-SourceCharset: UTF-8\n"
|
||||||
|
@ -25,9 +25,6 @@ msgstr ""
|
||||||
msgid "Provided API key does not exist or is invalid."
|
msgid "Provided API key does not exist or is invalid."
|
||||||
msgstr "La clave de API proporcionada no existe o es inválida."
|
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
|
#, php-format
|
||||||
msgid "Provided URL %s is invalid. Try with a different one."
|
msgid "Provided URL %s is invalid. Try with a different one."
|
||||||
msgstr "La URL proporcionada \"%s\" es inválida. Prueba con una diferente."
|
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"
|
msgid "Unexpected error occurred"
|
||||||
msgstr "Ocurrió un error inesperado"
|
msgstr "Ocurrió un error inesperado"
|
||||||
|
|
||||||
|
msgid "A URL was not provided"
|
||||||
|
msgstr "No se ha proporcionado una URL"
|
||||||
|
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "No URL found for short code \"%s\""
|
msgid "No URL found for short code \"%s\""
|
||||||
msgstr "No se ha encontrado ninguna URL para el código corto \"%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"
|
msgid "A list of tags was not provided"
|
||||||
msgstr "No se ha proporcionado una lista de etiquetas"
|
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
|
#, php-format
|
||||||
msgid "Provided short code \"%s\" has an invalid format"
|
msgid "Provided short code \"%s\" has an invalid format"
|
||||||
msgstr "El código corto proporcionado \"%s\" tiene un formato no inválido"
|
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 ""
|
msgid ""
|
||||||
"You have to provide both 'oldName' and 'newName' params in order to properly "
|
"You have to provide both 'oldName' and 'newName' params in order to properly "
|
||||||
"rename the tag"
|
"rename the tag"
|
||||||
|
@ -68,6 +67,10 @@ msgstr ""
|
||||||
msgid "It wasn't possible to find a tag with name '%s'"
|
msgid "It wasn't possible to find a tag with name '%s'"
|
||||||
msgstr "No fue posible encontrar una etiqueta con el nombre '%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
|
#, php-format
|
||||||
msgid "You need to provide the Bearer type in the %s header."
|
msgid "You need to provide the Bearer type in the %s header."
|
||||||
msgstr "Debes proporcionar el typo Bearer en la cabecera %s."
|
msgstr "Debes proporcionar el typo Bearer en la cabecera %s."
|
||||||
|
|
|
@ -11,6 +11,9 @@ use Psr\Log\NullLogger;
|
||||||
|
|
||||||
abstract class AbstractRestAction implements RequestHandlerInterface, RequestMethodInterface, StatusCodeInterface
|
abstract class AbstractRestAction implements RequestHandlerInterface, RequestMethodInterface, StatusCodeInterface
|
||||||
{
|
{
|
||||||
|
protected const ROUTE_PATH = '';
|
||||||
|
protected const ROUTE_ALLOWED_METHODS = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var LoggerInterface
|
* @var LoggerInterface
|
||||||
*/
|
*/
|
||||||
|
@ -20,4 +23,14 @@ abstract class AbstractRestAction implements RequestHandlerInterface, RequestMet
|
||||||
{
|
{
|
||||||
$this->logger = $logger ?: new NullLogger();
|
$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,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,9 @@ use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class AuthenticateAction extends AbstractRestAction
|
class AuthenticateAction extends AbstractRestAction
|
||||||
{
|
{
|
||||||
|
protected const ROUTE_PATH = '/authenticate';
|
||||||
|
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_POST];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TranslatorInterface
|
* @var TranslatorInterface
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,20 +1,23 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Rest\Action;
|
namespace Shlinkio\Shlink\Rest\Action\ShortCode;
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||||
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
||||||
|
use Shlinkio\Shlink\Core\Model\CreateShortCodeData;
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||||
|
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||||
use Zend\Diactoros\Response\JsonResponse;
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
use Zend\Diactoros\Uri;
|
use Zend\Diactoros\Uri;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class CreateShortcodeAction extends AbstractRestAction
|
abstract class AbstractCreateShortCodeAction extends AbstractRestAction
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var UrlShortenerInterface
|
* @var UrlShortenerInterface
|
||||||
|
@ -27,7 +30,7 @@ class CreateShortcodeAction extends AbstractRestAction
|
||||||
/**
|
/**
|
||||||
* @var TranslatorInterface
|
* @var TranslatorInterface
|
||||||
*/
|
*/
|
||||||
private $translator;
|
protected $translator;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
UrlShortenerInterface $urlShortener,
|
UrlShortenerInterface $urlShortener,
|
||||||
|
@ -48,31 +51,34 @@ class CreateShortcodeAction extends AbstractRestAction
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request): Response
|
public function handle(Request $request): Response
|
||||||
{
|
{
|
||||||
$postData = (array) $request->getParsedBody();
|
try {
|
||||||
if (! isset($postData['longUrl'])) {
|
$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([
|
return new JsonResponse([
|
||||||
'error' => RestUtils::INVALID_ARGUMENT_ERROR,
|
'error' => RestUtils::INVALID_ARGUMENT_ERROR,
|
||||||
'message' => $this->translator->translate('A URL was not provided'),
|
'message' => $e->getMessage(),
|
||||||
], self::STATUS_BAD_REQUEST);
|
], self::STATUS_BAD_REQUEST);
|
||||||
}
|
}
|
||||||
$longUrl = $postData['longUrl'];
|
|
||||||
$customSlug = $postData['customSlug'] ?? null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$shortCode = $this->urlShortener->urlToShortCode(
|
$shortCode = $this->urlShortener->urlToShortCode(
|
||||||
new Uri($longUrl),
|
$longUrl,
|
||||||
(array) ($postData['tags'] ?? []),
|
$shortCodeData->getTags(),
|
||||||
$this->getOptionalDate($postData, 'validSince'),
|
$shortCodeMeta->getValidSince(),
|
||||||
$this->getOptionalDate($postData, 'validUntil'),
|
$shortCodeMeta->getValidUntil(),
|
||||||
$customSlug,
|
$customSlug,
|
||||||
isset($postData['maxVisits']) ? (int) $postData['maxVisits'] : null
|
$shortCodeMeta->getMaxVisits()
|
||||||
);
|
);
|
||||||
$shortUrl = (new Uri())->withPath($shortCode)
|
$shortUrl = (new Uri())->withPath($shortCode)
|
||||||
->withScheme($this->domainConfig['schema'])
|
->withScheme($this->domainConfig['schema'])
|
||||||
->withHost($this->domainConfig['hostname']);
|
->withHost($this->domainConfig['hostname']);
|
||||||
|
|
||||||
return new JsonResponse([
|
return new JsonResponse([
|
||||||
'longUrl' => $longUrl,
|
'longUrl' => (string) $longUrl,
|
||||||
'shortUrl' => (string) $shortUrl,
|
'shortUrl' => (string) $shortUrl,
|
||||||
'shortCode' => $shortCode,
|
'shortCode' => $shortCode,
|
||||||
]);
|
]);
|
||||||
|
@ -80,7 +86,7 @@ class CreateShortcodeAction extends AbstractRestAction
|
||||||
$this->logger->warning('Provided Invalid URL.' . PHP_EOL . $e);
|
$this->logger->warning('Provided Invalid URL.' . PHP_EOL . $e);
|
||||||
return new JsonResponse([
|
return new JsonResponse([
|
||||||
'error' => RestUtils::getRestErrorCodeFromException($e),
|
'error' => RestUtils::getRestErrorCodeFromException($e),
|
||||||
'message' => sprintf(
|
'message' => \sprintf(
|
||||||
$this->translator->translate('Provided URL %s is invalid. Try with a different one.'),
|
$this->translator->translate('Provided URL %s is invalid. Try with a different one.'),
|
||||||
$longUrl
|
$longUrl
|
||||||
),
|
),
|
||||||
|
@ -89,7 +95,7 @@ class CreateShortcodeAction extends AbstractRestAction
|
||||||
$this->logger->warning('Provided non-unique slug.' . PHP_EOL . $e);
|
$this->logger->warning('Provided non-unique slug.' . PHP_EOL . $e);
|
||||||
return new JsonResponse([
|
return new JsonResponse([
|
||||||
'error' => RestUtils::getRestErrorCodeFromException($e),
|
'error' => RestUtils::getRestErrorCodeFromException($e),
|
||||||
'message' => sprintf(
|
'message' => \sprintf(
|
||||||
$this->translator->translate('Provided slug %s is already in use. Try with a different one.'),
|
$this->translator->translate('Provided slug %s is already in use. Try with a different one.'),
|
||||||
$customSlug
|
$customSlug
|
||||||
),
|
),
|
||||||
|
@ -103,8 +109,10 @@ class CreateShortcodeAction extends AbstractRestAction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getOptionalDate(array $postData, string $fieldName)
|
/**
|
||||||
{
|
* @param Request $request
|
||||||
return isset($postData[$fieldName]) ? new \DateTime($postData[$fieldName]) : null;
|
* @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,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Rest\Action;
|
namespace Shlinkio\Shlink\Rest\Action\ShortCode;
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
@ -9,6 +9,7 @@ use Psr\Log\LoggerInterface;
|
||||||
use Shlinkio\Shlink\Core\Exception;
|
use Shlinkio\Shlink\Core\Exception;
|
||||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||||
|
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||||
use Zend\Diactoros\Response\EmptyResponse;
|
use Zend\Diactoros\Response\EmptyResponse;
|
||||||
use Zend\Diactoros\Response\JsonResponse;
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
|
@ -16,6 +17,9 @@ use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class EditShortCodeAction extends AbstractRestAction
|
class EditShortCodeAction extends AbstractRestAction
|
||||||
{
|
{
|
||||||
|
protected const ROUTE_PATH = '/short-codes/{shortCode}';
|
||||||
|
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_PUT];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var ShortUrlServiceInterface
|
* @var ShortUrlServiceInterface
|
||||||
*/
|
*/
|
|
@ -1,19 +1,23 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Rest\Action;
|
namespace Shlinkio\Shlink\Rest\Action\ShortCode;
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||||
|
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||||
use Zend\Diactoros\Response\JsonResponse;
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
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
|
* @var ShortUrlServiceInterface
|
||||||
*/
|
*/
|
|
@ -1,21 +1,25 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Rest\Action;
|
namespace Shlinkio\Shlink\Rest\Action\ShortCode;
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
|
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
|
||||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||||
|
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||||
use Zend\Diactoros\Response\JsonResponse;
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class ListShortcodesAction extends AbstractRestAction
|
class ListShortCodesAction extends AbstractRestAction
|
||||||
{
|
{
|
||||||
use PaginatorUtilsTrait;
|
use PaginatorUtilsTrait;
|
||||||
|
|
||||||
|
protected const ROUTE_PATH = '/short-codes';
|
||||||
|
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var ShortUrlServiceInterface
|
* @var ShortUrlServiceInterface
|
||||||
*/
|
*/
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Rest\Action;
|
namespace Shlinkio\Shlink\Rest\Action\ShortCode;
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
@ -9,12 +9,16 @@ use Psr\Log\LoggerInterface;
|
||||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||||
|
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||||
use Zend\Diactoros\Response\JsonResponse;
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class ResolveUrlAction extends AbstractRestAction
|
class ResolveUrlAction extends AbstractRestAction
|
||||||
{
|
{
|
||||||
|
protected const ROUTE_PATH = '/short-codes/{shortCode}';
|
||||||
|
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var UrlShortenerInterface
|
* @var UrlShortenerInterface
|
||||||
*/
|
*/
|
|
@ -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']));
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,9 @@ use Zend\Diactoros\Response\JsonResponse;
|
||||||
|
|
||||||
class CreateTagsAction extends AbstractRestAction
|
class CreateTagsAction extends AbstractRestAction
|
||||||
{
|
{
|
||||||
|
protected const ROUTE_PATH = '/tags';
|
||||||
|
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_POST];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TagServiceInterface
|
* @var TagServiceInterface
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -12,6 +12,9 @@ use Zend\Diactoros\Response\EmptyResponse;
|
||||||
|
|
||||||
class DeleteTagsAction extends AbstractRestAction
|
class DeleteTagsAction extends AbstractRestAction
|
||||||
{
|
{
|
||||||
|
protected const ROUTE_PATH = '/tags';
|
||||||
|
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_DELETE];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TagServiceInterface
|
* @var TagServiceInterface
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -12,6 +12,9 @@ use Zend\Diactoros\Response\JsonResponse;
|
||||||
|
|
||||||
class ListTagsAction extends AbstractRestAction
|
class ListTagsAction extends AbstractRestAction
|
||||||
{
|
{
|
||||||
|
protected const ROUTE_PATH = '/tags';
|
||||||
|
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TagServiceInterface
|
* @var TagServiceInterface
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -16,6 +16,9 @@ use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class UpdateTagAction extends AbstractRestAction
|
class UpdateTagAction extends AbstractRestAction
|
||||||
{
|
{
|
||||||
|
protected const ROUTE_PATH = '/tags';
|
||||||
|
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_PUT];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TagServiceInterface
|
* @var TagServiceInterface
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Rest\Action;
|
namespace Shlinkio\Shlink\Rest\Action\Visit;
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
@ -9,12 +9,16 @@ use Psr\Log\LoggerInterface;
|
||||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||||
|
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||||
use Zend\Diactoros\Response\JsonResponse;
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
use Zend\I18n\Translator\TranslatorInterface;
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class GetVisitsAction extends AbstractRestAction
|
class GetVisitsAction extends AbstractRestAction
|
||||||
{
|
{
|
||||||
|
protected const ROUTE_PATH = '/short-codes/{shortCode}/visits';
|
||||||
|
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var VisitsTrackerInterface
|
* @var VisitsTrackerInterface
|
||||||
*/
|
*/
|
|
@ -10,7 +10,6 @@ use Psr\Http\Server\MiddlewareInterface;
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Psr\Log\NullLogger;
|
use Psr\Log\NullLogger;
|
||||||
use Shlinkio\Shlink\Rest\Action\AuthenticateAction;
|
|
||||||
use Shlinkio\Shlink\Rest\Authentication\JWTServiceInterface;
|
use Shlinkio\Shlink\Rest\Authentication\JWTServiceInterface;
|
||||||
use Shlinkio\Shlink\Rest\Exception\AuthenticationException;
|
use Shlinkio\Shlink\Rest\Exception\AuthenticationException;
|
||||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||||
|
@ -35,14 +34,20 @@ class CheckAuthenticationMiddleware implements MiddlewareInterface, StatusCodeIn
|
||||||
* @var LoggerInterface
|
* @var LoggerInterface
|
||||||
*/
|
*/
|
||||||
private $logger;
|
private $logger;
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $routesWhitelist;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
JWTServiceInterface $jwtService,
|
JWTServiceInterface $jwtService,
|
||||||
TranslatorInterface $translator,
|
TranslatorInterface $translator,
|
||||||
|
array $routesWhitelist,
|
||||||
LoggerInterface $logger = null
|
LoggerInterface $logger = null
|
||||||
) {
|
) {
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
$this->jwtService = $jwtService;
|
$this->jwtService = $jwtService;
|
||||||
|
$this->routesWhitelist = $routesWhitelist;
|
||||||
$this->logger = $logger ?: new NullLogger();
|
$this->logger = $logger ?: new NullLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,8 +69,8 @@ class CheckAuthenticationMiddleware implements MiddlewareInterface, StatusCodeIn
|
||||||
$routeResult = $request->getAttribute(RouteResult::class);
|
$routeResult = $request->getAttribute(RouteResult::class);
|
||||||
if ($routeResult === null
|
if ($routeResult === null
|
||||||
|| $routeResult->isFailure()
|
|| $routeResult->isFailure()
|
||||||
|| $routeResult->getMatchedRouteName() === AuthenticateAction::class
|
|
||||||
|| $request->getMethod() === 'OPTIONS'
|
|| $request->getMethod() === 'OPTIONS'
|
||||||
|
|| \in_array($routeResult->getMatchedRouteName(), $this->routesWhitelist, true)
|
||||||
) {
|
) {
|
||||||
return $handler->handle($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)
|
public function create(\DateTime $expirationDate = null)
|
||||||
{
|
{
|
||||||
$key = new ApiKey();
|
$key = new ApiKey();
|
||||||
if (isset($expirationDate)) {
|
if ($expirationDate !== null) {
|
||||||
$key->setExpirationDate($expirationDate);
|
$key->setExpirationDate($expirationDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ class ApiKeyService implements ApiKeyServiceInterface
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function check($key)
|
public function check(string $key)
|
||||||
{
|
{
|
||||||
/** @var ApiKey|null $apiKey */
|
/** @var ApiKey|null $apiKey */
|
||||||
$apiKey = $this->getByKey($key);
|
$apiKey = $this->getByKey($key);
|
||||||
|
@ -58,7 +58,7 @@ class ApiKeyService implements ApiKeyServiceInterface
|
||||||
* @return ApiKey
|
* @return ApiKey
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function disable($key)
|
public function disable(string $key)
|
||||||
{
|
{
|
||||||
/** @var ApiKey|null $apiKey */
|
/** @var ApiKey|null $apiKey */
|
||||||
$apiKey = $this->getByKey($key);
|
$apiKey = $this->getByKey($key);
|
||||||
|
@ -77,7 +77,7 @@ class ApiKeyService implements ApiKeyServiceInterface
|
||||||
* @param bool $enabledOnly Tells if only enabled keys should be returned
|
* @param bool $enabledOnly Tells if only enabled keys should be returned
|
||||||
* @return ApiKey[]
|
* @return ApiKey[]
|
||||||
*/
|
*/
|
||||||
public function listKeys($enabledOnly = false)
|
public function listKeys(bool $enabledOnly = false)
|
||||||
{
|
{
|
||||||
$conditions = $enabledOnly ? ['enabled' => true] : [];
|
$conditions = $enabledOnly ? ['enabled' => true] : [];
|
||||||
return $this->em->getRepository(ApiKey::class)->findBy($conditions);
|
return $this->em->getRepository(ApiKey::class)->findBy($conditions);
|
||||||
|
@ -89,7 +89,7 @@ class ApiKeyService implements ApiKeyServiceInterface
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @return ApiKey|null
|
* @return ApiKey|null
|
||||||
*/
|
*/
|
||||||
public function getByKey($key)
|
public function getByKey(string $key)
|
||||||
{
|
{
|
||||||
/** @var ApiKey|null $apiKey */
|
/** @var ApiKey|null $apiKey */
|
||||||
$apiKey = $this->em->getRepository(ApiKey::class)->findOneBy([
|
$apiKey = $this->em->getRepository(ApiKey::class)->findOneBy([
|
||||||
|
|
|
@ -22,7 +22,7 @@ interface ApiKeyServiceInterface
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function check($key);
|
public function check(string $key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disables provided api key
|
* Disables provided api key
|
||||||
|
@ -31,7 +31,7 @@ interface ApiKeyServiceInterface
|
||||||
* @return ApiKey
|
* @return ApiKey
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function disable($key);
|
public function disable(string $key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists all existing api keys
|
* Lists all existing api keys
|
||||||
|
@ -39,7 +39,7 @@ interface ApiKeyServiceInterface
|
||||||
* @param bool $enabledOnly Tells if only enabled keys should be returned
|
* @param bool $enabledOnly Tells if only enabled keys should be returned
|
||||||
* @return ApiKey[]
|
* @return ApiKey[]
|
||||||
*/
|
*/
|
||||||
public function listKeys($enabledOnly = false);
|
public function listKeys(bool $enabledOnly = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to find one API key by its key string
|
* Tries to find one API key by its key string
|
||||||
|
@ -47,5 +47,5 @@ interface ApiKeyServiceInterface
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @return ApiKey|null
|
* @return ApiKey|null
|
||||||
*/
|
*/
|
||||||
public function getByKey($key);
|
public function getByKey(string $key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ class RestUtils
|
||||||
case $e instanceof Core\NonUniqueSlugException:
|
case $e instanceof Core\NonUniqueSlugException:
|
||||||
return self::INVALID_SLUG_ERROR;
|
return self::INVALID_SLUG_ERROR;
|
||||||
case $e instanceof Common\InvalidArgumentException:
|
case $e instanceof Common\InvalidArgumentException:
|
||||||
|
case $e instanceof Core\InvalidArgumentException:
|
||||||
case $e instanceof Core\ValidationException:
|
case $e instanceof Core\ValidationException:
|
||||||
return self::INVALID_ARGUMENT_ERROR;
|
return self::INVALID_ARGUMENT_ERROR;
|
||||||
case $e instanceof Rest\AuthenticationException:
|
case $e instanceof Rest\AuthenticationException:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Rest\Action;
|
namespace ShlinkioTest\Shlink\Rest\Action\ShortCode;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
|
@ -9,16 +9,16 @@ use Prophecy\Prophecy\ObjectProphecy;
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||||
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
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 Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||||
use Zend\Diactoros\ServerRequestFactory;
|
use Zend\Diactoros\ServerRequestFactory;
|
||||||
use Zend\Diactoros\Uri;
|
use Zend\Diactoros\Uri;
|
||||||
use Zend\I18n\Translator\Translator;
|
use Zend\I18n\Translator\Translator;
|
||||||
|
|
||||||
class CreateShortcodeActionTest extends TestCase
|
class CreateShortCodeActionTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var CreateShortcodeAction
|
* @var CreateShortCodeAction
|
||||||
*/
|
*/
|
||||||
protected $action;
|
protected $action;
|
||||||
/**
|
/**
|
||||||
|
@ -29,7 +29,7 @@ class CreateShortcodeActionTest extends TestCase
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$this->urlShortener = $this->prophesize(UrlShortener::class);
|
$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',
|
'schema' => 'http',
|
||||||
'hostname' => 'foo.com',
|
'hostname' => 'foo.com',
|
||||||
]);
|
]);
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Rest\Action;
|
namespace ShlinkioTest\Shlink\Rest\Action\ShortCode;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
|
@ -9,7 +9,7 @@ use Prophecy\Prophecy\ObjectProphecy;
|
||||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
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 Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||||
use Zend\Diactoros\Response\JsonResponse;
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
use Zend\Diactoros\ServerRequestFactory;
|
use Zend\Diactoros\ServerRequestFactory;
|
|
@ -1,21 +1,21 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Rest\Action;
|
namespace ShlinkioTest\Shlink\Rest\Action\ShortCode;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||||
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
||||||
use Shlinkio\Shlink\Rest\Action\EditShortcodeTagsAction;
|
use Shlinkio\Shlink\Rest\Action\ShortCode\EditShortCodeTagsAction;
|
||||||
use Zend\Diactoros\ServerRequestFactory;
|
use Zend\Diactoros\ServerRequestFactory;
|
||||||
use Zend\I18n\Translator\Translator;
|
use Zend\I18n\Translator\Translator;
|
||||||
|
|
||||||
class EditShortcodeTagsActionTest extends TestCase
|
class EditShortCodeTagsActionTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var EditShortcodeTagsAction
|
* @var EditShortCodeTagsAction
|
||||||
*/
|
*/
|
||||||
protected $action;
|
protected $action;
|
||||||
/**
|
/**
|
||||||
|
@ -26,7 +26,7 @@ class EditShortcodeTagsActionTest extends TestCase
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$this->shortUrlService = $this->prophesize(ShortUrlService::class);
|
$this->shortUrlService = $this->prophesize(ShortUrlService::class);
|
||||||
$this->action = new EditShortcodeTagsAction($this->shortUrlService->reveal(), Translator::factory([]));
|
$this->action = new EditShortCodeTagsAction($this->shortUrlService->reveal(), Translator::factory([]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -1,12 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Rest\Action;
|
namespace ShlinkioTest\Shlink\Rest\Action\ShortCode;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
||||||
use Shlinkio\Shlink\Rest\Action\ListShortcodesAction;
|
use Shlinkio\Shlink\Rest\Action\ShortCode\ListShortCodesAction;
|
||||||
use Zend\Diactoros\ServerRequestFactory;
|
use Zend\Diactoros\ServerRequestFactory;
|
||||||
use Zend\I18n\Translator\Translator;
|
use Zend\I18n\Translator\Translator;
|
||||||
use Zend\Paginator\Adapter\ArrayAdapter;
|
use Zend\Paginator\Adapter\ArrayAdapter;
|
||||||
|
@ -15,7 +15,7 @@ use Zend\Paginator\Paginator;
|
||||||
class ListShortCodesActionTest extends TestCase
|
class ListShortCodesActionTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var ListShortcodesAction
|
* @var ListShortCodesAction
|
||||||
*/
|
*/
|
||||||
protected $action;
|
protected $action;
|
||||||
/**
|
/**
|
||||||
|
@ -26,7 +26,7 @@ class ListShortCodesActionTest extends TestCase
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$this->service = $this->prophesize(ShortUrlService::class);
|
$this->service = $this->prophesize(ShortUrlService::class);
|
||||||
$this->action = new ListShortcodesAction($this->service->reveal(), Translator::factory([]));
|
$this->action = new ListShortCodesAction($this->service->reveal(), Translator::factory([]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -1,14 +1,14 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Rest\Action;
|
namespace ShlinkioTest\Shlink\Rest\Action\ShortCode;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
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 Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||||
use Zend\Diactoros\ServerRequestFactory;
|
use Zend\Diactoros\ServerRequestFactory;
|
||||||
use Zend\I18n\Translator\Translator;
|
use Zend\I18n\Translator\Translator;
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Rest\Action;
|
namespace ShlinkioTest\Shlink\Rest\Action\Visit;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
|
@ -9,7 +9,7 @@ use Prophecy\Prophecy\ObjectProphecy;
|
||||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||||
use Shlinkio\Shlink\Rest\Action\GetVisitsAction;
|
use Shlinkio\Shlink\Rest\Action\Visit\GetVisitsAction;
|
||||||
use Zend\Diactoros\ServerRequestFactory;
|
use Zend\Diactoros\ServerRequestFactory;
|
||||||
use Zend\I18n\Translator\Translator;
|
use Zend\I18n\Translator\Translator;
|
||||||
|
|
|
@ -37,9 +37,11 @@ class CheckAuthenticationMiddlewareTest extends TestCase
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$this->jwtService = $this->prophesize(JWTService::class);
|
$this->jwtService = $this->prophesize(JWTService::class);
|
||||||
$this->middleware = new CheckAuthenticationMiddleware($this->jwtService->reveal(), Translator::factory([]));
|
$this->middleware = new CheckAuthenticationMiddleware($this->jwtService->reveal(), Translator::factory([]), [
|
||||||
$this->dummyMiddleware = middleware(function ($request, $handler) {
|
AuthenticateAction::class,
|
||||||
return new Response\EmptyResponse;
|
]);
|
||||||
|
$this->dummyMiddleware = middleware(function () {
|
||||||
|
return new Response\EmptyResponse();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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'],
|
||||||
|
[[], ''],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue