mirror of
https://github.com/shlinkio/shlink.git
synced 2024-12-22 15:13:59 -06:00
Merge branch 'feature/21' into develop
This commit is contained in:
commit
b413b16c86
@ -222,9 +222,12 @@ Posible errors:
|
||||
|
||||
**REQUEST**
|
||||
|
||||
* `GET` -> `/rest/visits/{shortCode}`
|
||||
* `GET` -> `/rest/short-codes/{shortCode}/visits`
|
||||
* Route params:
|
||||
* shortCode: `string` -> The shortCode from which we eant to get the visits.
|
||||
* Query params:
|
||||
* startDate: `string` -> If provided, only visits older that this date will be returned
|
||||
* endDate: `string` -> If provided, only visits newer that this date will be returned
|
||||
* Headers:
|
||||
* X-Auth-Token: `string` -> The token provided in the authentication request
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
namespace Shlinkio\Shlink\CLI\Command;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
@ -9,6 +10,7 @@ use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
|
||||
@ -35,7 +37,19 @@ class GetVisitsCommand extends Command
|
||||
{
|
||||
$this->setName('shortcode:visits')
|
||||
->setDescription('Returns the detailed visits information for provided short code')
|
||||
->addArgument('shortCode', InputArgument::REQUIRED, 'The short code which visits we want to get');
|
||||
->addArgument('shortCode', InputArgument::REQUIRED, 'The short code which visits we want to get')
|
||||
->addOption(
|
||||
'startDate',
|
||||
's',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Allows to filter visits, returning only those older than start date'
|
||||
)
|
||||
->addOption(
|
||||
'endDate',
|
||||
'e',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Allows to filter visits, returning only those newer than end date'
|
||||
);
|
||||
}
|
||||
|
||||
public function interact(InputInterface $input, OutputInterface $output)
|
||||
@ -60,18 +74,35 @@ class GetVisitsCommand extends Command
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$shortCode = $input->getArgument('shortCode');
|
||||
$visits = $this->visitsTracker->info($shortCode);
|
||||
$startDate = $this->getDateOption($input, 'startDate');
|
||||
$endDate = $this->getDateOption($input, 'endDate');
|
||||
|
||||
$visits = $this->visitsTracker->info($shortCode, new DateRange($startDate, $endDate));
|
||||
$table = new Table($output);
|
||||
$table->setHeaders([
|
||||
'Referer',
|
||||
'Date',
|
||||
'Temote Address',
|
||||
'Remote Address',
|
||||
'User agent',
|
||||
]);
|
||||
|
||||
foreach ($visits as $row) {
|
||||
$table->addRow(array_values($row->jsonSerialize()));
|
||||
$rowData = $row->jsonSerialize();
|
||||
// Unset location info
|
||||
unset($rowData['visitLocation']);
|
||||
|
||||
$table->addRow(array_values($rowData));
|
||||
}
|
||||
$table->render();
|
||||
}
|
||||
|
||||
protected function getDateOption(InputInterface $input, $key)
|
||||
{
|
||||
$value = $input->getOption($key);
|
||||
if (isset($value)) {
|
||||
$value = new \DateTime($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
44
module/Common/src/Util/DateRange.php
Normal file
44
module/Common/src/Util/DateRange.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\Util;
|
||||
|
||||
class DateRange
|
||||
{
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
private $startDate;
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
private $endDate;
|
||||
|
||||
public function __construct(\DateTimeInterface $startDate = null, \DateTimeInterface $endDate = null)
|
||||
{
|
||||
$this->startDate = $startDate;
|
||||
$this->endDate = $endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface
|
||||
*/
|
||||
public function getStartDate()
|
||||
{
|
||||
return $this->startDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface
|
||||
*/
|
||||
public function getEndDate()
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return is_null($this->startDate) && is_null($this->endDate);
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
namespace Shlinkio\Shlink\Core\Repository;
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
|
||||
class VisitRepository extends EntityRepository implements VisitRepositoryInterface
|
||||
@ -16,4 +18,39 @@ class VisitRepository extends EntityRepository implements VisitRepositoryInterfa
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ShortUrl|int $shortUrl
|
||||
* @param DateRange|null $dateRange
|
||||
* @return Visit[]
|
||||
*/
|
||||
public function findVisitsByShortUrl($shortUrl, DateRange $dateRange = null)
|
||||
{
|
||||
$shortUrl = $shortUrl instanceof ShortUrl
|
||||
? $shortUrl
|
||||
: $this->getEntityManager()->find(ShortUrl::class, $shortUrl);
|
||||
if (! isset($dateRange) || $dateRange->isEmpty()) {
|
||||
$startDate = $shortUrl->getDateCreated();
|
||||
$endDate = clone $startDate;
|
||||
$endDate->add(new \DateInterval('P2D'));
|
||||
$dateRange = new DateRange($startDate, $endDate);
|
||||
}
|
||||
|
||||
$qb = $this->createQueryBuilder('v');
|
||||
$qb->where($qb->expr()->eq('v.shortUrl', ':shortUrl'))
|
||||
->setParameter('shortUrl', $shortUrl)
|
||||
->orderBy('v.date', 'DESC') ;
|
||||
|
||||
// Apply date range filtering
|
||||
if (! empty($dateRange->getStartDate())) {
|
||||
$qb->andWhere($qb->expr()->gte('v.date', ':startDate'))
|
||||
->setParameter('startDate', $dateRange->getStartDate());
|
||||
}
|
||||
if (! empty($dateRange->getEndDate())) {
|
||||
$qb->andWhere($qb->expr()->lte('v.date', ':endDate'))
|
||||
->setParameter('endDate', $dateRange->getEndDate());
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
namespace Shlinkio\Shlink\Core\Repository;
|
||||
|
||||
use Doctrine\Common\Persistence\ObjectRepository;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
|
||||
interface VisitRepositoryInterface extends ObjectRepository
|
||||
@ -10,4 +12,11 @@ interface VisitRepositoryInterface extends ObjectRepository
|
||||
* @return Visit[]
|
||||
*/
|
||||
public function findUnlocatedVisits();
|
||||
|
||||
/**
|
||||
* @param ShortUrl|int $shortUrl
|
||||
* @param DateRange|null $dateRange
|
||||
* @return Visit[]
|
||||
*/
|
||||
public function findVisitsByShortUrl($shortUrl, DateRange $dateRange = null);
|
||||
}
|
||||
|
@ -4,9 +4,10 @@ namespace Shlinkio\Shlink\Core\Service;
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Zend\Paginator\Paginator;
|
||||
use Shlinkio\Shlink\Core\Repository\VisitRepository;
|
||||
|
||||
class VisitsTracker implements VisitsTrackerInterface
|
||||
{
|
||||
@ -62,12 +63,13 @@ class VisitsTracker implements VisitsTrackerInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the visits on certain shortcode
|
||||
* Returns the visits on certain short code
|
||||
*
|
||||
* @param $shortCode
|
||||
* @return Paginator|Visit[]
|
||||
* @param DateRange $dateRange
|
||||
* @return Visit[]
|
||||
*/
|
||||
public function info($shortCode)
|
||||
public function info($shortCode, DateRange $dateRange = null)
|
||||
{
|
||||
/** @var ShortUrl $shortUrl */
|
||||
$shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([
|
||||
@ -77,10 +79,8 @@ class VisitsTracker implements VisitsTrackerInterface
|
||||
throw new InvalidArgumentException(sprintf('Short code "%s" not found', $shortCode));
|
||||
}
|
||||
|
||||
return $this->em->getRepository(Visit::class)->findBy([
|
||||
'shortUrl' => $shortUrl,
|
||||
], [
|
||||
'date' => 'DESC'
|
||||
]);
|
||||
/** @var VisitRepository $repo */
|
||||
$repo = $this->em->getRepository(Visit::class);
|
||||
return $repo->findVisitsByShortUrl($shortUrl, $dateRange);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Service;
|
||||
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Zend\Paginator\Paginator;
|
||||
|
||||
interface VisitsTrackerInterface
|
||||
{
|
||||
@ -15,10 +15,11 @@ interface VisitsTrackerInterface
|
||||
public function track($shortCode, array $visitorData = null);
|
||||
|
||||
/**
|
||||
* Returns the visits on certain shortcode
|
||||
* Returns the visits on certain short code
|
||||
*
|
||||
* @param $shortCode
|
||||
* @return Paginator|Visit[]
|
||||
* @param DateRange $dateRange
|
||||
* @return Visit[]
|
||||
*/
|
||||
public function info($shortCode);
|
||||
public function info($shortCode, DateRange $dateRange = null);
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => 'rest-get-visits',
|
||||
'path' => '/rest/visits/{shortCode}',
|
||||
'path' => '/rest/short-codes/{shortCode}/visits',
|
||||
'middleware' => Action\GetVisitsMiddleware::class,
|
||||
'allowed_methods' => ['GET', 'OPTIONS'],
|
||||
],
|
||||
|
@ -5,6 +5,7 @@ use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
@ -37,14 +38,15 @@ class GetVisitsMiddleware extends AbstractRestMiddleware
|
||||
public function dispatch(Request $request, Response $response, callable $out = null)
|
||||
{
|
||||
$shortCode = $request->getAttribute('shortCode');
|
||||
$startDate = $this->getDateQueryParam($request, 'startDate');
|
||||
$endDate = $this->getDateQueryParam($request, 'endDate');
|
||||
|
||||
try {
|
||||
$visits = $this->visitsTracker->info($shortCode);
|
||||
$visits = $this->visitsTracker->info($shortCode, new DateRange($startDate, $endDate));
|
||||
|
||||
return new JsonResponse([
|
||||
'visits' => [
|
||||
'data' => $visits,
|
||||
// 'pagination' => [],
|
||||
]
|
||||
]);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
@ -59,4 +61,19 @@ class GetVisitsMiddleware extends AbstractRestMiddleware
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param $key
|
||||
* @return \DateTime|null
|
||||
*/
|
||||
protected function getDateQueryParam(Request $request, $key)
|
||||
{
|
||||
$query = $request->getQueryParams();
|
||||
if (! isset($query[$key]) || empty($query[$key])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new \DateTime($query[$key]);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user