Defined abstract action which handles short codes generations

This commit is contained in:
Alejandro Celaya 2018-05-01 19:35:12 +02:00
parent 2f5290b9d3
commit e5e1aa2ff4
6 changed files with 198 additions and 88 deletions

View 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;
}
}

View file

@ -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()) {

View file

@ -10,12 +10,7 @@ return [
Action\AuthenticateAction::getRouteDef(), Action\AuthenticateAction::getRouteDef(),
// Short codes // Short codes
[ Action\CreateShortcodeAction::getRouteDef(),
'name' => Action\CreateShortcodeAction::class,
'path' => '/short-codes',
'middleware' => Action\CreateShortcodeAction::class,
'allowed_methods' => [RequestMethod::METHOD_POST],
],
// [ // [
// 'name' => Action\CreateShortcodeAction::class, // 'name' => Action\CreateShortcodeAction::class,
// 'path' => '/short-codes', // 'path' => '/short-codes',

View file

@ -3,104 +3,43 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Rest\Action; namespace Shlinkio\Shlink\Rest\Action;
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 Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\ValidationException;
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Model\CreateShortCodeData;
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Rest\Util\RestUtils; use Shlinkio\Shlink\Rest\Action\ShortCode\AbstractCreateShortCodeAction;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\Uri; use Zend\Diactoros\Uri;
use Zend\I18n\Translator\TranslatorInterface;
class CreateShortcodeAction extends AbstractRestAction class CreateShortcodeAction extends AbstractCreateShortCodeAction
{ {
/** protected const ROUTE_PATH = '/short-codes';
* @var UrlShortenerInterface protected const ROUTE_ALLOWED_METHODS = [self::METHOD_POST];
*/
private $urlShortener;
/**
* @var array
*/
private $domainConfig;
/**
* @var TranslatorInterface
*/
private $translator;
public function __construct(
UrlShortenerInterface $urlShortener,
TranslatorInterface $translator,
array $domainConfig,
LoggerInterface $logger = null
) {
parent::__construct($logger);
$this->urlShortener = $urlShortener;
$this->translator = $translator;
$this->domainConfig = $domainConfig;
}
/** /**
* @param Request $request * @param Request $request
* @return Response * @return CreateShortCodeData
* @throws ValidationException
* @throws InvalidArgumentException
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/ */
public function handle(Request $request): Response protected function buildUrlToShortCodeData(Request $request): CreateShortCodeData
{ {
$postData = (array) $request->getParsedBody(); $postData = (array) $request->getParsedBody();
if (! isset($postData['longUrl'])) { if (! isset($postData['longUrl'])) {
return new JsonResponse([ throw new InvalidArgumentException('A URL was not provided');
'error' => RestUtils::INVALID_ARGUMENT_ERROR,
'message' => $this->translator->translate('A URL was not provided'),
], self::STATUS_BAD_REQUEST);
} }
$longUrl = $postData['longUrl'];
$customSlug = $postData['customSlug'] ?? null;
try { return new CreateShortCodeData(
$shortCode = $this->urlShortener->urlToShortCode( new Uri($postData['longUrl']),
new Uri($longUrl), (array) ($postData['tags'] ?? []),
(array) ($postData['tags'] ?? []), ShortUrlMeta::createFromParams(
$this->getOptionalDate($postData, 'validSince'), $this->getOptionalDate($postData, 'validSince'),
$this->getOptionalDate($postData, 'validUntil'), $this->getOptionalDate($postData, 'validUntil'),
$customSlug, $postData['customSlug'] ?? null,
isset($postData['maxVisits']) ? (int) $postData['maxVisits'] : null isset($postData['maxVisits']) ? (int) $postData['maxVisits'] : null
); )
$shortUrl = (new Uri())->withPath($shortCode) );
->withScheme($this->domainConfig['schema'])
->withHost($this->domainConfig['hostname']);
return new JsonResponse([
'longUrl' => $longUrl,
'shortUrl' => (string) $shortUrl,
'shortCode' => $shortCode,
]);
} catch (InvalidUrlException $e) {
$this->logger->warning('Provided Invalid URL.' . PHP_EOL . $e);
return new JsonResponse([
'error' => RestUtils::getRestErrorCodeFromException($e),
'message' => \sprintf(
$this->translator->translate('Provided URL %s is invalid. Try with a different one.'),
$longUrl
),
], self::STATUS_BAD_REQUEST);
} catch (NonUniqueSlugException $e) {
$this->logger->warning('Provided non-unique slug.' . PHP_EOL . $e);
return new JsonResponse([
'error' => RestUtils::getRestErrorCodeFromException($e),
'message' => \sprintf(
$this->translator->translate('Provided slug %s is already in use. Try with a different one.'),
$customSlug
),
], self::STATUS_BAD_REQUEST);
} catch (\Throwable $e) {
$this->logger->error('Unexpected error creating shortcode.' . PHP_EOL . $e);
return new JsonResponse([
'error' => RestUtils::UNKNOWN_ERROR,
'message' => $this->translator->translate('Unexpected error occurred'),
], self::STATUS_INTERNAL_SERVER_ERROR);
}
} }
private function getOptionalDate(array $postData, string $fieldName) private function getOptionalDate(array $postData, string $fieldName)

View file

@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
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\Exception\ValidationException;
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;
abstract class AbstractCreateShortCodeAction extends AbstractRestAction
{
/**
* @var UrlShortenerInterface
*/
private $urlShortener;
/**
* @var array
*/
private $domainConfig;
/**
* @var TranslatorInterface
*/
private $translator;
public function __construct(
UrlShortenerInterface $urlShortener,
TranslatorInterface $translator,
array $domainConfig,
LoggerInterface $logger = null
) {
parent::__construct($logger);
$this->urlShortener = $urlShortener;
$this->translator = $translator;
$this->domainConfig = $domainConfig;
}
/**
* @param Request $request
* @return Response
* @throws \InvalidArgumentException
*/
public function handle(Request $request): Response
{
try {
$shortCodeData = $this->buildUrlToShortCodeData($request);
$shortCodeMeta = $shortCodeData->getMeta();
$longUrl = $shortCodeData->getLongUrl();
$customSlug = $shortCodeMeta->getCustomSlug();
$shortCode = $this->urlShortener->urlToShortCode(
$longUrl,
$shortCodeData->getTags(),
$shortCodeMeta->getValidSince(),
$shortCodeMeta->getValidUntil(),
$customSlug,
$shortCodeMeta->getMaxVisits()
);
$shortUrl = (new Uri())->withPath($shortCode)
->withScheme($this->domainConfig['schema'])
->withHost($this->domainConfig['hostname']);
// TODO Make response to be generated based on Accept header
return new JsonResponse([
'longUrl' => (string) $longUrl,
'shortUrl' => (string) $shortUrl,
'shortCode' => $shortCode,
]);
} catch (InvalidUrlException $e) {
$this->logger->warning('Provided Invalid URL.' . PHP_EOL . $e);
return new JsonResponse([
'error' => RestUtils::getRestErrorCodeFromException($e),
'message' => \sprintf(
$this->translator->translate('Provided URL %s is invalid. Try with a different one.'),
$longUrl
),
], self::STATUS_BAD_REQUEST);
} catch (NonUniqueSlugException $e) {
$this->logger->warning('Provided non-unique slug.' . PHP_EOL . $e);
return new JsonResponse([
'error' => RestUtils::getRestErrorCodeFromException($e),
'message' => \sprintf(
$this->translator->translate('Provided slug %s is already in use. Try with a different one.'),
$customSlug
),
], self::STATUS_BAD_REQUEST);
} catch (ValidationException | InvalidArgumentException $e) {
$this->logger->warning('Provided data is invalid.' . PHP_EOL . $e);
return new JsonResponse([
'error' => RestUtils::INVALID_ARGUMENT_ERROR,
'message' => $this->translator->translate('Provided data is invalid'),
], self::STATUS_BAD_REQUEST);
} catch (\Throwable $e) {
$this->logger->error('Unexpected error creating shortcode.' . PHP_EOL . $e);
return new JsonResponse([
'error' => RestUtils::UNKNOWN_ERROR,
'message' => $this->translator->translate('Unexpected error occurred'),
], self::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* @param Request $request
* @return CreateShortCodeData
* @throws ValidationException
* @throws InvalidArgumentException
*/
abstract protected function buildUrlToShortCodeData(Request $request): CreateShortCodeData;
}

View file

@ -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: