Merge pull request #745 from acelaya-forks/feature/general-visits

Feature/general visits
This commit is contained in:
Alejandro Celaya 2020-05-01 12:16:22 +02:00 committed by GitHub
commit 84b38c4940
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 323 additions and 98 deletions

View File

@ -21,6 +21,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
Also, Shlink exposes a new endpoint `GET /rest/v2/mercure-info`, which returns the public URL of the mercure hub, and a valid JWT that can be used to subsribe to updates.
* [#673](https://github.com/shlinkio/shlink/issues/673) Added new `[GET /visits]` rest endpoint which returns basic visits stats.
#### Changed
* *Nothing*

View File

@ -0,0 +1,10 @@
{
"type": "object",
"required": ["visitsCount"],
"properties": {
"visitsCount": {
"type": "number",
"description": "The total amount of visits received."
}
}
}

View File

@ -0,0 +1,54 @@
{
"get": {
"operationId": "getGlobalVisits",
"tags": [
"Visits"
],
"summary": "Get general visits stats",
"description": "Get general visits stats not linked to one specific short URL.",
"parameters": [
{
"$ref": "../parameters/version.json"
}
],
"security": [
{
"ApiKey": []
}
],
"responses": {
"200": {
"description": "Visits stats.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"visits": {
"$ref": "../definitions/VisitStats.json"
}
}
}
}
},
"examples": {
"application/json": {
"visits": {
"visitsCount": 1569874
}
}
}
},
"500": {
"description": "Unexpected error.",
"content": {
"application/problem+json": {
"schema": {
"$ref": "../definitions/Error.json"
}
}
}
}
}
}
}

View File

@ -78,6 +78,9 @@
"$ref": "paths/v1_tags.json"
},
"/rest/v{version}/visits": {
"$ref": "paths/v2_visits.json"
},
"/rest/v{version}/short-urls/{shortCode}/visits": {
"$ref": "paths/v1_short-urls_{shortCode}_visits.json"
},

View File

