Applied API role specs to single short URL resolution

This commit is contained in:
Alejandro Celaya 2021-01-03 13:33:07 +01:00
parent 940383646b
commit dc08286a72
7 changed files with 34 additions and 17 deletions

View File

@ -90,10 +90,10 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
} }
private function createListQueryBuilder( private function createListQueryBuilder(
?string $searchTerm = null, ?string $searchTerm,
array $tags = [], array $tags,
?DateRange $dateRange = null, ?DateRange $dateRange,
?Specification $spec = null ?Specification $spec
): QueryBuilder { ): QueryBuilder {
$qb = $this->getEntityManager()->createQueryBuilder(); $qb = $this->getEntityManager()->createQueryBuilder();
$qb->from(ShortUrl::class, 's') $qb->from(ShortUrl::class, 's')
@ -171,9 +171,9 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
return $query->getOneOrNullResult(); return $query->getOneOrNullResult();
} }
public function findOne(string $shortCode, ?string $domain = null): ?ShortUrl public function findOne(string $shortCode, ?string $domain = null, ?Specification $spec = null): ?ShortUrl
{ {
$qb = $this->createFindOneQueryBuilder($shortCode, $domain); $qb = $this->createFindOneQueryBuilder($shortCode, $domain, $spec);
$qb->select('s'); $qb->select('s');
return $qb->getQuery()->getOneOrNullResult(); return $qb->getQuery()->getOneOrNullResult();
@ -181,13 +181,13 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
public function shortCodeIsInUse(string $slug, ?string $domain = null): bool public function shortCodeIsInUse(string $slug, ?string $domain = null): bool
{ {
$qb = $this->createFindOneQueryBuilder($slug, $domain); $qb = $this->createFindOneQueryBuilder($slug, $domain, null);
$qb->select('COUNT(DISTINCT s.id)'); $qb->select('COUNT(DISTINCT s.id)');
return ((int) $qb->getQuery()->getSingleScalarResult()) > 0; return ((int) $qb->getQuery()->getSingleScalarResult()) > 0;
} }
private function createFindOneQueryBuilder(string $slug, ?string $domain = null): QueryBuilder private function createFindOneQueryBuilder(string $slug, ?string $domain, ?Specification $spec): QueryBuilder
{ {
$qb = $this->getEntityManager()->createQueryBuilder(); $qb = $this->getEntityManager()->createQueryBuilder();
$qb->from(ShortUrl::class, 's') $qb->from(ShortUrl::class, 's')
@ -198,6 +198,10 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
$this->whereDomainIs($qb, $domain); $this->whereDomainIs($qb, $domain);
if ($spec !== null) {
$this->applySpecification($qb, $spec, 's');
}
return $qb; return $qb;
} }

View File

@ -34,7 +34,7 @@ interface ShortUrlRepositoryInterface extends ObjectRepository, EntitySpecificat
public function findOneWithDomainFallback(string $shortCode, ?string $domain = null): ?ShortUrl; public function findOneWithDomainFallback(string $shortCode, ?string $domain = null): ?ShortUrl;
public function findOne(string $shortCode, ?string $domain = null): ?ShortUrl; public function findOne(string $shortCode, ?string $domain = null, ?Specification $spec = null): ?ShortUrl;
public function shortCodeIsInUse(string $slug, ?string $domain): bool; public function shortCodeIsInUse(string $slug, ?string $domain): bool;

View File

@ -9,6 +9,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
class ShortUrlResolver implements ShortUrlResolverInterface class ShortUrlResolver implements ShortUrlResolverInterface
{ {
@ -22,11 +23,15 @@ class ShortUrlResolver implements ShortUrlResolverInterface
/** /**
* @throws ShortUrlNotFoundException * @throws ShortUrlNotFoundException
*/ */
public function resolveShortUrl(ShortUrlIdentifier $identifier): ShortUrl public function resolveShortUrl(ShortUrlIdentifier $identifier, ?ApiKey $apiKey = null): ShortUrl
{ {
/** @var ShortUrlRepository $shortUrlRepo */ /** @var ShortUrlRepository $shortUrlRepo */
$shortUrlRepo = $this->em->getRepository(ShortUrl::class); $shortUrlRepo = $this->em->getRepository(ShortUrl::class);
$shortUrl = $shortUrlRepo->findOne($identifier->shortCode(), $identifier->domain()); $shortUrl = $shortUrlRepo->findOne(
$identifier->shortCode(),
$identifier->domain(),
$apiKey !== null ? $apiKey->spec() : null,
);
if ($shortUrl === null) { if ($shortUrl === null) {
throw ShortUrlNotFoundException::fromNotFound($identifier); throw ShortUrlNotFoundException::fromNotFound($identifier);
} }

View File

@ -7,13 +7,14 @@ namespace Shlinkio\Shlink\Core\Service\ShortUrl;
use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
interface ShortUrlResolverInterface interface ShortUrlResolverInterface
{ {
/** /**
* @throws ShortUrlNotFoundException * @throws ShortUrlNotFoundException
*/ */
public function resolveShortUrl(ShortUrlIdentifier $identifier): ShortUrl; public function resolveShortUrl(ShortUrlIdentifier $identifier, ?ApiKey $apiKey = null): ShortUrl;
/** /**
* @throws ShortUrlNotFoundException * @throws ShortUrlNotFoundException

View File

@ -42,7 +42,7 @@ class ShortUrlResolverTest extends TestCase
$shortCode = $shortUrl->getShortCode(); $shortCode = $shortUrl->getShortCode();
$repo = $this->prophesize(ShortUrlRepositoryInterface::class); $repo = $this->prophesize(ShortUrlRepositoryInterface::class);
$findOne = $repo->findOne($shortCode, null)->willReturn($shortUrl); $findOne = $repo->findOne($shortCode, null, null)->willReturn($shortUrl);
$getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
$result = $this->urlResolver->resolveShortUrl(new ShortUrlIdentifier($shortCode)); $result = $this->urlResolver->resolveShortUrl(new ShortUrlIdentifier($shortCode));
@ -58,7 +58,7 @@ class ShortUrlResolverTest extends TestCase
$shortCode = 'abc123'; $shortCode = 'abc123';
$repo = $this->prophesize(ShortUrlRepositoryInterface::class); $repo = $this->prophesize(ShortUrlRepositoryInterface::class);
$findOne = $repo->findOne($shortCode, null)->willReturn(null); $findOne = $repo->findOne($shortCode, null, null)->willReturn(null);
$getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
$this->expectException(ShortUrlNotFoundException::class); $this->expectException(ShortUrlNotFoundException::class);

View File

@ -11,6 +11,7 @@ use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface; use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer; use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware;
class ResolveShortUrlAction extends AbstractRestAction class ResolveShortUrlAction extends AbstractRestAction
{ {
@ -29,7 +30,10 @@ class ResolveShortUrlAction extends AbstractRestAction
public function handle(Request $request): Response public function handle(Request $request): Response
{ {
$transformer = new ShortUrlDataTransformer($this->domainConfig); $transformer = new ShortUrlDataTransformer($this->domainConfig);
$url = $this->urlResolver->resolveShortUrl(ShortUrlIdentifier::fromApiRequest($request)); $url = $this->urlResolver->resolveShortUrl(
ShortUrlIdentifier::fromApiRequest($request),
AuthenticationMiddleware::apiKeyFromRequest($request),
);
return new JsonResponse($transformer->transform($url)); return new JsonResponse($transformer->transform($url));
} }

View File

@ -12,6 +12,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface; use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Rest\Action\ShortUrl\ResolveShortUrlAction; use Shlinkio\Shlink\Rest\Action\ShortUrl\ResolveShortUrlAction;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
use function strpos; use function strpos;
@ -32,12 +33,14 @@ class ResolveShortUrlActionTest extends TestCase
public function correctShortCodeReturnsSuccess(): void public function correctShortCodeReturnsSuccess(): void
{ {
$shortCode = 'abc123'; $shortCode = 'abc123';
$this->urlResolver->resolveShortUrl(new ShortUrlIdentifier($shortCode))->willReturn( $apiKey = new ApiKey();
$this->urlResolver->resolveShortUrl(new ShortUrlIdentifier($shortCode), $apiKey)->willReturn(
new ShortUrl('http://domain.com/foo/bar'), new ShortUrl('http://domain.com/foo/bar'),
)->shouldBeCalledOnce(); )->shouldBeCalledOnce();
$request = (new ServerRequest())->withAttribute('shortCode', $shortCode); $request = (new ServerRequest())->withAttribute('shortCode', $shortCode)->withAttribute(ApiKey::class, $apiKey);
$response = $this->action->handle($request); $response = $this->action->handle($request);
self::assertEquals(200, $response->getStatusCode()); self::assertEquals(200, $response->getStatusCode());
self::assertTrue(strpos($response->getBody()->getContents(), 'http://domain.com/foo/bar') > 0); self::assertTrue(strpos($response->getBody()->getContents(), 'http://domain.com/foo/bar') > 0);
} }