mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-14 04:00:57 +03:00
Merge branch 'develop'
This commit is contained in:
commit
b3664597b0
47 changed files with 857 additions and 179 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,5 +1,15 @@
|
|||
## 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
|
||||
|
||||
**Tasks**
|
||||
|
|
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
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
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\CheckAuthenticationMiddleware;
|
||||
use Shlinkio\Shlink\Rest\Middleware\CrossDomainMiddleware;
|
||||
|
@ -16,6 +16,7 @@ return [
|
|||
'pre-routing' => [
|
||||
'middleware' => [
|
||||
ErrorHandler::class,
|
||||
Expressive\Helper\ContentLengthMiddleware::class,
|
||||
LocaleMiddleware::class,
|
||||
],
|
||||
'priority' => 11,
|
||||
|
@ -49,7 +50,7 @@ return [
|
|||
'post-routing' => [
|
||||
'middleware' => [
|
||||
Expressive\Router\Middleware\DispatchMiddleware::class,
|
||||
NotFoundDelegate::class,
|
||||
NotFoundHandler::class,
|
||||
],
|
||||
'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": {
|
||||
"$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"
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
@ -33,14 +33,10 @@ return [
|
|||
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 => [
|
||||
|
@ -60,14 +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],
|
||||
],
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ 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;
|
||||
|
@ -30,15 +32,21 @@ abstract class AbstractTrackingAction implements MiddlewareInterface
|
|||
* @var AppOptions
|
||||
*/
|
||||
private $appOptions;
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
public function __construct(
|
||||
UrlShortenerInterface $urlShortener,
|
||||
VisitsTrackerInterface $visitTracker,
|
||||
AppOptions $appOptions
|
||||
AppOptions $appOptions,
|
||||
LoggerInterface $logger = null
|
||||
) {
|
||||
$this->urlShortener = $urlShortener;
|
||||
$this->visitTracker = $visitTracker;
|
||||
$this->appOptions = $appOptions;
|
||||
$this->logger = $logger ?: new NullLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,6 +74,7 @@ abstract class AbstractTrackingAction implements MiddlewareInterface
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ 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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,6 +63,7 @@ class PreviewAction implements MiddlewareInterface
|
|||
$imagePath = $this->previewGenerator->generatePreview($url);
|
||||
return $this->generateImageResponse($imagePath);
|
||||
} catch (InvalidShortCodeException | EntityDoesNotExistException | PreviewGenerationException $e) {
|
||||
$this->logger->warning('An error occurred while generating preview image.' . PHP_EOL . $e);
|
||||
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\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Zend\Expressive\Router\Exception\RuntimeException;
|
||||
use Zend\Expressive\Router\RouterInterface;
|
||||
|
||||
class QrCodeAction implements MiddlewareInterface
|
||||
|
@ -52,6 +53,8 @@ class QrCodeAction implements MiddlewareInterface
|
|||
* @param RequestHandlerInterface $handler
|
||||
*
|
||||
* @return Response
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||
{
|
||||
|
@ -59,11 +62,8 @@ class QrCodeAction implements MiddlewareInterface
|
|||
$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, $handler);
|
||||
} catch (EntityDoesNotExistException $e) {
|
||||
$this->logger->warning('Tried to create a QR code with a not found short code' . PHP_EOL . $e);
|
||||
} catch (InvalidShortCodeException | EntityDoesNotExistException $e) {
|
||||
$this->logger->warning('An error occurred while creating QR code' . PHP_EOL . $e);
|
||||
return $this->buildErrorResponse($request, $handler);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace Shlinkio\Shlink\Core\Action\Util;
|
|||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Core\Response\NotFoundDelegate;
|
||||
use Shlinkio\Shlink\Core\Response\NotFoundHandler;
|
||||
|
||||
trait ErrorResponseBuilderTrait
|
||||
{
|
||||
|
@ -14,7 +14,7 @@ trait ErrorResponseBuilderTrait
|
|||
ServerRequestInterface $request,
|
||||
RequestHandlerInterface $handler
|
||||
): 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);
|
||||
}
|
||||
}
|
||||
|
|
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()) {
|
||||
|
|
|
@ -6,13 +6,13 @@ namespace Shlinkio\Shlink\Core\Response;
|
|||
use Fig\Http\Message\StatusCodeInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface as DelegateInterface;
|
||||
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
|
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
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."
|
||||
|
|
|
@ -11,6 +11,9 @@ use Psr\Log\NullLogger;
|
|||
|
||||
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 RequestHandlerInterface, RequestMet
|
|||
{
|
||||
$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
|
||||
{
|
||||
protected const ROUTE_PATH = '/authenticate';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_POST];
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
<?php
|
||||
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\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
|
||||
|
@ -27,7 +30,7 @@ class CreateShortcodeAction extends AbstractRestAction
|
|||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
protected $translator;
|
||||
|
||||
public function __construct(
|
||||
UrlShortenerInterface $urlShortener,
|
||||
|
@ -48,31 +51,34 @@ class CreateShortcodeAction extends AbstractRestAction
|
|||
*/
|
||||
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,
|
||||
]);
|
||||
|
@ -80,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
|
||||
),
|
||||
|
@ -89,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
|
||||
),
|
||||
|
@ -103,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,7 +1,7 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
namespace Shlinkio\Shlink\Rest\Action\ShortCode;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
@ -9,6 +9,7 @@ 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;
|
||||
|
@ -16,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
|
||||
*/
|
|
@ -1,19 +1,23 @@
|
|||
<?php
|
||||
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\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
|
||||
*/
|
|
@ -1,21 +1,25 @@
|
|||
<?php
|
||||
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\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
|
||||
*/
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
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\ServerRequestInterface as Request;
|
||||
|
@ -9,12 +9,16 @@ 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
|
||||
*/
|
|
@ -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
|
||||
{
|
||||
protected const ROUTE_PATH = '/tags';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_POST];
|
||||
|
||||
/**
|
||||
* @var TagServiceInterface
|
||||
*/
|
||||
|
|
|
@ -12,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
|
||||
*/
|
||||
|
|
|
@ -12,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
|
||||
*/
|
||||
|
|
|
@ -16,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
|
||||
*/
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
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\ServerRequestInterface as Request;
|
||||
|
@ -9,12 +9,16 @@ 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
|
||||
*/
|
|
@ -10,7 +10,6 @@ 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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -64,8 +69,8 @@ class CheckAuthenticationMiddleware implements MiddlewareInterface, StatusCodeIn
|
|||
$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 $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);
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,16 +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 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;
|
||||
/**
|
||||
|
@ -29,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',
|
||||
]);
|
|
@ -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,7 +9,7 @@ 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;
|
|
@ -1,21 +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 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;
|
||||
/**
|
||||
|
@ -26,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([]));
|
||||
}
|
||||
|
||||
/**
|
|
@ -1,12 +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 Shlinkio\Shlink\Rest\Action\ShortCode\ListShortCodesAction;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
use Zend\Paginator\Adapter\ArrayAdapter;
|
||||
|
@ -15,7 +15,7 @@ use Zend\Paginator\Paginator;
|
|||
class ListShortCodesActionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ListShortcodesAction
|
||||
* @var ListShortCodesAction
|
||||
*/
|
||||
protected $action;
|
||||
/**
|
||||
|
@ -26,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([]));
|
||||
}
|
||||
|
||||
/**
|
|
@ -1,14 +1,14 @@
|
|||
<?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 Zend\Diactoros\ServerRequestFactory;
|
||||
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
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Action;
|
||||
namespace ShlinkioTest\Shlink\Rest\Action\Visit;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
|
@ -9,7 +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 Shlinkio\Shlink\Rest\Action\Visit\GetVisitsAction;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
|
@ -37,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();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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…
Add table
Reference in a new issue