@ -27,6 +27,7 @@ return [
Service\VisitsTracker::class => ConfigAbstractFactory::class,
Service\ShortUrlService::class => ConfigAbstractFactory::class,
Visit\VisitLocator::class => ConfigAbstractFactory::class,
Visit\VisitsStatsHelper::class => ConfigAbstractFactory::class,
Service\Tag\TagService::class => ConfigAbstractFactory::class,
Service\ShortUrl\DeleteShortUrlService::class => ConfigAbstractFactory::class,
Service\ShortUrl\ShortUrlResolver::class => ConfigAbstractFactory::class,
@ -56,6 +57,7 @@ return [
Service\VisitsTracker::class => ['em', EventDispatcherInterface::class],
Service\ShortUrlService::class => ['em', Service\ShortUrl\ShortUrlResolver::class, Util\UrlValidator::class],
Visit\VisitLocator::class => ['em'],
Visit\VisitsStatsHelper::class => ['em'],
Service\Tag\TagService::class => ['em'],
Service\ShortUrl\DeleteShortUrlService::class => [
'em',

View File

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

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Visit;
use Doctrine\ORM\EntityManagerInterface;
use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Repository\VisitRepository;
use Shlinkio\Shlink\Core\Visit\Model\VisitsStats;
class VisitsStatsHelper implements VisitsStatsHelperInterface
{
private EntityManagerInterface $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function getVisitsStats(): VisitsStats
{
return new VisitsStats($this->getVisitsCount());
}
private function getVisitsCount(): int
{
/** @var VisitRepository $visitsRepo */
$visitsRepo = $this->em->getRepository(Visit::class);
return $visitsRepo->count([]);
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Visit;
use Shlinkio\Shlink\Core\Visit\Model\VisitsStats;
interface VisitsStatsHelperInterface
{
public function getVisitsStats(): VisitsStats;
}

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Core\Visit;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Repository\VisitRepository;
use Shlinkio\Shlink\Core\Visit\Model\VisitsStats;
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelper;
use function Functional\map;
use function range;
class VisitsStatsHelperTest extends TestCase
{
private VisitsStatsHelper $helper;
private ObjectProphecy $em;
public function setUp(): void
{
$this->em = $this->prophesize(EntityManagerInterface::class);
$this->helper = new VisitsStatsHelper($this->em->reveal());
}
/**
* @test
* @dataProvider provideCounts
*/
public function returnsExpectedVisitsStats(int $expectedCount): void
{
$repo = $this->prophesize(VisitRepository::class);
$count = $repo->count([])->willReturn($expectedCount);
$getRepo = $this->em->getRepository(Visit::class)->willReturn($repo->reveal());
$stats = $this->helper->getVisitsStats();
$this->assertEquals(new VisitsStats($expectedCount), $stats);
$count->shouldHaveBeenCalledOnce();
$getRepo->shouldHaveBeenCalledOnce();
}
public function provideCounts(): iterable
{
return map(range(0, 50, 5), fn (int $value) => [$value]);
}
}

View File

@ -7,10 +7,10 @@ namespace Shlinkio\Shlink\Rest;
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
use Laminas\ServiceManager\Factory\InvokableFactory;
use Mezzio\Router\Middleware\ImplicitOptionsMiddleware;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Common\Mercure\LcobucciJwtProvider;
use Shlinkio\Shlink\Core\Options\AppOptions;
use Shlinkio\Shlink\Core\Service;
use Shlinkio\Shlink\Core\Visit;
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
return [
@ -28,7 +28,8 @@ return [
Action\ShortUrl\ResolveShortUrlAction::class => ConfigAbstractFactory::class,
Action\ShortUrl\ListShortUrlsAction::class => ConfigAbstractFactory::class,
Action\ShortUrl\EditShortUrlTagsAction::class => ConfigAbstractFactory::class,
Action\Visit\GetVisitsAction::class => ConfigAbstractFactory::class,
Action\Visit\ShortUrlVisitsAction::class => ConfigAbstractFactory::class,
Action\Visit\GlobalVisitsAction::class => ConfigAbstractFactory::class,
Action\Tag\ListTagsAction::class => ConfigAbstractFactory::class,
Action\Tag\DeleteTagsAction::class => ConfigAbstractFactory::class,
Action\Tag\CreateTagsAction::class => ConfigAbstractFactory::class,
@ -46,36 +47,28 @@ return [
ConfigAbstractFactory::class => [
ApiKeyService::class => ['em'],
Action\HealthAction::class => ['em', AppOptions::class, 'Logger_Shlink'],
Action\MercureInfoAction::class => [LcobucciJwtProvider::class, 'config.mercure', 'Logger_Shlink'],
Action\ShortUrl\CreateShortUrlAction::class => [
Service\UrlShortener::class,
'config.url_shortener.domain',
'Logger_Shlink',
],
Action\HealthAction::class => ['em', AppOptions::class],
Action\MercureInfoAction::class => [LcobucciJwtProvider::class, 'config.mercure'],
Action\ShortUrl\CreateShortUrlAction::class => [Service\UrlShortener::class, 'config.url_shortener.domain'],
Action\ShortUrl\SingleStepCreateShortUrlAction::class => [
Service\UrlShortener::class,
ApiKeyService::class,
'config.url_shortener.domain',
'Logger_Shlink',
],
Action\ShortUrl\EditShortUrlAction::class => [Service\ShortUrlService::class, 'Logger_Shlink'],
Action\ShortUrl\DeleteShortUrlAction::class => [Service\ShortUrl\DeleteShortUrlService::class, 'Logger_Shlink'],
Action\ShortUrl\EditShortUrlAction::class => [Service\ShortUrlService::class],
Action\ShortUrl\DeleteShortUrlAction::class => [Service\ShortUrl\DeleteShortUrlService::class],
Action\ShortUrl\ResolveShortUrlAction::class => [
Service\ShortUrl\ShortUrlResolver::class,
'config.url_shortener.domain',
],
Action\Visit\GetVisitsAction::class => [Service\VisitsTracker::class, 'Logger_Shlink'],
Action\ShortUrl\ListShortUrlsAction::class => [
Service\ShortUrlService::class,
'config.url_shortener.domain',
'Logger_Shlink',
],
Action\ShortUrl\EditShortUrlTagsAction::class => [Service\ShortUrlService::class, 'Logger_Shlink'],
Action\Tag\ListTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class],
Action\Tag\DeleteTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class],
Action\Tag\CreateTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class],
Action\Tag\UpdateTagAction::class => [Service\Tag\TagService::class, LoggerInterface::class],
Action\Visit\ShortUrlVisitsAction::class => [Service\VisitsTracker::class],
Action\Visit\GlobalVisitsAction::class => [Visit\VisitsStatsHelper::class],
Action\ShortUrl\ListShortUrlsAction::class => [Service\ShortUrlService::class, 'config.url_shortener.domain'],
Action\ShortUrl\EditShortUrlTagsAction::class => [Service\ShortUrlService::class],
Action\Tag\ListTagsAction::class => [Service\Tag\TagService::class],
Action\Tag\DeleteTagsAction::class => [Service\Tag\TagService::class],
Action\Tag\CreateTagsAction::class => [Service\Tag\TagService::class],
Action\Tag\UpdateTagAction::class => [Service\Tag\TagService::class],
Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware::class => ['config.url_shortener.domain.hostname'],
Middleware\ShortUrl\DefaultShortCodesLengthMiddleware::class => [

View File

@ -26,7 +26,8 @@ return [
Action\ShortUrl\EditShortUrlTagsAction::getRouteDef([$dropDomainMiddleware]),
// Visits
Action\Visit\GetVisitsAction::getRouteDef([$dropDomainMiddleware]),
Action\Visit\ShortUrlVisitsAction::getRouteDef([$dropDomainMiddleware]),
Action\Visit\GlobalVisitsAction::getRouteDef(),
// Tags
Action\Tag\ListTagsAction::getRouteDef(),

View File

@ -7,8 +7,6 @@ namespace Shlinkio\Shlink\Rest\Action;
use Fig\Http\Message\RequestMethodInterface;
use Fig\Http\Message\StatusCodeInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use function array_merge;
@ -17,13 +15,6 @@ abstract class AbstractRestAction implements RequestHandlerInterface, RequestMet
protected const ROUTE_PATH = '';
protected const ROUTE_ALLOWED_METHODS = [];
protected LoggerInterface $logger;
public function __construct(?LoggerInterface $logger = null)
{
$this->logger = $logger ?: new NullLogger();
}
public static function getRouteDef(array $prevMiddleware = [], array $postMiddleware = []): array
{
return [

View File

@ -8,7 +8,6 @@ use Doctrine\ORM\EntityManagerInterface;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Core\Options\AppOptions;
use Throwable;
@ -24,9 +23,8 @@ class HealthAction extends AbstractRestAction
private EntityManagerInterface $em;
private AppOptions $options;
public function __construct(EntityManagerInterface $em, AppOptions $options, ?LoggerInterface $logger = null)
public function __construct(EntityManagerInterface $em, AppOptions $options)
{
parent::__construct($logger);
$this->em = $em;
$this->options = $options;
}

View File

@ -8,7 +8,6 @@ use Cake\Chronos\Chronos;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Common\Mercure\JwtProviderInterface;
use Shlinkio\Shlink\Rest\Exception\MercureException;
use Throwable;
@ -23,12 +22,8 @@ class MercureInfoAction extends AbstractRestAction
private JwtProviderInterface $jwtProvider;
private array $mercureConfig;
public function __construct(
JwtProviderInterface $jwtProvider,
array $mercureConfig,
?LoggerInterface $logger = null
) {
parent::__construct($logger);
public function __construct(JwtProviderInterface $jwtProvider, array $mercureConfig)
{
$this->jwtProvider = $jwtProvider;
$this->mercureConfig = $mercureConfig;
}

View File

@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Rest\Action\ShortUrl;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Core\Exception\ValidationException;
use Shlinkio\Shlink\Core\Model\CreateShortUrlData;
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
@ -19,12 +18,8 @@ abstract class AbstractCreateShortUrlAction extends AbstractRestAction
private UrlShortenerInterface $urlShortener;
private array $domainConfig;
public function __construct(
UrlShortenerInterface $urlShortener,
array $domainConfig,
?LoggerInterface $logger = null
) {
parent::__construct($logger);
public function __construct(UrlShortenerInterface $urlShortener, array $domainConfig)
{
$this->urlShortener = $urlShortener;
$this->domainConfig = $domainConfig;
}

View File

@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Rest\Action\ShortUrl;
use Laminas\Diactoros\Response\EmptyResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlServiceInterface;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
@ -19,9 +18,8 @@ class DeleteShortUrlAction extends AbstractRestAction
private DeleteShortUrlServiceInterface $deleteShortUrlService;
public function __construct(DeleteShortUrlServiceInterface $deleteShortUrlService, ?LoggerInterface $logger = null)
public function __construct(DeleteShortUrlServiceInterface $deleteShortUrlService)
{
parent::__construct($logger);
$this->deleteShortUrlService = $deleteShortUrlService;
}

View File

@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Rest\Action\ShortUrl;
use Laminas\Diactoros\Response\EmptyResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Core\Model\ShortUrlEdit;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
@ -20,9 +19,8 @@ class EditShortUrlAction extends AbstractRestAction
private ShortUrlServiceInterface $shortUrlService;
public function __construct(ShortUrlServiceInterface $shortUrlService, ?LoggerInterface $logger = null)
public function __construct(ShortUrlServiceInterface $shortUrlService)
{
parent::__construct($logger);
$this->shortUrlService = $shortUrlService;
}

View File

@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Rest\Action\ShortUrl;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Core\Exception\ValidationException;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
@ -20,9 +19,8 @@ class EditShortUrlTagsAction extends AbstractRestAction
private ShortUrlServiceInterface $shortUrlService;
public function __construct(ShortUrlServiceInterface $shortUrlService, ?LoggerInterface $logger = null)
public function __construct(ShortUrlServiceInterface $shortUrlService)
{
parent::__construct($logger);
$this->shortUrlService = $shortUrlService;
}

View File

@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Rest\Action\ShortUrl;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
@ -24,12 +23,8 @@ class ListShortUrlsAction extends AbstractRestAction
private ShortUrlServiceInterface $shortUrlService;
private array $domainConfig;
public function __construct(
ShortUrlServiceInterface $shortUrlService,
array $domainConfig,
?LoggerInterface $logger = null
) {
parent::__construct($logger);
public function __construct(ShortUrlServiceInterface $shortUrlService, array $domainConfig)
{
$this->shortUrlService = $shortUrlService;
$this->domainConfig = $domainConfig;
}

View File

@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Rest\Action\ShortUrl;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
@ -21,12 +20,8 @@ class ResolveShortUrlAction extends AbstractRestAction
private ShortUrlResolverInterface $urlResolver;
private array $domainConfig;
public function __construct(
ShortUrlResolverInterface $urlResolver,
array $domainConfig,
?LoggerInterface $logger = null
) {
parent::__construct($logger);
public function __construct(ShortUrlResolverInterface $urlResolver, array $domainConfig)
{
$this->urlResolver = $urlResolver;
$this->domainConfig = $domainConfig;
}

View File

@ -6,7 +6,6 @@ namespace Shlinkio\Shlink\Rest\Action\ShortUrl;
use Laminas\Diactoros\Uri;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Core\Exception\ValidationException;
use Shlinkio\Shlink\Core\Model\CreateShortUrlData;
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
@ -22,10 +21,9 @@ class SingleStepCreateShortUrlAction extends AbstractCreateShortUrlAction
public function __construct(
UrlShortenerInterface $urlShortener,
ApiKeyServiceInterface $apiKeyService,
array $domainConfig,
?LoggerInterface $logger = null
array $domainConfig
) {
parent::__construct($urlShortener, $domainConfig, $logger);
parent::__construct($urlShortener, $domainConfig);
$this->apiKeyService = $apiKeyService;
}

View File

@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Rest\Action\Tag;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
@ -18,9 +17,8 @@ class CreateTagsAction extends AbstractRestAction
private TagServiceInterface $tagService;
public function __construct(TagServiceInterface $tagService, ?LoggerInterface $logger = null)
public function __construct(TagServiceInterface $tagService)
{
parent::__construct($logger);
$this->tagService = $tagService;
}

View File

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

View File

@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Rest\Action\Tag;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
@ -18,9 +17,8 @@ class ListTagsAction extends AbstractRestAction
private TagServiceInterface $tagService;
public function __construct(TagServiceInterface $tagService, ?LoggerInterface $logger = null)
public function __construct(TagServiceInterface $tagService)
{
parent::__construct($logger);
$this->tagService = $tagService;
}

View File

@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Rest\Action\Tag;
use Laminas\Diactoros\Response\EmptyResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Core\Exception\ValidationException;
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
@ -19,9 +18,8 @@ class UpdateTagAction extends AbstractRestAction
private TagServiceInterface $tagService;
public function __construct(TagServiceInterface $tagService, ?LoggerInterface $logger = null)
public function __construct(TagServiceInterface $tagService)
{
parent::__construct($logger);
$this->tagService = $tagService;
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Rest\Action\Visit;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
class GlobalVisitsAction extends AbstractRestAction
{
protected const ROUTE_PATH = '/visits';
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET];
private VisitsStatsHelperInterface $statsHelper;
public function __construct(VisitsStatsHelperInterface $statsHelper)
{
$this->statsHelper = $statsHelper;
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
return new JsonResponse([
'visits' => $this->statsHelper->getVisitsStats(),
]);
}
}

View File

@ -7,14 +7,13 @@ namespace Shlinkio\Shlink\Rest\Action\Visit;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Model\VisitsParams;
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
class GetVisitsAction extends AbstractRestAction
class ShortUrlVisitsAction extends AbstractRestAction
{
use PaginatorUtilsTrait;
@ -23,9 +22,8 @@ class GetVisitsAction extends AbstractRestAction
private VisitsTrackerInterface $visitsTracker;
public function __construct(VisitsTrackerInterface $visitsTracker, ?LoggerInterface $logger = null)
public function __construct(VisitsTrackerInterface $visitsTracker)
{
parent::__construct($logger);
$this->visitsTracker = $visitsTracker;
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace ShlinkioApiTest\Shlink\Rest\Action;
use Shlinkio\Shlink\TestUtils\ApiTest\ApiTestCase;
class GlobalVisitsActionTest extends ApiTestCase
{
/** @test */
public function returnsExpectedVisitsStats(): void
{
$resp = $this->callApiWithKey(self::METHOD_GET, '/visits');
$payload = $this->getJsonResponsePayload($resp);
$this->assertArrayHasKey('visits', $payload);
$this->assertArrayHasKey('visitsCount', $payload['visits']);
$this->assertEquals(7, $payload['visits']['visitsCount']);
}
}

View File

@ -11,7 +11,7 @@ use ShlinkioApiTest\Shlink\Rest\Utils\NotFoundUrlHelpersTrait;
use function GuzzleHttp\Psr7\build_query;
use function sprintf;
class GetVisitsActionTest extends ApiTestCase
class ShortUrlVisitsActionTest extends ApiTestCase
{
use NotFoundUrlHelpersTrait;

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Rest\Action\Visit;
use Laminas\Diactoros\Response\JsonResponse;
use Laminas\Diactoros\ServerRequestFactory;
use PHPUnit\Framework\TestCase;
use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Core\Visit\Model\VisitsStats;
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
use Shlinkio\Shlink\Rest\Action\Visit\GlobalVisitsAction;
class GlobalVisitsActionTest extends TestCase
{
private GlobalVisitsAction $action;
private ObjectProphecy $helper;
public function setUp(): void
{
$this->helper = $this->prophesize(VisitsStatsHelperInterface::class);
$this->action = new GlobalVisitsAction($this->helper->reveal());
}
/** @test */
public function statsAreReturnedFromHelper(): void
{
$stats = new VisitsStats(5);
$getStats = $this->helper->getVisitsStats()->willReturn($stats);
/** @var JsonResponse $resp */
$resp = $this->action->handle(ServerRequestFactory::fromGlobals());
$payload = $resp->getPayload();
$this->assertEquals($payload, ['visits' => $stats]);
$getStats->shouldHaveBeenCalledOnce();
}
}

View File

@ -15,17 +15,17 @@ use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Model\VisitsParams;
use Shlinkio\Shlink\Core\Service\VisitsTracker;
use Shlinkio\Shlink\Rest\Action\Visit\GetVisitsAction;
use Shlinkio\Shlink\Rest\Action\Visit\ShortUrlVisitsAction;
class GetVisitsActionTest extends TestCase
class ShortUrlVisitsActionTest extends TestCase
{
private GetVisitsAction $action;
private ShortUrlVisitsAction $action;
private ObjectProphecy $visitsTracker;
public function setUp(): void
{
$this->visitsTracker = $this->prophesize(VisitsTracker::class);
$this->action = new GetVisitsAction($this->visitsTracker->reveal());
$this->action = new ShortUrlVisitsAction($this->visitsTracker->reveal());
}
/** @test */