mirror of
https://github.com/shlinkio/shlink.git
synced 2024-11-29 01:18:59 +03:00
Implemented EditShortCodeAction
This commit is contained in:
parent
7ba9eb8e2c
commit
84094a51a2
7 changed files with 189 additions and 13 deletions
|
@ -9,7 +9,7 @@ return [
|
||||||
'routes' => [
|
'routes' => [
|
||||||
[
|
[
|
||||||
'name' => Action\AuthenticateAction::class,
|
'name' => Action\AuthenticateAction::class,
|
||||||
'path' => '/rest/v{version:1}/authenticate',
|
'path' => '/authenticate',
|
||||||
'middleware' => Action\AuthenticateAction::class,
|
'middleware' => Action\AuthenticateAction::class,
|
||||||
'allowed_methods' => [RequestMethod::METHOD_POST],
|
'allowed_methods' => [RequestMethod::METHOD_POST],
|
||||||
],
|
],
|
||||||
|
@ -17,25 +17,31 @@ return [
|
||||||
// Short codes
|
// Short codes
|
||||||
[
|
[
|
||||||
'name' => Action\CreateShortcodeAction::class,
|
'name' => Action\CreateShortcodeAction::class,
|
||||||
'path' => '/rest/v{version:1}/short-codes',
|
'path' => '/short-codes',
|
||||||
'middleware' => Action\CreateShortcodeAction::class,
|
'middleware' => Action\CreateShortcodeAction::class,
|
||||||
'allowed_methods' => [RequestMethod::METHOD_POST],
|
'allowed_methods' => [RequestMethod::METHOD_POST],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'name' => Action\EditShortCodeAction::class,
|
||||||
|
'path' => '/short-codes/{shortCode}',
|
||||||
|
'middleware' => Action\EditShortCodeAction::class,
|
||||||
|
'allowed_methods' => [RequestMethod::METHOD_PUT],
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'name' => Action\ResolveUrlAction::class,
|
'name' => Action\ResolveUrlAction::class,
|
||||||
'path' => '/rest/v{version:1}/short-codes/{shortCode}',
|
'path' => '/short-codes/{shortCode}',
|
||||||
'middleware' => Action\ResolveUrlAction::class,
|
'middleware' => Action\ResolveUrlAction::class,
|
||||||
'allowed_methods' => [RequestMethod::METHOD_GET],
|
'allowed_methods' => [RequestMethod::METHOD_GET],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => Action\ListShortcodesAction::class,
|
'name' => Action\ListShortcodesAction::class,
|
||||||
'path' => '/rest/v{version:1}/short-codes',
|
'path' => '/short-codes',
|
||||||
'middleware' => Action\ListShortcodesAction::class,
|
'middleware' => Action\ListShortcodesAction::class,
|
||||||
'allowed_methods' => [RequestMethod::METHOD_GET],
|
'allowed_methods' => [RequestMethod::METHOD_GET],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => Action\EditShortcodeTagsAction::class,
|
'name' => Action\EditShortcodeTagsAction::class,
|
||||||
'path' => '/rest/v{version:1}/short-codes/{shortCode}/tags',
|
'path' => '/short-codes/{shortCode}/tags',
|
||||||
'middleware' => Action\EditShortcodeTagsAction::class,
|
'middleware' => Action\EditShortcodeTagsAction::class,
|
||||||
'allowed_methods' => [RequestMethod::METHOD_PUT],
|
'allowed_methods' => [RequestMethod::METHOD_PUT],
|
||||||
],
|
],
|
||||||
|
@ -43,7 +49,7 @@ return [
|
||||||
// Visits
|
// Visits
|
||||||
[
|
[
|
||||||
'name' => Action\GetVisitsAction::class,
|
'name' => Action\GetVisitsAction::class,
|
||||||
'path' => '/rest/v{version:1}/short-codes/{shortCode}/visits',
|
'path' => '/short-codes/{shortCode}/visits',
|
||||||
'middleware' => Action\GetVisitsAction::class,
|
'middleware' => Action\GetVisitsAction::class,
|
||||||
'allowed_methods' => [RequestMethod::METHOD_GET],
|
'allowed_methods' => [RequestMethod::METHOD_GET],
|
||||||
],
|
],
|
||||||
|
@ -51,25 +57,25 @@ return [
|
||||||
// Tags
|
// Tags
|
||||||
[
|
[
|
||||||
'name' => Action\Tag\ListTagsAction::class,
|
'name' => Action\Tag\ListTagsAction::class,
|
||||||
'path' => '/rest/v{version:1}/tags',
|
'path' => '/tags',
|
||||||
'middleware' => Action\Tag\ListTagsAction::class,
|
'middleware' => Action\Tag\ListTagsAction::class,
|
||||||
'allowed_methods' => [RequestMethod::METHOD_GET],
|
'allowed_methods' => [RequestMethod::METHOD_GET],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => Action\Tag\DeleteTagsAction::class,
|
'name' => Action\Tag\DeleteTagsAction::class,
|
||||||
'path' => '/rest/v{version:1}/tags',
|
'path' => '/tags',
|
||||||
'middleware' => Action\Tag\DeleteTagsAction::class,
|
'middleware' => Action\Tag\DeleteTagsAction::class,
|
||||||
'allowed_methods' => [RequestMethod::METHOD_DELETE],
|
'allowed_methods' => [RequestMethod::METHOD_DELETE],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => Action\Tag\CreateTagsAction::class,
|
'name' => Action\Tag\CreateTagsAction::class,
|
||||||
'path' => '/rest/v{version:1}/tags',
|
'path' => '/tags',
|
||||||
'middleware' => Action\Tag\CreateTagsAction::class,
|
'middleware' => Action\Tag\CreateTagsAction::class,
|
||||||
'allowed_methods' => [RequestMethod::METHOD_POST],
|
'allowed_methods' => [RequestMethod::METHOD_POST],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => Action\Tag\UpdateTagAction::class,
|
'name' => Action\Tag\UpdateTagAction::class,
|
||||||
'path' => '/rest/v{version:1}/tags',
|
'path' => '/tags',
|
||||||
'middleware' => Action\Tag\UpdateTagAction::class,
|
'middleware' => Action\Tag\UpdateTagAction::class,
|
||||||
'allowed_methods' => [RequestMethod::METHOD_PUT],
|
'allowed_methods' => [RequestMethod::METHOD_PUT],
|
||||||
],
|
],
|
||||||
|
|
|
@ -6,9 +6,36 @@ namespace Shlinkio\Shlink\Rest\Action;
|
||||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Shlinkio\Shlink\Core\Exception;
|
||||||
|
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||||
|
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||||
|
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||||
|
use Zend\Diactoros\Response\EmptyResponse;
|
||||||
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
|
||||||
class EditShortCodeAction extends AbstractRestAction
|
class EditShortCodeAction extends AbstractRestAction
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var ShortUrlServiceInterface
|
||||||
|
*/
|
||||||
|
private $shortUrlService;
|
||||||
|
/**
|
||||||
|
* @var TranslatorInterface
|
||||||
|
*/
|
||||||
|
private $translator;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
ShortUrlServiceInterface $shortUrlService,
|
||||||
|
TranslatorInterface $translator,
|
||||||
|
LoggerInterface $logger = null
|
||||||
|
) {
|
||||||
|
parent::__construct($logger);
|
||||||
|
$this->shortUrlService = $shortUrlService;
|
||||||
|
$this->translator = $translator;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process an incoming server request and return a response, optionally delegating
|
* Process an incoming server request and return a response, optionally delegating
|
||||||
* to the next middleware component to create the response.
|
* to the next middleware component to create the response.
|
||||||
|
@ -17,8 +44,31 @@ class EditShortCodeAction extends AbstractRestAction
|
||||||
* @param DelegateInterface $delegate
|
* @param DelegateInterface $delegate
|
||||||
*
|
*
|
||||||
* @return ResponseInterface
|
* @return ResponseInterface
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface
|
public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface
|
||||||
{
|
{
|
||||||
|
$postData = (array) $request->getParsedBody();
|
||||||
|
$shortCode = $request->getAttribute('shortCode', '');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->shortUrlService->updateMetadataByShortCode(
|
||||||
|
$shortCode,
|
||||||
|
ShortUrlMeta::createFromRawData($postData)
|
||||||
|
);
|
||||||
|
return new EmptyResponse();
|
||||||
|
} catch (Exception\InvalidShortCodeException $e) {
|
||||||
|
$this->logger->warning('Provided data is invalid.' . PHP_EOL . $e);
|
||||||
|
return new JsonResponse([
|
||||||
|
'error' => RestUtils::getRestErrorCodeFromException($e),
|
||||||
|
'message' => \sprintf($this->translator->translate('No URL found for short code "%s"'), $shortCode),
|
||||||
|
], self::STATUS_NOT_FOUND);
|
||||||
|
} catch (Exception\ValidationException $e) {
|
||||||
|
$this->logger->warning('Provided data is invalid.' . PHP_EOL . $e);
|
||||||
|
return new JsonResponse([
|
||||||
|
'error' => RestUtils::getRestErrorCodeFromException($e),
|
||||||
|
'message' => $this->translator->translate('Provided data is invalid.'),
|
||||||
|
], self::STATUS_BAD_REQUEST);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,24 @@ use Zend\Stdlib\Glob;
|
||||||
|
|
||||||
class ConfigProvider
|
class ConfigProvider
|
||||||
{
|
{
|
||||||
|
const ROUTES_PREFIX = '/rest/v{version:1}';
|
||||||
|
|
||||||
public function __invoke()
|
public function __invoke()
|
||||||
{
|
{
|
||||||
return Factory::fromFiles(Glob::glob(__DIR__ . '/../config/{,*.}config.php', Glob::GLOB_BRACE));
|
return $this->applyRoutesPrefix(
|
||||||
|
Factory::fromFiles(Glob::glob(__DIR__ . '/../config/{,*.}config.php', Glob::GLOB_BRACE))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyRoutesPrefix(array $config): array
|
||||||
|
{
|
||||||
|
$routes =& $config['routes'] ?? [];
|
||||||
|
|
||||||
|
// Prepend the routes prefix to every path
|
||||||
|
foreach ($routes as $key => $route) {
|
||||||
|
$routes[$key]['path'] = self::ROUTES_PREFIX . $route['path'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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\ValidationException:
|
||||||
return self::INVALID_ARGUMENT_ERROR;
|
return self::INVALID_ARGUMENT_ERROR;
|
||||||
case $e instanceof Rest\AuthenticationException:
|
case $e instanceof Rest\AuthenticationException:
|
||||||
return self::INVALID_CREDENTIALS_ERROR;
|
return self::INVALID_CREDENTIALS_ERROR;
|
||||||
|
|
93
module/Rest/test/Action/EditShortCodeActionTest.php
Normal file
93
module/Rest/test/Action/EditShortCodeActionTest.php
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ShlinkioTest\Shlink\Rest\Action;
|
||||||
|
|
||||||
|
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\Argument;
|
||||||
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
|
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||||
|
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||||
|
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||||
|
use Shlinkio\Shlink\Rest\Action\EditShortCodeAction;
|
||||||
|
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||||
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
|
use Zend\Diactoros\ServerRequestFactory;
|
||||||
|
use Zend\I18n\Translator\Translator;
|
||||||
|
|
||||||
|
class EditShortCodeActionTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var EditShortCodeAction
|
||||||
|
*/
|
||||||
|
private $action;
|
||||||
|
/**
|
||||||
|
* @var ObjectProphecy
|
||||||
|
*/
|
||||||
|
private $shortUrlService;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->shortUrlService = $this->prophesize(ShortUrlServiceInterface::class);
|
||||||
|
$this->action = new EditShortCodeAction($this->shortUrlService->reveal(), Translator::factory([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function invalidDataReturnsError()
|
||||||
|
{
|
||||||
|
$request = ServerRequestFactory::fromGlobals()->withParsedBody([
|
||||||
|
'maxVisits' => 'invalid',
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** @var JsonResponse $resp */
|
||||||
|
$resp = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal());
|
||||||
|
$payload = $resp->getPayload();
|
||||||
|
|
||||||
|
$this->assertEquals(400, $resp->getStatusCode());
|
||||||
|
$this->assertEquals(RestUtils::INVALID_ARGUMENT_ERROR, $payload['error']);
|
||||||
|
$this->assertEquals('Provided data is invalid.', $payload['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function incorrectShortCodeReturnsError()
|
||||||
|
{
|
||||||
|
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', 'abc123')
|
||||||
|
->withParsedBody([
|
||||||
|
'maxVisits' => 5,
|
||||||
|
]);
|
||||||
|
$updateMeta = $this->shortUrlService->updateMetadataByShortCode(Argument::cetera())->willThrow(
|
||||||
|
InvalidShortCodeException::class
|
||||||
|
);
|
||||||
|
|
||||||
|
/** @var JsonResponse $resp */
|
||||||
|
$resp = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal());
|
||||||
|
$payload = $resp->getPayload();
|
||||||
|
|
||||||
|
$this->assertEquals(404, $resp->getStatusCode());
|
||||||
|
$this->assertEquals(RestUtils::INVALID_SHORTCODE_ERROR, $payload['error']);
|
||||||
|
$this->assertEquals('No URL found for short code "abc123"', $payload['message']);
|
||||||
|
$updateMeta->shouldHaveBeenCalled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function correctShortCodeReturnsSuccess()
|
||||||
|
{
|
||||||
|
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', 'abc123')
|
||||||
|
->withParsedBody([
|
||||||
|
'maxVisits' => 5,
|
||||||
|
]);
|
||||||
|
$updateMeta = $this->shortUrlService->updateMetadataByShortCode(Argument::cetera())->willReturn(new ShortUrl());
|
||||||
|
|
||||||
|
$resp = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal());
|
||||||
|
|
||||||
|
$this->assertEquals(204, $resp->getStatusCode());
|
||||||
|
$updateMeta->shouldHaveBeenCalled();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,9 @@
|
||||||
<phpunit bootstrap="./func_tests_bootstrap.php" colors="true">
|
<?xml version="1.0"?>
|
||||||
|
<phpunit
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.5/phpunit.xsd"
|
||||||
|
bootstrap="./func_tests_bootstrap.php"
|
||||||
|
colors="true">
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="Shlink functional database tests">
|
<testsuite name="Shlink functional database tests">
|
||||||
<directory>./module/*/test-func</directory>
|
<directory>./module/*/test-func</directory>
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
<phpunit bootstrap="./vendor/autoload.php" colors="true">
|
<?xml version="1.0"?>
|
||||||
|
<phpunit
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.5/phpunit.xsd"
|
||||||
|
bootstrap="./vendor/autoload.php"
|
||||||
|
colors="true">
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="Common">
|
<testsuite name="Common">
|
||||||
<directory>./module/Common/test</directory>
|
<directory>./module/Common/test</directory>
|
||||||
|
|
Loading…
Reference in a new issue