mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-20 01:09:56 +03:00
Defined abstract action which handles short codes generations
This commit is contained in:
parent
2f5290b9d3
commit
e5e1aa2ff4
6 changed files with 198 additions and 88 deletions
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
|
* @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()) {
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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:
|
||||||
|
|
Loading…
Add table
Reference in a new issue