Applied API role specs to tags list without stats

This commit is contained in:
Alejandro Celaya 2021-01-04 12:44:29 +01:00
parent 68c601a5a8
commit 24f7fb9c4f
8 changed files with 28 additions and 16 deletions

View File

@ -191,7 +191,7 @@
"Short URLs"
],
"summary": "Create short URL",
"description": "Creates a new short URL.<br></br>**Param findIfExists:**: Starting with v1.16, this new param allows to force shlink to return existing short URLs when found based on provided params, instead of creating a new one. However, it might add complexity and have unexpected outputs.\n\nThese are the use cases:\n* Only the long URL is provided: It will return the newest match or create a new short URL if none is found.\n* Long url and custom slug are provided: It will return the short URL when both params match, return an error when the slug is in use for another long URL, or create a new short URL otherwise.\n* Any of the above but including other params (tags, validSince, validUntil, maxVisits): It will behave the same as the previous two cases, but it will try to exactly match existing results using all the params. If any of them does not match, it will try to create a new short URL.",
"description": "Creates a new short URL.<br></br>**Param findIfExists**: This new param allows to force shlink to return existing short URLs when found based on provided params, instead of creating a new one. However, it might add complexity and have unexpected outputs.\n\nThese are the use cases:\n* Only the long URL is provided: It will return the newest match or create a new short URL if none is found.\n* Long url and custom slug are provided: It will return the short URL when both params match, return an error when the slug is in use for another long URL, or create a new short URL otherwise.\n* Any of the above but including other params (tags, validSince, validUntil, maxVisits): It will behave the same as the previous two cases, but it will try to exactly match existing results using all the params. If any of them does not match, it will try to create a new short URL.",
"security": [
{
"ApiKey": []

View File

@ -56,7 +56,7 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito
{
$result = (int) $this->matchSingleScalarResult(Spec::andX(
new CountTagsWithName($tag),
new WithApiKeySpecsEnsuringJoin($apiKey, 'shortUrls'),
new WithApiKeySpecsEnsuringJoin($apiKey),
));
return $result > 0;

View File

@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Core\Tag;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM;
use Happyr\DoctrineSpecification\Spec;
use Shlinkio\Shlink\Core\Entity\Tag;
use Shlinkio\Shlink\Core\Exception\TagConflictException;
use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
@ -13,6 +14,7 @@ use Shlinkio\Shlink\Core\Repository\TagRepository;
use Shlinkio\Shlink\Core\Repository\TagRepositoryInterface;
use Shlinkio\Shlink\Core\Tag\Model\TagInfo;
use Shlinkio\Shlink\Core\Util\TagManagerTrait;
use Shlinkio\Shlink\Rest\ApiKey\Spec\WithApiKeySpecsEnsuringJoin;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
class TagService implements TagServiceInterface
@ -29,10 +31,15 @@ class TagService implements TagServiceInterface
/**
* @return Tag[]
*/
public function listTags(): array
public function listTags(?ApiKey $apiKey = null): array
{
/** @var TagRepository $repo */
$repo = $this->em->getRepository(Tag::class);
/** @var Tag[] $tags */
$tags = $this->em->getRepository(Tag::class)->findBy([], ['name' => 'ASC']);
$tags = $repo->match(Spec::andX(
Spec::orderBy('name'),
new WithApiKeySpecsEnsuringJoin($apiKey),
));
return $tags;
}

View File

@ -16,7 +16,7 @@ interface TagServiceInterface
/**
* @return Tag[]
*/
public function listTags(): array;
public function listTags(?ApiKey $apiKey = null): array;
/**
* @return TagInfo[]

View File

@ -38,12 +38,12 @@ class TagServiceTest extends TestCase
{
$expected = [new Tag('foo'), new Tag('bar')];
$find = $this->repo->findBy(Argument::cetera())->willReturn($expected);
$match = $this->repo->match(Argument::cetera())->willReturn($expected);
$result = $this->service->listTags();
self::assertEquals($expected, $result);
$find->shouldHaveBeenCalled();
$match->shouldHaveBeenCalled();
}
/** @test */

View File

@ -30,16 +30,16 @@ class ListTagsAction extends AbstractRestAction
{
$query = $request->getQueryParams();
$withStats = ($query['withStats'] ?? null) === 'true';
$apiKey = AuthenticationMiddleware::apiKeyFromRequest($request);
if (! $withStats) {
return new JsonResponse([
'tags' => [
'data' => $this->tagService->listTags(),
'data' => $this->tagService->listTags($apiKey),
],
]);
}
$apiKey = AuthenticationMiddleware::apiKeyFromRequest($request);
$tagsInfo = $this->tagService->tagsInfo($apiKey);
$data = map($tagsInfo, fn (TagInfo $info) => (string) $info->tag());

View File

@ -14,7 +14,7 @@ class WithApiKeySpecsEnsuringJoin extends BaseSpecification
private ?ApiKey $apiKey;
private string $fieldToJoin;
public function __construct(?ApiKey $apiKey, string $fieldToJoin)
public function __construct(?ApiKey $apiKey, string $fieldToJoin = 'shortUrls')
{
parent::__construct();
$this->apiKey = $apiKey;

View File

@ -7,8 +7,10 @@ namespace ShlinkioTest\Shlink\Rest\Action\Tag;
use Laminas\Diactoros\Response\JsonResponse;
use Laminas\Diactoros\ServerRequestFactory;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Core\Entity\Tag;
use Shlinkio\Shlink\Core\Tag\Model\TagInfo;
use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
@ -35,10 +37,10 @@ class ListTagsActionTest extends TestCase
public function returnsBaseDataWhenStatsAreNotRequested(array $query): void
{
$tags = [new Tag('foo'), new Tag('bar')];
$listTags = $this->tagService->listTags()->willReturn($tags);
$listTags = $this->tagService->listTags(Argument::type(ApiKey::class))->willReturn($tags);
/** @var JsonResponse $resp */
$resp = $this->action->handle(ServerRequestFactory::fromGlobals()->withQueryParams($query));
$resp = $this->action->handle($this->requestWithApiKey()->withQueryParams($query));
$payload = $resp->getPayload();
self::assertEquals([
@ -63,10 +65,8 @@ class ListTagsActionTest extends TestCase
new TagInfo(new Tag('foo'), 1, 1),
new TagInfo(new Tag('bar'), 3, 10),
];
$apiKey = new ApiKey();
$tagsInfo = $this->tagService->tagsInfo($apiKey)->willReturn($stats);
$req = ServerRequestFactory::fromGlobals()->withQueryParams(['withStats' => 'true'])
->withAttribute(ApiKey::class, $apiKey);
$tagsInfo = $this->tagService->tagsInfo(Argument::type(ApiKey::class))->willReturn($stats);
$req = $this->requestWithApiKey()->withQueryParams(['withStats' => 'true']);
/** @var JsonResponse $resp */
$resp = $this->action->handle($req);
@ -80,4 +80,9 @@ class ListTagsActionTest extends TestCase
], $payload);
$tagsInfo->shouldHaveBeenCalled();
}
private function requestWithApiKey(): ServerRequestInterface
{
return ServerRequestFactory::fromGlobals()->withAttribute(ApiKey::class, new ApiKey());
}
}