mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-14 04:00:57 +03:00
Merge pull request #849 from acelaya-forks/feature/domains-endpoint
Feature/domains endpoint
This commit is contained in:
commit
b15e90408f
21 changed files with 542 additions and 2 deletions
|
@ -18,6 +18,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
|
|||
* On the `POST /short-url` and `PATCH /short-url/{shortCode}` endpoints, you can now pass `validateUrl: true/false` in order to enforce enabling or disabling validation, ignoring the global config. If the value is not provided, the global config is still normally applied.
|
||||
* On the `short-url:generate` CLI command, you can pass `--validate-url` or `--no-validate-url` flags, in order to enforce enabling or disabling validation. If none of them is provided, the global config is still normally applied.
|
||||
|
||||
* [#838](https://github.com/shlinkio/shlink/issues/838) Added new endpoint and CLI command to list existing domains.
|
||||
|
||||
It returns both default domain and specific domains that were used for some short URLs.
|
||||
|
||||
* REST endpoint: `GET /rest/v2/domains`
|
||||
* CLI Command: `domain:list`
|
||||
|
||||
#### Changed
|
||||
|
||||
* [#836](https://github.com/shlinkio/shlink/issues/836) Added support for the `<field>-<dir>` notation while determining how to order the short URLs list, as in `?orderBy=shortCode-DESC`. This effectively deprecates the array notation (`?orderBy[shortCode]=DESC`), that will be removed in Shlink 3.0.0
|
||||
|
|
86
docs/swagger/paths/v2_domains.json
Normal file
86
docs/swagger/paths/v2_domains.json
Normal file
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"get": {
|
||||
"operationId": "listDomains",
|
||||
"tags": [
|
||||
"Domains"
|
||||
],
|
||||
"summary": "List existing domains",
|
||||
"description": "Returns the list of all domains ever used, with a flag that tells if they are the default domain",
|
||||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "../parameters/version.json"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The list of tags",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["domains"],
|
||||
"properties": {
|
||||
"domains": {
|
||||
"type": "object",
|
||||
"required": ["data"],
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["domain", "isDefault"],
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string"
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"domains": {
|
||||
"data": [
|
||||
{
|
||||
"domain": "example.com",
|
||||
"isDefault": true
|
||||
},
|
||||
{
|
||||
"domain": "aaa.com",
|
||||
"isDefault": false
|
||||
},
|
||||
{
|
||||
"domain": "bbb.com",
|
||||
"isDefault": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/problem+json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -88,6 +88,10 @@
|
|||
"$ref": "paths/v2_tags_{tag}_visits.json"
|
||||
},
|
||||
|
||||
"/rest/v{version}/domains": {
|
||||
"$ref": "paths/v2_domains.json"
|
||||
},
|
||||
|
||||
"/rest/v{version}/mercure-info": {
|
||||
"$ref": "paths/v2_mercure-info.json"
|
||||
},
|
||||
|
|
|
@ -25,6 +25,8 @@ return [
|
|||
Command\Tag\RenameTagCommand::NAME => Command\Tag\RenameTagCommand::class,
|
||||
Command\Tag\DeleteTagsCommand::NAME => Command\Tag\DeleteTagsCommand::class,
|
||||
|
||||
Command\Domain\ListDomainsCommand::NAME => Command\Domain\ListDomainsCommand::class,
|
||||
|
||||
Command\Db\CreateDatabaseCommand::NAME => Command\Db\CreateDatabaseCommand::class,
|
||||
Command\Db\MigrateDatabaseCommand::NAME => Command\Db\MigrateDatabaseCommand::class,
|
||||
],
|
||||
|
|
|
@ -10,6 +10,7 @@ use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
|||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||
use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdater;
|
||||
use Shlinkio\Shlink\Common\Doctrine\NoDbNameConnectionFactory;
|
||||
use Shlinkio\Shlink\Core\Domain\DomainService;
|
||||
use Shlinkio\Shlink\Core\Service;
|
||||
use Shlinkio\Shlink\Core\Tag\TagService;
|
||||
use Shlinkio\Shlink\Core\Visit;
|
||||
|
@ -52,6 +53,8 @@ return [
|
|||
|
||||
Command\Db\CreateDatabaseCommand::class => ConfigAbstractFactory::class,
|
||||
Command\Db\MigrateDatabaseCommand::class => ConfigAbstractFactory::class,
|
||||
|
||||
Command\Domain\ListDomainsCommand::class => ConfigAbstractFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
|
@ -84,6 +87,8 @@ return [
|
|||
Command\Tag\RenameTagCommand::class => [TagService::class],
|
||||
Command\Tag\DeleteTagsCommand::class => [TagService::class],
|
||||
|
||||
Command\Domain\ListDomainsCommand::class => [DomainService::class, 'config.url_shortener.domain.hostname'],
|
||||
|
||||
Command\Db\CreateDatabaseCommand::class => [
|
||||
LockFactory::class,
|
||||
SymfonyCli\Helper\ProcessHelper::class,
|
||||
|
|
49
module/CLI/src/Command/Domain/ListDomainsCommand.php
Normal file
49
module/CLI/src/Command/Domain/ListDomainsCommand.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Command\Domain;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
||||
use Shlinkio\Shlink\CLI\Util\ShlinkTable;
|
||||
use Shlinkio\Shlink\Core\Domain\DomainServiceInterface;
|
||||
use Shlinkio\Shlink\Core\Entity\Domain;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function Functional\map;
|
||||
|
||||
class ListDomainsCommand extends Command
|
||||
{
|
||||
public const NAME = 'domain:list';
|
||||
|
||||
private DomainServiceInterface $domainService;
|
||||
private string $defaultDomain;
|
||||
|
||||
public function __construct(DomainServiceInterface $domainService, string $defaultDomain)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->domainService = $domainService;
|
||||
$this->defaultDomain = $defaultDomain;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName(self::NAME)
|
||||
->setDescription('List all domains that have been ever used for some short URL');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): ?int
|
||||
{
|
||||
$regularDomains = $this->domainService->listDomainsWithout($this->defaultDomain);
|
||||
|
||||
ShlinkTable::fromOutput($output)->render(['Domain', 'Is default'], [
|
||||
[$this->defaultDomain, 'Yes'],
|
||||
...map($regularDomains, fn (Domain $domain) => [$domain->getAuthority(), 'No']),
|
||||
]);
|
||||
|
||||
return ExitCodes::EXIT_SUCCESS;
|
||||
}
|
||||
}
|
56
module/CLI/test/Command/Domain/ListDomainsCommandTest.php
Normal file
56
module/CLI/test/Command/Domain/ListDomainsCommandTest.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Domain;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Domain\ListDomainsCommand;
|
||||
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
||||
use Shlinkio\Shlink\Core\Domain\DomainServiceInterface;
|
||||
use Shlinkio\Shlink\Core\Entity\Domain;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
class ListDomainsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private ObjectProphecy $domainService;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->domainService = $this->prophesize(DomainServiceInterface::class);
|
||||
|
||||
$command = new ListDomainsCommand($this->domainService->reveal(), 'foo.com');
|
||||
$app = new Application();
|
||||
$app->add($command);
|
||||
|
||||
$this->commandTester = new CommandTester($command);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function allDomainsAreProperlyPrinted(): void
|
||||
{
|
||||
$expectedOutput = <<<OUTPUT
|
||||
+---------+------------+
|
||||
| Domain | Is default |
|
||||
+---------+------------+
|
||||
| foo.com | Yes |
|
||||
| bar.com | No |
|
||||
| baz.com | No |
|
||||
+---------+------------+
|
||||
|
||||
OUTPUT;
|
||||
$listDomains = $this->domainService->listDomainsWithout('foo.com')->willReturn([
|
||||
new Domain('bar.com'),
|
||||
new Domain('baz.com'),
|
||||
]);
|
||||
|
||||
$this->commandTester->execute([]);
|
||||
|
||||
self::assertEquals($expectedOutput, $this->commandTester->getDisplay());
|
||||
self::assertEquals(ExitCodes::EXIT_SUCCESS, $this->commandTester->getStatusCode());
|
||||
$listDomains->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ return [
|
|||
Tag\TagService::class => ConfigAbstractFactory::class,
|
||||
Service\ShortUrl\DeleteShortUrlService::class => ConfigAbstractFactory::class,
|
||||
Service\ShortUrl\ShortUrlResolver::class => ConfigAbstractFactory::class,
|
||||
Domain\DomainService::class => ConfigAbstractFactory::class,
|
||||
|
||||
Util\UrlValidator::class => ConfigAbstractFactory::class,
|
||||
|
||||
|
@ -69,6 +70,7 @@ return [
|
|||
Service\ShortUrl\ShortUrlResolver::class,
|
||||
],
|
||||
Service\ShortUrl\ShortUrlResolver::class => ['em'],
|
||||
Domain\DomainService::class => ['em'],
|
||||
|
||||
Util\UrlValidator::class => ['httpClient', Options\UrlShortenerOptions::class],
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@ use Doctrine\ORM\Mapping\ClassMetadata;
|
|||
return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable(determineTableName('domains', $emConfig));
|
||||
$builder->setTable(determineTableName('domains', $emConfig))
|
||||
->setCustomRepositoryClass(Domain\Repository\DomainRepository::class);
|
||||
|
||||
$builder->createField('id', Types::BIGINT)
|
||||
->columnName('id')
|
||||
|
|
29
module/Core/src/Domain/DomainService.php
Normal file
29
module/Core/src/Domain/DomainService.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Domain;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Shlinkio\Shlink\Core\Domain\Repository\DomainRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Entity\Domain;
|
||||
|
||||
class DomainService implements DomainServiceInterface
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Domain[]
|
||||
*/
|
||||
public function listDomainsWithout(?string $excludeDomain = null): array
|
||||
{
|
||||
/** @var DomainRepositoryInterface $repo */
|
||||
$repo = $this->em->getRepository(Domain::class);
|
||||
return $repo->findDomainsWithout($excludeDomain);
|
||||
}
|
||||
}
|
15
module/Core/src/Domain/DomainServiceInterface.php
Normal file
15
module/Core/src/Domain/DomainServiceInterface.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Domain;
|
||||
|
||||
use Shlinkio\Shlink\Core\Entity\Domain;
|
||||
|
||||
interface DomainServiceInterface
|
||||
{
|
||||
/**
|
||||
* @return Domain[]
|
||||
*/
|
||||
public function listDomainsWithout(?string $excludeDomain = null): array;
|
||||
}
|
26
module/Core/src/Domain/Repository/DomainRepository.php
Normal file
26
module/Core/src/Domain/Repository/DomainRepository.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Domain\Repository;
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Shlinkio\Shlink\Core\Entity\Domain;
|
||||
|
||||
class DomainRepository extends EntityRepository implements DomainRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @return Domain[]
|
||||
*/
|
||||
public function findDomainsWithout(?string $excludedAuthority = null): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('d')->orderBy('d.authority', 'ASC');
|
||||
|
||||
if ($excludedAuthority !== null) {
|
||||
$qb->where($qb->expr()->neq('d.authority', ':excludedAuthority'))
|
||||
->setParameter('excludedAuthority', $excludedAuthority);
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Domain\Repository;
|
||||
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
use Shlinkio\Shlink\Core\Entity\Domain;
|
||||
|
||||
interface DomainRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
/**
|
||||
* @return Domain[]
|
||||
*/
|
||||
public function findDomainsWithout(?string $excludedAuthority = null): array;
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Core\Domain\Repository;
|
||||
|
||||
use Shlinkio\Shlink\Core\Domain\Repository\DomainRepository;
|
||||
use Shlinkio\Shlink\Core\Entity\Domain;
|
||||
use Shlinkio\Shlink\TestUtils\DbTest\DatabaseTestCase;
|
||||
|
||||
class DomainRepositoryTest extends DatabaseTestCase
|
||||
{
|
||||
protected const ENTITIES_TO_EMPTY = [Domain::class];
|
||||
|
||||
private DomainRepository $repo;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repo = $this->getEntityManager()->getRepository(Domain::class);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function findDomainsReturnsExpectedResult(): void
|
||||
{
|
||||
$fooDomain = new Domain('foo.com');
|
||||
$barDomain = new Domain('bar.com');
|
||||
$bazDomain = new Domain('baz.com');
|
||||
|
||||
$this->getEntityManager()->persist($fooDomain);
|
||||
$this->getEntityManager()->persist($barDomain);
|
||||
$this->getEntityManager()->persist($bazDomain);
|
||||
$this->getEntityManager()->flush();
|
||||
|
||||
self::assertEquals([$barDomain, $bazDomain, $fooDomain], $this->repo->findDomainsWithout());
|
||||
self::assertEquals([$barDomain, $bazDomain], $this->repo->findDomainsWithout('foo.com'));
|
||||
self::assertEquals([$bazDomain, $fooDomain], $this->repo->findDomainsWithout('bar.com'));
|
||||
self::assertEquals([$barDomain, $fooDomain], $this->repo->findDomainsWithout('baz.com'));
|
||||
}
|
||||
}
|
49
module/Core/test/Domain/DomainServiceTest.php
Normal file
49
module/Core/test/Domain/DomainServiceTest.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Core\Domain;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Domain\DomainService;
|
||||
use Shlinkio\Shlink\Core\Domain\Repository\DomainRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Entity\Domain;
|
||||
|
||||
class DomainServiceTest extends TestCase
|
||||
{
|
||||
private DomainService $domainService;
|
||||
private ObjectProphecy $em;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->em = $this->prophesize(EntityManagerInterface::class);
|
||||
$this->domainService = new DomainService($this->em->reveal());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideExcludedDomains
|
||||
*/
|
||||
public function listDomainsWithoutDelegatesIntoRepository(?string $excludedDomain, array $expectedResult): void
|
||||
{
|
||||
$repo = $this->prophesize(DomainRepositoryInterface::class);
|
||||
$getRepo = $this->em->getRepository(Domain::class)->willReturn($repo->reveal());
|
||||
$findDomains = $repo->findDomainsWithout($excludedDomain)->willReturn($expectedResult);
|
||||
|
||||
$result = $this->domainService->listDomainsWithout($excludedDomain);
|
||||
|
||||
self::assertEquals($expectedResult, $result);
|
||||
$getRepo->shouldHaveBeenCalledOnce();
|
||||
$findDomains->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
public function provideExcludedDomains(): iterable
|
||||
{
|
||||
yield 'no excluded domain' => [null, []];
|
||||
yield 'foo.com excluded domain' => ['foo.com', []];
|
||||
yield 'bar.com excluded domain' => ['bar.com', [new Domain('bar.com')]];
|
||||
yield 'baz.com excluded domain' => ['baz.com', [new Domain('foo.com'), new Domain('bar.com')]];
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
|||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||
use Mezzio\Router\Middleware\ImplicitOptionsMiddleware;
|
||||
use Shlinkio\Shlink\Common\Mercure\LcobucciJwtProvider;
|
||||
use Shlinkio\Shlink\Core\Domain\DomainService;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Shlinkio\Shlink\Core\Service;
|
||||
use Shlinkio\Shlink\Core\Tag\TagService;
|
||||
|
@ -36,6 +37,7 @@ return [
|
|||
Action\Tag\DeleteTagsAction::class => ConfigAbstractFactory::class,
|
||||
Action\Tag\CreateTagsAction::class => ConfigAbstractFactory::class,
|
||||
Action\Tag\UpdateTagAction::class => ConfigAbstractFactory::class,
|
||||
Action\Domain\ListDomainsAction::class => ConfigAbstractFactory::class,
|
||||
|
||||
ImplicitOptionsMiddleware::class => Middleware\EmptyResponseImplicitOptionsMiddlewareFactory::class,
|
||||
Middleware\BodyParserMiddleware::class => InvokableFactory::class,
|
||||
|
@ -72,6 +74,7 @@ return [
|
|||
Action\Tag\DeleteTagsAction::class => [TagService::class],
|
||||
Action\Tag\CreateTagsAction::class => [TagService::class],
|
||||
Action\Tag\UpdateTagAction::class => [TagService::class],
|
||||
Action\Domain\ListDomainsAction::class => [DomainService::class, 'config.url_shortener.domain.hostname'],
|
||||
|
||||
Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware::class => ['config.url_shortener.domain.hostname'],
|
||||
Middleware\ShortUrl\DefaultShortCodesLengthMiddleware::class => [
|
||||
|
|
|
@ -12,7 +12,7 @@ return [
|
|||
'routes' => [
|
||||
Action\HealthAction::getRouteDef(),
|
||||
|
||||
// Short codes
|
||||
// Short URLs
|
||||
Action\ShortUrl\CreateShortUrlAction::getRouteDef([
|
||||
$contentNegotiationMiddleware,
|
||||
$dropDomainMiddleware,
|
||||
|
@ -36,6 +36,9 @@ return [
|
|||
Action\Tag\CreateTagsAction::getRouteDef(),
|
||||
Action\Tag\UpdateTagAction::getRouteDef(),
|
||||
|
||||
// Domains
|
||||
Action\Domain\ListDomainsAction::getRouteDef(),
|
||||
|
||||
Action\MercureInfoAction::getRouteDef(),
|
||||
],
|
||||
|
||||
|
|
51
module/Rest/src/Action/Domain/ListDomainsAction.php
Normal file
51
module/Rest/src/Action/Domain/ListDomainsAction.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action\Domain;
|
||||
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Shlinkio\Shlink\Core\Domain\DomainServiceInterface;
|
||||
use Shlinkio\Shlink\Core\Entity\Domain;
|
||||
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||
|
||||
use function Functional\map;
|
||||
|
||||
class ListDomainsAction extends AbstractRestAction
|
||||
{
|
||||
protected const ROUTE_PATH = '/domains';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET];
|
||||
|
||||
private DomainServiceInterface $domainService;
|
||||
private string $defaultDomain;
|
||||
|
||||
public function __construct(DomainServiceInterface $domainService, string $defaultDomain)
|
||||
{
|
||||
$this->domainService = $domainService;
|
||||
$this->defaultDomain = $defaultDomain;
|
||||
}
|
||||
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$regularDomains = $this->domainService->listDomainsWithout($this->defaultDomain);
|
||||
|
||||
return new JsonResponse([
|
||||
'domains' => [
|
||||
'data' => [
|
||||
$this->mapDomain($this->defaultDomain, true),
|
||||
...map($regularDomains, fn (Domain $domain) => $this->mapDomain($domain->getAuthority())),
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function mapDomain(string $domain, bool $isDefault = false): array
|
||||
{
|
||||
return [
|
||||
'domain' => $domain,
|
||||
'isDefault' => $isDefault,
|
||||
];
|
||||
}
|
||||
}
|
37
module/Rest/test-api/Action/ListDomainsTest.php
Normal file
37
module/Rest/test-api/Action/ListDomainsTest.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioApiTest\Shlink\Rest\Action;
|
||||
|
||||
use Shlinkio\Shlink\TestUtils\ApiTest\ApiTestCase;
|
||||
|
||||
class ListDomainsTest extends ApiTestCase
|
||||
{
|
||||
/** @test */
|
||||
public function domainsAreProperlyListed(): void
|
||||
{
|
||||
$resp = $this->callApiWithKey(self::METHOD_GET, '/domains');
|
||||
$respPayload = $this->getJsonResponsePayload($resp);
|
||||
|
||||
self::assertEquals(self::STATUS_OK, $resp->getStatusCode());
|
||||
self::assertEquals([
|
||||
'domains' => [
|
||||
'data' => [
|
||||
[
|
||||
'domain' => 'doma.in',
|
||||
'isDefault' => true,
|
||||
],
|
||||
[
|
||||
'domain' => 'example.com',
|
||||
'isDefault' => false,
|
||||
],
|
||||
[
|
||||
'domain' => 'some-domain.com',
|
||||
'isDefault' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
], $respPayload);
|
||||
}
|
||||
}
|
58
module/Rest/test/Action/Domain/ListDomainsActionTest.php
Normal file
58
module/Rest/test/Action/Domain/ListDomainsActionTest.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest\Action\Domain;
|
||||
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Laminas\Diactoros\ServerRequestFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Domain\DomainServiceInterface;
|
||||
use Shlinkio\Shlink\Core\Entity\Domain;
|
||||
use Shlinkio\Shlink\Rest\Action\Domain\ListDomainsAction;
|
||||
|
||||
class ListDomainsActionTest extends TestCase
|
||||
{
|
||||
private ListDomainsAction $action;
|
||||
private ObjectProphecy $domainService;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->domainService = $this->prophesize(DomainServiceInterface::class);
|
||||
$this->action = new ListDomainsAction($this->domainService->reveal(), 'foo.com');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function domainsAreProperlyListed(): void
|
||||
{
|
||||
$listDomains = $this->domainService->listDomainsWithout('foo.com')->willReturn([
|
||||
new Domain('bar.com'),
|
||||
new Domain('baz.com'),
|
||||
]);
|
||||
|
||||
/** @var JsonResponse $resp */
|
||||
$resp = $this->action->handle(ServerRequestFactory::fromGlobals());
|
||||
$payload = $resp->getPayload();
|
||||
|
||||
self::assertEquals([
|
||||
'domains' => [
|
||||
'data' => [
|
||||
[
|
||||
'domain' => 'foo.com',
|
||||
'isDefault' => true,
|
||||
],
|
||||
[
|
||||
'domain' => 'bar.com',
|
||||
'isDefault' => false,
|
||||
],
|
||||
[
|
||||
'domain' => 'baz.com',
|
||||
'isDefault' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
], $payload);
|
||||
$listDomains->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
}
|
|
@ -23,6 +23,8 @@
|
|||
|
||||
<exclude>
|
||||
<directory suffix=".php">./module/Core/src/Repository</directory>
|
||||
<directory suffix=".php">./module/Core/src/**/Repository</directory>
|
||||
<directory suffix=".php">./module/Core/src/**/**/Repository</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
|
Loading…
Add table
Reference in a new issue