From e5e1aa2ff4ec5223afe39af5853f4da748c568b2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Tue, 1 May 2018 19:35:12 +0200 Subject: [PATCH] Defined abstract action which handles short codes generations --- module/Core/src/Model/CreateShortCodeData.php | 56 +++++++++ module/Core/src/Model/ShortUrlMeta.php | 2 +- module/Rest/config/routes.config.php | 7 +- .../Rest/src/Action/CreateShortcodeAction.php | 101 +++------------ .../AbstractCreateShortCodeAction.php | 119 ++++++++++++++++++ module/Rest/src/Util/RestUtils.php | 1 + 6 files changed, 198 insertions(+), 88 deletions(-) create mode 100644 module/Core/src/Model/CreateShortCodeData.php create mode 100644 module/Rest/src/Action/ShortCode/AbstractCreateShortCodeAction.php diff --git a/module/Core/src/Model/CreateShortCodeData.php b/module/Core/src/Model/CreateShortCodeData.php new file mode 100644 index 00000000..880c32c2 --- /dev/null +++ b/module/Core/src/Model/CreateShortCodeData.php @@ -0,0 +1,56 @@ +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; + } +} diff --git a/module/Core/src/Model/ShortUrlMeta.php b/module/Core/src/Model/ShortUrlMeta.php index f79edce6..d9acf6b9 100644 --- a/module/Core/src/Model/ShortUrlMeta.php +++ b/module/Core/src/Model/ShortUrlMeta.php @@ -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()) { diff --git a/module/Rest/config/routes.config.php b/module/Rest/config/routes.config.php index e2ee8f75..709b363b 100644 --- a/module/Rest/config/routes.config.php +++ b/module/Rest/config/routes.config.php @@ -10,12 +10,7 @@ return [ Action\AuthenticateAction::getRouteDef(), // Short codes - [ - 'name' => Action\CreateShortcodeAction::class, - 'path' => '/short-codes', - 'middleware' => Action\CreateShortcodeAction::class, - 'allowed_methods' => [RequestMethod::METHOD_POST], - ], + Action\CreateShortcodeAction::getRouteDef(), // [ // 'name' => Action\CreateShortcodeAction::class, // 'path' => '/short-codes', diff --git a/module/Rest/src/Action/CreateShortcodeAction.php b/module/Rest/src/Action/CreateShortcodeAction.php index d880d552..b4fff2db 100644 --- a/module/Rest/src/Action/CreateShortcodeAction.php +++ b/module/Rest/src/Action/CreateShortcodeAction.php @@ -3,104 +3,43 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action; -use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -use Psr\Log\LoggerInterface; -use Shlinkio\Shlink\Core\Exception\InvalidUrlException; -use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; -use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; -use Shlinkio\Shlink\Rest\Util\RestUtils; -use Zend\Diactoros\Response\JsonResponse; +use Shlinkio\Shlink\Core\Exception\InvalidArgumentException; +use Shlinkio\Shlink\Core\Exception\ValidationException; +use Shlinkio\Shlink\Core\Model\CreateShortCodeData; +use Shlinkio\Shlink\Core\Model\ShortUrlMeta; +use Shlinkio\Shlink\Rest\Action\ShortCode\AbstractCreateShortCodeAction; use Zend\Diactoros\Uri; -use Zend\I18n\Translator\TranslatorInterface; -class CreateShortcodeAction extends AbstractRestAction +class CreateShortcodeAction extends AbstractCreateShortCodeAction { - /** - * @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; - } + protected const ROUTE_PATH = '/short-codes'; + protected const ROUTE_ALLOWED_METHODS = [self::METHOD_POST]; /** * @param Request $request - * @return Response + * @return CreateShortCodeData + * @throws ValidationException + * @throws InvalidArgumentException * @throws \InvalidArgumentException */ - public function handle(Request $request): Response + protected function buildUrlToShortCodeData(Request $request): CreateShortCodeData { $postData = (array) $request->getParsedBody(); if (! isset($postData['longUrl'])) { - return new JsonResponse([ - 'error' => RestUtils::INVALID_ARGUMENT_ERROR, - 'message' => $this->translator->translate('A URL was not provided'), - ], self::STATUS_BAD_REQUEST); + throw new InvalidArgumentException('A URL was not provided'); } - $longUrl = $postData['longUrl']; - $customSlug = $postData['customSlug'] ?? null; - try { - $shortCode = $this->urlShortener->urlToShortCode( - new Uri($longUrl), - (array) ($postData['tags'] ?? []), + return new CreateShortCodeData( + new Uri($postData['longUrl']), + (array) ($postData['tags'] ?? []), + ShortUrlMeta::createFromParams( $this->getOptionalDate($postData, 'validSince'), $this->getOptionalDate($postData, 'validUntil'), - $customSlug, + $postData['customSlug'] ?? 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) diff --git a/module/Rest/src/Action/ShortCode/AbstractCreateShortCodeAction.php b/module/Rest/src/Action/ShortCode/AbstractCreateShortCodeAction.php new file mode 100644 index 00000000..3bbc15bb --- /dev/null +++ b/module/Rest/src/Action/ShortCode/AbstractCreateShortCodeAction.php @@ -0,0 +1,119 @@ +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; +} diff --git a/module/Rest/src/Util/RestUtils.php b/module/Rest/src/Util/RestUtils.php index 1d697676..666469ed 100644 --- a/module/Rest/src/Util/RestUtils.php +++ b/module/Rest/src/Util/RestUtils.php @@ -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: