Created action which allows short URLs to be created on a single API request

This commit is contained in:
Alejandro Celaya 2018-05-03 13:21:43 +02:00
parent 28650aee2b
commit e5ef8d7f8c
8 changed files with 84 additions and 23 deletions

View file

@ -8,6 +8,7 @@ return [
'auth' => [ 'auth' => [
'routes_whitelist' => [ 'routes_whitelist' => [
Action\AuthenticateAction::class, Action\AuthenticateAction::class,
Action\ShortCode\SingleStepCreateShortCodeAction::class,
], ],
], ],

View file

@ -21,6 +21,7 @@ return [
Action\AuthenticateAction::class => ConfigAbstractFactory::class, Action\AuthenticateAction::class => ConfigAbstractFactory::class,
Action\CreateShortCodeAction::class => ConfigAbstractFactory::class, Action\CreateShortCodeAction::class => ConfigAbstractFactory::class,
Action\ShortCode\SingleStepCreateShortCodeAction::class => ConfigAbstractFactory::class,
Action\EditShortCodeAction::class => ConfigAbstractFactory::class, Action\EditShortCodeAction::class => ConfigAbstractFactory::class,
Action\ResolveUrlAction::class => ConfigAbstractFactory::class, Action\ResolveUrlAction::class => ConfigAbstractFactory::class,
Action\GetVisitsAction::class => ConfigAbstractFactory::class, Action\GetVisitsAction::class => ConfigAbstractFactory::class,
@ -49,6 +50,13 @@ return [
'config.url_shortener.domain', 'config.url_shortener.domain',
'Logger_Shlink', 'Logger_Shlink',
], ],
Action\ShortCode\SingleStepCreateShortCodeAction::class => [
Service\UrlShortener::class,
'translator',
ApiKeyService::class,
'config.url_shortener.domain',
'Logger_Shlink',
],
Action\EditShortCodeAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink',], Action\EditShortCodeAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink',],
Action\ResolveUrlAction::class => [Service\UrlShortener::class, 'translator'], Action\ResolveUrlAction::class => [Service\UrlShortener::class, 'translator'],
Action\GetVisitsAction::class => [Service\VisitsTracker::class, 'translator', 'Logger_Shlink'], Action\GetVisitsAction::class => [Service\VisitsTracker::class, 'translator', 'Logger_Shlink'],

View file

@ -10,12 +10,7 @@ return [
// Short codes // Short codes
Action\CreateShortCodeAction::getRouteDef(), Action\CreateShortCodeAction::getRouteDef(),
// [ Action\ShortCode\SingleStepCreateShortCodeAction::getRouteDef(),
// 'name' => Action\CreateShortCodeAction::class,
// 'path' => '/short-codes',
// 'middleware' => Action\CreateShortCodeAction::class,
// 'allowed_methods' => [RequestMethod::METHOD_GET],
// ],
Action\EditShortCodeAction::getRouteDef(), Action\EditShortCodeAction::getRouteDef(),
Action\ResolveUrlAction::getRouteDef(), Action\ResolveUrlAction::getRouteDef(),
Action\ListShortCodesAction::getRouteDef(), Action\ListShortCodesAction::getRouteDef(),

View file

@ -5,7 +5,6 @@ namespace Shlinkio\Shlink\Rest\Action;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use Shlinkio\Shlink\Core\Exception\InvalidArgumentException; use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
use Shlinkio\Shlink\Core\Exception\ValidationException;
use Shlinkio\Shlink\Core\Model\CreateShortCodeData; use Shlinkio\Shlink\Core\Model\CreateShortCodeData;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Rest\Action\ShortCode\AbstractCreateShortCodeAction; use Shlinkio\Shlink\Rest\Action\ShortCode\AbstractCreateShortCodeAction;
@ -19,7 +18,6 @@ class CreateShortCodeAction extends AbstractCreateShortCodeAction
/** /**
* @param Request $request * @param Request $request
* @return CreateShortCodeData * @return CreateShortCodeData
* @throws ValidationException
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/ */
@ -27,7 +25,7 @@ class CreateShortCodeAction extends AbstractCreateShortCodeAction
{ {
$postData = (array) $request->getParsedBody(); $postData = (array) $request->getParsedBody();
if (! isset($postData['longUrl'])) { if (! isset($postData['longUrl'])) {
throw new InvalidArgumentException('A URL was not provided'); throw new InvalidArgumentException($this->translator->translate('A URL was not provided'));
} }
return new CreateShortCodeData( return new CreateShortCodeData(

View file

@ -9,7 +9,6 @@ use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Core\Exception\InvalidArgumentException; 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\Exception\ValidationException;
use Shlinkio\Shlink\Core\Model\CreateShortCodeData; 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\Action\AbstractRestAction;
@ -31,7 +30,7 @@ abstract class AbstractCreateShortCodeAction extends AbstractRestAction
/** /**
* @var TranslatorInterface * @var TranslatorInterface
*/ */
private $translator; protected $translator;
public function __construct( public function __construct(
UrlShortenerInterface $urlShortener, UrlShortenerInterface $urlShortener,
@ -57,11 +56,11 @@ abstract class AbstractCreateShortCodeAction extends AbstractRestAction
$shortCodeMeta = $shortCodeData->getMeta(); $shortCodeMeta = $shortCodeData->getMeta();
$longUrl = $shortCodeData->getLongUrl(); $longUrl = $shortCodeData->getLongUrl();
$customSlug = $shortCodeMeta->getCustomSlug(); $customSlug = $shortCodeMeta->getCustomSlug();
} catch (ValidationException | InvalidArgumentException $e) { } catch (InvalidArgumentException $e) {
$this->logger->warning('Provided data is invalid.' . PHP_EOL . $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('Provided data is invalid'), 'message' => $e->getMessage(),
], self::STATUS_BAD_REQUEST); ], self::STATUS_BAD_REQUEST);
} }
@ -114,7 +113,6 @@ abstract class AbstractCreateShortCodeAction extends AbstractRestAction
/** /**
* @param Request $request * @param Request $request
* @return CreateShortCodeData * @return CreateShortCodeData
* @throws ValidationException
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
abstract protected function buildUrlToShortCodeData(Request $request): CreateShortCodeData; abstract protected function buildUrlToShortCodeData(Request $request): CreateShortCodeData;

View file

@ -0,0 +1,61 @@
<?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\Exception\ValidationException;
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']));
}
}

View file

@ -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([

View file

@ -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);
} }