From 1da285a63a9aadef213925e4d7c9d40589cfa6fd Mon Sep 17 00:00:00 2001 From: Alejandro Celaya <alejandro@alejandrocelaya.com> Date: Sun, 21 Aug 2016 16:52:26 +0200 Subject: [PATCH] Created action to set the togas for a short url --- .../Exception/InvalidShortCodeException.php | 7 +- module/Core/src/Service/ShortUrlService.php | 26 +++++++ .../src/Service/ShortUrlServiceInterface.php | 9 +++ module/Core/src/Service/UrlShortener.php | 2 +- module/Core/src/Util/TagManagerTrait.php | 12 ++++ module/Rest/config/routes.config.php | 6 ++ module/Rest/src/Action/EditTagsAction.php | 70 +++++++++++++++++++ 7 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 module/Rest/src/Action/EditTagsAction.php diff --git a/module/Core/src/Exception/InvalidShortCodeException.php b/module/Core/src/Exception/InvalidShortCodeException.php index b1e23d0b..85cc3d54 100644 --- a/module/Core/src/Exception/InvalidShortCodeException.php +++ b/module/Core/src/Exception/InvalidShortCodeException.php @@ -5,7 +5,7 @@ use Shlinkio\Shlink\Common\Exception\RuntimeException; class InvalidShortCodeException extends RuntimeException { - public static function fromShortCode($shortCode, $charSet, \Exception $previous = null) + public static function fromCharset($shortCode, $charSet, \Exception $previous = null) { $code = isset($previous) ? $previous->getCode() : -1; return new static( @@ -14,4 +14,9 @@ class InvalidShortCodeException extends RuntimeException $previous ); } + + public static function fromNotFoundShortCode($shortCode) + { + return new static(sprintf('Provided short code "%s" does not belong to a short URL', $shortCode)); + } } diff --git a/module/Core/src/Service/ShortUrlService.php b/module/Core/src/Service/ShortUrlService.php index 84552115..60845b88 100644 --- a/module/Core/src/Service/ShortUrlService.php +++ b/module/Core/src/Service/ShortUrlService.php @@ -5,11 +5,15 @@ use Acelaya\ZsmAnnotatedServices\Annotation\Inject; use Doctrine\ORM\EntityManagerInterface; use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter; use Shlinkio\Shlink\Core\Entity\ShortUrl; +use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; +use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Zend\Paginator\Paginator; class ShortUrlService implements ShortUrlServiceInterface { + use TagManagerTrait; + /** * @var EntityManagerInterface */ @@ -40,4 +44,26 @@ class ShortUrlService implements ShortUrlServiceInterface return $paginator; } + + /** + * @param string $shortCode + * @param string[] $tags + * @return ShortUrl + * @throws InvalidShortCodeException + */ + public function setTagsByShortCode($shortCode, array $tags = []) + { + /** @var ShortUrl $shortUrl */ + $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([ + 'shortCode' => $shortCode, + ]); + if (! isset($shortUrl)) { + throw InvalidShortCodeException::fromNotFoundShortCode($shortCode); + } + + $shortUrl->setTags($this->tagNamesToEntities($this->em, $tags)); + $this->em->flush(); + + return $shortUrl; + } } diff --git a/module/Core/src/Service/ShortUrlServiceInterface.php b/module/Core/src/Service/ShortUrlServiceInterface.php index c6885c39..5ad304ee 100644 --- a/module/Core/src/Service/ShortUrlServiceInterface.php +++ b/module/Core/src/Service/ShortUrlServiceInterface.php @@ -2,6 +2,7 @@ namespace Shlinkio\Shlink\Core\Service; use Shlinkio\Shlink\Core\Entity\ShortUrl; +use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Zend\Paginator\Paginator; interface ShortUrlServiceInterface @@ -11,4 +12,12 @@ interface ShortUrlServiceInterface * @return ShortUrl[]|Paginator */ public function listShortUrls($page = 1); + + /** + * @param string $shortCode + * @param string[] $tags + * @return ShortUrl + * @throws InvalidShortCodeException + */ + public function setTagsByShortCode($shortCode, array $tags = []); } diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index 38be02f5..bc422cab 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -161,7 +161,7 @@ class UrlShortener implements UrlShortenerInterface // Validate short code format if (! preg_match('|[' . $this->chars . "]+|", $shortCode)) { - throw InvalidShortCodeException::fromShortCode($shortCode, $this->chars); + throw InvalidShortCodeException::fromCharset($shortCode, $this->chars); } /** @var ShortUrl $shortUrl */ diff --git a/module/Core/src/Util/TagManagerTrait.php b/module/Core/src/Util/TagManagerTrait.php index f4dec468..9ca02b92 100644 --- a/module/Core/src/Util/TagManagerTrait.php +++ b/module/Core/src/Util/TagManagerTrait.php @@ -16,6 +16,7 @@ trait TagManagerTrait { $entities = []; foreach ($tags as $tagName) { + $tagName = $this->normalizeTagName($tagName); $tag = $em->getRepository(Tag::class)->findOneBy(['name' => $tagName]) ?: (new Tag())->setName($tagName); $em->persist($tag); $entities[] = $tag; @@ -23,4 +24,15 @@ trait TagManagerTrait return new Collections\ArrayCollection($entities); } + + /** + * Tag names are trimmed, lowercased and spaces are replaced by dashes + * + * @param string $tagName + * @return string + */ + protected function normalizeTagName($tagName) + { + return str_replace(' ', '-', strtolower(trim($tagName))); + } } diff --git a/module/Rest/config/routes.config.php b/module/Rest/config/routes.config.php index 4cc0f510..d27b9c6d 100644 --- a/module/Rest/config/routes.config.php +++ b/module/Rest/config/routes.config.php @@ -34,6 +34,12 @@ return [ 'middleware' => Action\GetVisitsAction::class, 'allowed_methods' => ['GET', 'OPTIONS'], ], + [ + 'name' => 'rest-edit-tags', + 'path' => '/rest/short-codes/{shortCode}/tags', + 'middleware' => Action\EditTagsAction::class, + 'allowed_methods' => ['PUT', 'OPTIONS'], + ], ], ]; diff --git a/module/Rest/src/Action/EditTagsAction.php b/module/Rest/src/Action/EditTagsAction.php new file mode 100644 index 00000000..680980ed --- /dev/null +++ b/module/Rest/src/Action/EditTagsAction.php @@ -0,0 +1,70 @@ +<?php +namespace Shlinkio\Shlink\Rest\Action; + +use Acelaya\ZsmAnnotatedServices\Annotation\Inject; +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\ShortUrlService; +use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface; +use Shlinkio\Shlink\Rest\Util\RestUtils; +use Zend\Diactoros\Response\JsonResponse; +use Zend\I18n\Translator\TranslatorInterface; + +class EditTagsAction extends AbstractRestAction +{ + /** + * @var ShortUrlServiceInterface + */ + private $shortUrlService; + /** + * @var TranslatorInterface + */ + private $translator; + + /** + * EditTagsAction constructor. + * @param ShortUrlServiceInterface $shortUrlService + * @param TranslatorInterface $translator + * @param LoggerInterface|null $logger + * + * @Inject({ShortUrlService::class, "translator", "Logger_Shlink"}) + */ + public function __construct( + ShortUrlServiceInterface $shortUrlService, + TranslatorInterface $translator, + LoggerInterface $logger = null + ) { + parent::__construct($logger); + $this->shortUrlService = $shortUrlService; + $this->translator = $translator; + } + + /** + * @param Request $request + * @param Response $response + * @param callable|null $out + * @return null|Response + */ + protected function dispatch(Request $request, Response $response, callable $out = null) + { + $shortCode = $request->getAttribute('shortCode'); + $bodyParams = $request->getParsedBody(); + + if (! isset($bodyParams['tags'])) { + return new JsonResponse([ + 'error' => RestUtils::INVALID_ARGUMENT_ERROR, + 'message' => $this->translator->translate('A list of tags was not provided'), + ], 400); + } + $tags = $bodyParams['tags']; + + try { + $shortUrl = $this->shortUrlService->setTagsByShortCode($shortCode, $tags); + return new JsonResponse(['tags' => $shortUrl->getTags()->toArray()]); + } catch (InvalidShortCodeException $e) { + return $out($request, $response->withStatus(404), 'Not found'); + } + } +}