Enhanced list tags endpoint so that it can also return stats foir every tag

This commit is contained in:
Alejandro Celaya 2020-05-08 10:15:33 +02:00
parent 7e0a14493e
commit 626c92460b
27 changed files with 159 additions and 47 deletions

View File

@ -78,10 +78,10 @@ return [
Command\Api\DisableKeyCommand::class => [ApiKeyService::class], Command\Api\DisableKeyCommand::class => [ApiKeyService::class],
Command\Api\ListKeysCommand::class => [ApiKeyService::class], Command\Api\ListKeysCommand::class => [ApiKeyService::class],
Command\Tag\ListTagsCommand::class => [Service\Tag\TagService::class], Command\Tag\ListTagsCommand::class => [\Shlinkio\Shlink\Core\Tag\TagService::class],
Command\Tag\CreateTagCommand::class => [Service\Tag\TagService::class], Command\Tag\CreateTagCommand::class => [\Shlinkio\Shlink\Core\Tag\TagService::class],
Command\Tag\RenameTagCommand::class => [Service\Tag\TagService::class], Command\Tag\RenameTagCommand::class => [\Shlinkio\Shlink\Core\Tag\TagService::class],
Command\Tag\DeleteTagsCommand::class => [Service\Tag\TagService::class], Command\Tag\DeleteTagsCommand::class => [\Shlinkio\Shlink\Core\Tag\TagService::class],
Command\Db\CreateDatabaseCommand::class => [ Command\Db\CreateDatabaseCommand::class => [
LockFactory::class, LockFactory::class,

View File

@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Tag; namespace Shlinkio\Shlink\CLI\Command\Tag;
use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\CLI\Util\ExitCodes;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;

View File

@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Tag; namespace Shlinkio\Shlink\CLI\Command\Tag;
use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\CLI\Util\ExitCodes;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
@ -16,9 +16,9 @@ class DeleteTagsCommand extends Command
{ {
public const NAME = 'tag:delete'; public const NAME = 'tag:delete';
private TagServiceInterface $tagService; private \Shlinkio\Shlink\Core\Tag\TagServiceInterface $tagService;
public function __construct(TagServiceInterface $tagService) public function __construct(\Shlinkio\Shlink\Core\Tag\TagServiceInterface $tagService)
{ {
parent::__construct(); parent::__construct();
$this->tagService = $tagService; $this->tagService = $tagService;

View File

@ -7,7 +7,7 @@ namespace Shlinkio\Shlink\CLI\Command\Tag;
use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\CLI\Util\ExitCodes;
use Shlinkio\Shlink\CLI\Util\ShlinkTable; use Shlinkio\Shlink\CLI\Util\ShlinkTable;
use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Entity\Tag;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;

View File

@ -7,7 +7,7 @@ namespace Shlinkio\Shlink\CLI\Command\Tag;
use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\CLI\Util\ExitCodes;
use Shlinkio\Shlink\Core\Exception\TagConflictException; use Shlinkio\Shlink\Core\Exception\TagConflictException;
use Shlinkio\Shlink\Core\Exception\TagNotFoundException; use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
@ -20,7 +20,7 @@ class RenameTagCommand extends Command
private TagServiceInterface $tagService; private TagServiceInterface $tagService;
public function __construct(TagServiceInterface $tagService) public function __construct(\Shlinkio\Shlink\Core\Tag\TagServiceInterface $tagService)
{ {
parent::__construct(); parent::__construct();
$this->tagService = $tagService; $this->tagService = $tagService;

View File

@ -8,7 +8,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\CLI\Command\Tag\CreateTagCommand; use Shlinkio\Shlink\CLI\Command\Tag\CreateTagCommand;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Console\Tester\CommandTester;

View File

@ -7,7 +7,7 @@ namespace ShlinkioTest\Shlink\CLI\Command\Tag;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\CLI\Command\Tag\DeleteTagsCommand; use Shlinkio\Shlink\CLI\Command\Tag\DeleteTagsCommand;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Console\Tester\CommandTester;
@ -18,7 +18,7 @@ class DeleteTagsCommandTest extends TestCase
public function setUp(): void public function setUp(): void
{ {
$this->tagService = $this->prophesize(TagServiceInterface::class); $this->tagService = $this->prophesize(\Shlinkio\Shlink\Core\Tag\TagServiceInterface::class);
$command = new DeleteTagsCommand($this->tagService->reveal()); $command = new DeleteTagsCommand($this->tagService->reveal());
$app = new Application(); $app = new Application();

View File

@ -8,7 +8,7 @@ use PHPUnit\Framework\TestCase;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\CLI\Command\Tag\ListTagsCommand; use Shlinkio\Shlink\CLI\Command\Tag\ListTagsCommand;
use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Entity\Tag;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Console\Tester\CommandTester;
@ -19,7 +19,7 @@ class ListTagsCommandTest extends TestCase
public function setUp(): void public function setUp(): void
{ {
$this->tagService = $this->prophesize(TagServiceInterface::class); $this->tagService = $this->prophesize(\Shlinkio\Shlink\Core\Tag\TagServiceInterface::class);
$command = new ListTagsCommand($this->tagService->reveal()); $command = new ListTagsCommand($this->tagService->reveal());
$app = new Application(); $app = new Application();

View File

@ -9,7 +9,7 @@ use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\CLI\Command\Tag\RenameTagCommand; use Shlinkio\Shlink\CLI\Command\Tag\RenameTagCommand;
use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Entity\Tag;
use Shlinkio\Shlink\Core\Exception\TagNotFoundException; use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Console\Tester\CommandTester;

View File

@ -28,7 +28,7 @@ return [
Service\ShortUrlService::class => ConfigAbstractFactory::class, Service\ShortUrlService::class => ConfigAbstractFactory::class,
Visit\VisitLocator::class => ConfigAbstractFactory::class, Visit\VisitLocator::class => ConfigAbstractFactory::class,
Visit\VisitsStatsHelper::class => ConfigAbstractFactory::class, Visit\VisitsStatsHelper::class => ConfigAbstractFactory::class,
Service\Tag\TagService::class => ConfigAbstractFactory::class, Tag\TagService::class => ConfigAbstractFactory::class,
Service\ShortUrl\DeleteShortUrlService::class => ConfigAbstractFactory::class, Service\ShortUrl\DeleteShortUrlService::class => ConfigAbstractFactory::class,
Service\ShortUrl\ShortUrlResolver::class => ConfigAbstractFactory::class, Service\ShortUrl\ShortUrlResolver::class => ConfigAbstractFactory::class,
@ -58,7 +58,7 @@ return [
Service\ShortUrlService::class => ['em', Service\ShortUrl\ShortUrlResolver::class, Util\UrlValidator::class], Service\ShortUrlService::class => ['em', Service\ShortUrl\ShortUrlResolver::class, Util\UrlValidator::class],
Visit\VisitLocator::class => ['em'], Visit\VisitLocator::class => ['em'],
Visit\VisitsStatsHelper::class => ['em'], Visit\VisitsStatsHelper::class => ['em'],
Service\Tag\TagService::class => ['em'], Tag\TagService::class => ['em'],
Service\ShortUrl\DeleteShortUrlService::class => [ Service\ShortUrl\DeleteShortUrlService::class => [
'em', 'em',
Options\DeleteShortUrlsOptions::class, Options\DeleteShortUrlsOptions::class,

View File

@ -24,4 +24,10 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
$builder->createField('name', Types::STRING) $builder->createField('name', Types::STRING)
->unique() ->unique()
->build(); ->build();
$builder->createManyToMany('shortUrls', Entity\ShortUrl::class)
->setJoinTable(determineTableName('short_urls_in_tags', $emConfig))
->addInverseJoinColumn('short_url_id', 'id', true, false, 'CASCADE')
->addJoinColumn('tag_id', 'id', true, false, 'CASCADE')
->build();
}; };

View File

@ -4,16 +4,19 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Entity; namespace Shlinkio\Shlink\Core\Entity;
use Doctrine\Common\Collections;
use JsonSerializable; use JsonSerializable;
use Shlinkio\Shlink\Common\Entity\AbstractEntity; use Shlinkio\Shlink\Common\Entity\AbstractEntity;
class Tag extends AbstractEntity implements JsonSerializable class Tag extends AbstractEntity implements JsonSerializable
{ {
private string $name; private string $name;
private Collections\Collection $shortUrls;
public function __construct(string $name) public function __construct(string $name)
{ {
$this->name = $name; $this->name = $name;
$this->shortUrls = new Collections\ArrayCollection();
} }
public function rename(string $name): void public function rename(string $name): void

View File

@ -6,6 +6,8 @@ namespace Shlinkio\Shlink\Core\Repository;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Entity\Tag;
use Shlinkio\Shlink\Core\Tag\Model\TagInfo;
use function Functional\map;
class TagRepository extends EntityRepository implements TagRepositoryInterface class TagRepository extends EntityRepository implements TagRepositoryInterface
{ {
@ -21,4 +23,25 @@ class TagRepository extends EntityRepository implements TagRepositoryInterface
return $qb->getQuery()->execute(); return $qb->getQuery()->execute();
} }
/**
* @return TagInfo[]
*/
public function findTagsWithInfo(): array
{
$dql = <<<DQL
SELECT t AS tag, COUNT(DISTINCT s.id) AS shortUrlsCount, COUNT(DISTINCT v.id) AS visitsCount
FROM Shlinkio\Shlink\Core\Entity\Tag t
LEFT JOIN t.shortUrls s
LEFT JOIN s.visits v
GROUP BY tag
ORDER BY t.name ASC
DQL;
$query = $this->getEntityManager()->createQuery($dql);
return map(
$query->getResult(),
fn (array $row) => new TagInfo($row['tag'], (int) $row['shortUrlsCount'], (int) $row['visitsCount']),
);
}
} }

View File

@ -5,8 +5,14 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Repository; namespace Shlinkio\Shlink\Core\Repository;
use Doctrine\Persistence\ObjectRepository; use Doctrine\Persistence\ObjectRepository;
use Shlinkio\Shlink\Core\Tag\Model\TagInfo;
interface TagRepositoryInterface extends ObjectRepository interface TagRepositoryInterface extends ObjectRepository
{ {
public function deleteByName(array $names): int; public function deleteByName(array $names): int;
/**
* @return TagInfo[]
*/
public function findTagsWithInfo(): array;
} }

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Tag\Model;
use JsonSerializable;
use Shlinkio\Shlink\Core\Entity\Tag;
final class TagInfo implements JsonSerializable
{
private Tag $tag;
private int $shortUrlsCount;
private int $visitsCount;
public function __construct(Tag $tag, int $shortUrlsCount, int $visitsCount)
{
$this->tag = $tag;
$this->shortUrlsCount = $shortUrlsCount;
$this->visitsCount = $visitsCount;
}
public function tag(): Tag
{
return $this->tag;
}
public function shortUrlsCount(): int
{
return $this->shortUrlsCount;
}
public function visitsCount(): int
{
return $this->visitsCount;
}
public function jsonSerialize(): array
{
return [
'tag' => $this->tag,
'shortUrlsCount' => $this->shortUrlsCount,
'visitsCount' => $this->visitsCount,
];
}
}

View File

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Service\Tag; namespace Shlinkio\Shlink\Core\Tag;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM; use Doctrine\ORM;
@ -10,6 +10,8 @@ use Shlinkio\Shlink\Core\Entity\Tag;
use Shlinkio\Shlink\Core\Exception\TagConflictException; use Shlinkio\Shlink\Core\Exception\TagConflictException;
use Shlinkio\Shlink\Core\Exception\TagNotFoundException; use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
use Shlinkio\Shlink\Core\Repository\TagRepository; 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\Core\Util\TagManagerTrait;
class TagService implements TagServiceInterface class TagService implements TagServiceInterface
@ -25,7 +27,6 @@ class TagService implements TagServiceInterface
/** /**
* @return Tag[] * @return Tag[]
* @throws \UnexpectedValueException
*/ */
public function listTags(): array public function listTags(): array
{ {
@ -34,6 +35,16 @@ class TagService implements TagServiceInterface
return $tags; return $tags;
} }
/**
* @return TagInfo[]
*/
public function tagsInfo(): array
{
/** @var TagRepositoryInterface $repo */
$repo = $this->em->getRepository(Tag::class);
return $repo->findTagsWithInfo();
}
/** /**
* @param string[] $tagNames * @param string[] $tagNames
*/ */

View File

@ -2,12 +2,13 @@
declare(strict_types=1); declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Service\Tag; namespace Shlinkio\Shlink\Core\Tag;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Entity\Tag;
use Shlinkio\Shlink\Core\Exception\TagConflictException; use Shlinkio\Shlink\Core\Exception\TagConflictException;
use Shlinkio\Shlink\Core\Exception\TagNotFoundException; use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
use Shlinkio\Shlink\Core\Tag\Model\TagInfo;
interface TagServiceInterface interface TagServiceInterface
{ {
@ -16,6 +17,11 @@ interface TagServiceInterface
*/ */
public function listTags(): array; public function listTags(): array;
/**
* @return TagInfo[]
*/
public function tagsInfo(): array;
/** /**
* @param string[] $tagNames * @param string[] $tagNames
*/ */

View File

@ -13,7 +13,7 @@ use Shlinkio\Shlink\Core\Entity\Tag;
use Shlinkio\Shlink\Core\Exception\TagConflictException; use Shlinkio\Shlink\Core\Exception\TagConflictException;
use Shlinkio\Shlink\Core\Exception\TagNotFoundException; use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
use Shlinkio\Shlink\Core\Repository\TagRepository; use Shlinkio\Shlink\Core\Repository\TagRepository;
use Shlinkio\Shlink\Core\Service\Tag\TagService; use Shlinkio\Shlink\Core\Tag\TagService;
class TagServiceTest extends TestCase class TagServiceTest extends TestCase
{ {

View File

@ -65,10 +65,10 @@ return [
Action\Visit\GlobalVisitsAction::class => [Visit\VisitsStatsHelper::class], Action\Visit\GlobalVisitsAction::class => [Visit\VisitsStatsHelper::class],
Action\ShortUrl\ListShortUrlsAction::class => [Service\ShortUrlService::class, 'config.url_shortener.domain'], Action\ShortUrl\ListShortUrlsAction::class => [Service\ShortUrlService::class, 'config.url_shortener.domain'],
Action\ShortUrl\EditShortUrlTagsAction::class => [Service\ShortUrlService::class], Action\ShortUrl\EditShortUrlTagsAction::class => [Service\ShortUrlService::class],
Action\Tag\ListTagsAction::class => [Service\Tag\TagService::class], Action\Tag\ListTagsAction::class => [\Shlinkio\Shlink\Core\Tag\TagService::class],
Action\Tag\DeleteTagsAction::class => [Service\Tag\TagService::class], Action\Tag\DeleteTagsAction::class => [\Shlinkio\Shlink\Core\Tag\TagService::class],
Action\Tag\CreateTagsAction::class => [Service\Tag\TagService::class], Action\Tag\CreateTagsAction::class => [\Shlinkio\Shlink\Core\Tag\TagService::class],
Action\Tag\UpdateTagAction::class => [Service\Tag\TagService::class], Action\Tag\UpdateTagAction::class => [\Shlinkio\Shlink\Core\Tag\TagService::class],
Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware::class => ['config.url_shortener.domain.hostname'], Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware::class => ['config.url_shortener.domain.hostname'],
Middleware\ShortUrl\DefaultShortCodesLengthMiddleware::class => [ Middleware\ShortUrl\DefaultShortCodesLengthMiddleware::class => [

View File

@ -7,7 +7,7 @@ namespace Shlinkio\Shlink\Rest\Action\Tag;
use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
class CreateTagsAction extends AbstractRestAction class CreateTagsAction extends AbstractRestAction

View File

@ -7,7 +7,7 @@ namespace Shlinkio\Shlink\Rest\Action\Tag;
use Laminas\Diactoros\Response\EmptyResponse; use Laminas\Diactoros\Response\EmptyResponse;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
class DeleteTagsAction extends AbstractRestAction class DeleteTagsAction extends AbstractRestAction
@ -17,7 +17,7 @@ class DeleteTagsAction extends AbstractRestAction
private TagServiceInterface $tagService; private TagServiceInterface $tagService;
public function __construct(TagServiceInterface $tagService) public function __construct(\Shlinkio\Shlink\Core\Tag\TagServiceInterface $tagService)
{ {
$this->tagService = $tagService; $this->tagService = $tagService;
} }

View File

@ -7,9 +7,12 @@ namespace Shlinkio\Shlink\Rest\Action\Tag;
use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\Model\TagInfo;
use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
use function Functional\map;
class ListTagsAction extends AbstractRestAction class ListTagsAction extends AbstractRestAction
{ {
protected const ROUTE_PATH = '/tags'; protected const ROUTE_PATH = '/tags';
@ -22,18 +25,26 @@ class ListTagsAction extends AbstractRestAction
$this->tagService = $tagService; $this->tagService = $tagService;
} }
/**
* Process an incoming server request and return a response, optionally delegating
* to the next middleware component to create the response.
*
*
* @throws \InvalidArgumentException
*/
public function handle(ServerRequestInterface $request): ResponseInterface public function handle(ServerRequestInterface $request): ResponseInterface
{ {
$query = $request->getQueryParams();
$withStats = ($query['withStats'] ?? null) === 'true';
if (! $withStats) {
return new JsonResponse([
'tags' => [
'data' => $this->tagService->listTags(),
],
]);
}
$tagsInfo = $this->tagService->tagsInfo();
$data = map($tagsInfo, fn (TagInfo $info) => (string) $info->tag());
return new JsonResponse([ return new JsonResponse([
'tags' => [ 'tags' => [
'data' => $this->tagService->listTags(), 'data' => $data,
'stats' => $tagsInfo,
], ],
]); ]);
} }

View File

@ -8,7 +8,7 @@ use Laminas\Diactoros\Response\EmptyResponse;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Exception\ValidationException;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
class UpdateTagAction extends AbstractRestAction class UpdateTagAction extends AbstractRestAction
@ -16,7 +16,7 @@ class UpdateTagAction extends AbstractRestAction
protected const ROUTE_PATH = '/tags'; protected const ROUTE_PATH = '/tags';
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_PUT]; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_PUT];
private TagServiceInterface $tagService; private \Shlinkio\Shlink\Core\Tag\TagServiceInterface $tagService;
public function __construct(TagServiceInterface $tagService) public function __construct(TagServiceInterface $tagService)
{ {

View File

@ -8,7 +8,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\ServerRequest;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Shlinkio\Shlink\Rest\Action\Tag\CreateTagsAction; use Shlinkio\Shlink\Rest\Action\Tag\CreateTagsAction;
class CreateTagsActionTest extends TestCase class CreateTagsActionTest extends TestCase

View File

@ -7,7 +7,7 @@ namespace ShlinkioTest\Shlink\Rest\Action\Tag;
use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\ServerRequest;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Shlinkio\Shlink\Rest\Action\Tag\DeleteTagsAction; use Shlinkio\Shlink\Rest\Action\Tag\DeleteTagsAction;
class DeleteTagsActionTest extends TestCase class DeleteTagsActionTest extends TestCase

View File

@ -8,7 +8,7 @@ use Laminas\Diactoros\ServerRequest;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Entity\Tag;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Shlinkio\Shlink\Rest\Action\Tag\ListTagsAction; use Shlinkio\Shlink\Rest\Action\Tag\ListTagsAction;
use function Shlinkio\Shlink\Common\json_decode; use function Shlinkio\Shlink\Common\json_decode;
@ -20,7 +20,7 @@ class ListTagsActionTest extends TestCase
public function setUp(): void public function setUp(): void
{ {
$this->tagService = $this->prophesize(TagServiceInterface::class); $this->tagService = $this->prophesize(\Shlinkio\Shlink\Core\Tag\TagServiceInterface::class);
$this->action = new ListTagsAction($this->tagService->reveal()); $this->action = new ListTagsAction($this->tagService->reveal());
} }

View File

@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Entity\Tag;
use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Exception\ValidationException;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
use Shlinkio\Shlink\Rest\Action\Tag\UpdateTagAction; use Shlinkio\Shlink\Rest\Action\Tag\UpdateTagAction;
class UpdateTagActionTest extends TestCase class UpdateTagActionTest extends TestCase
@ -19,7 +19,7 @@ class UpdateTagActionTest extends TestCase
public function setUp(): void public function setUp(): void
{ {
$this->tagService = $this->prophesize(TagServiceInterface::class); $this->tagService = $this->prophesize(\Shlinkio\Shlink\Core\Tag\TagServiceInterface::class);
$this->action = new UpdateTagAction($this->tagService->reveal()); $this->action = new UpdateTagAction($this->tagService->reveal());
} }