mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-25 18:45:27 -06:00
Created services and command to process visits
This commit is contained in:
@@ -9,6 +9,7 @@ return [
|
|||||||
Command\ResolveUrlCommand::class,
|
Command\ResolveUrlCommand::class,
|
||||||
Command\ListShortcodesCommand::class,
|
Command\ListShortcodesCommand::class,
|
||||||
Command\GetVisitsCommand::class,
|
Command\GetVisitsCommand::class,
|
||||||
|
Command\ProcessVisitsCommand::class,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ return [
|
|||||||
CLI\Command\ResolveUrlCommand::class => AnnotatedFactory::class,
|
CLI\Command\ResolveUrlCommand::class => AnnotatedFactory::class,
|
||||||
CLI\Command\ListShortcodesCommand::class => AnnotatedFactory::class,
|
CLI\Command\ListShortcodesCommand::class => AnnotatedFactory::class,
|
||||||
CLI\Command\GetVisitsCommand::class => AnnotatedFactory::class,
|
CLI\Command\GetVisitsCommand::class => AnnotatedFactory::class,
|
||||||
|
CLI\Command\ProcessVisitsCommand::class => AnnotatedFactory::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shlinkio\Shlink\CLI\Command;
|
||||||
|
|
||||||
|
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||||
|
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||||
|
use Shlinkio\Shlink\Common\Service\IpLocationResolver;
|
||||||
|
use Shlinkio\Shlink\Common\Service\IpLocationResolverInterface;
|
||||||
|
use Shlinkio\Shlink\Core\Entity\VisitLocation;
|
||||||
|
use Shlinkio\Shlink\Core\Service\VisitService;
|
||||||
|
use Shlinkio\Shlink\Core\Service\VisitServiceInterface;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class ProcessVisitsCommand extends Command
|
||||||
|
{
|
||||||
|
const LOCALHOST = '127.0.0.1';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var VisitServiceInterface
|
||||||
|
*/
|
||||||
|
private $visitService;
|
||||||
|
/**
|
||||||
|
* @var IpLocationResolverInterface
|
||||||
|
*/
|
||||||
|
private $ipLocationResolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ProcessVisitsCommand constructor.
|
||||||
|
* @param VisitServiceInterface|VisitService $visitService
|
||||||
|
* @param IpLocationResolverInterface|IpLocationResolver $ipLocationResolver
|
||||||
|
*
|
||||||
|
* @Inject({VisitService::class, IpLocationResolver::class})
|
||||||
|
*/
|
||||||
|
public function __construct(VisitServiceInterface $visitService, IpLocationResolverInterface $ipLocationResolver)
|
||||||
|
{
|
||||||
|
parent::__construct(null);
|
||||||
|
$this->visitService = $visitService;
|
||||||
|
$this->ipLocationResolver = $ipLocationResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configure()
|
||||||
|
{
|
||||||
|
$this->setName('visit:process')
|
||||||
|
->setDescription('Processes visits where location is not set already');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$visits = $this->visitService->getUnlocatedVisits();
|
||||||
|
|
||||||
|
foreach ($visits as $visit) {
|
||||||
|
$ipAddr = $visit->getRemoteAddr();
|
||||||
|
$output->write(sprintf('Processing IP <info>%s</info>', $ipAddr));
|
||||||
|
if ($ipAddr === self::LOCALHOST) {
|
||||||
|
$output->writeln(' (<comment>Ignored localhost address</comment>)');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = $this->ipLocationResolver->resolveIpLocation($ipAddr);
|
||||||
|
$location = new VisitLocation();
|
||||||
|
$location->exchangeArray($result);
|
||||||
|
$visit->setVisitLocation($location);
|
||||||
|
$this->visitService->saveVisit($visit);
|
||||||
|
$output->writeln(sprintf(' (Address located at "%s")', $location->getCityName()));
|
||||||
|
} catch (WrongIpException $e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln('Finished processing all IPs');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ return [
|
|||||||
Service\UrlShortener::class => AnnotatedFactory::class,
|
Service\UrlShortener::class => AnnotatedFactory::class,
|
||||||
Service\VisitsTracker::class => AnnotatedFactory::class,
|
Service\VisitsTracker::class => AnnotatedFactory::class,
|
||||||
Service\ShortUrlService::class => AnnotatedFactory::class,
|
Service\ShortUrlService::class => AnnotatedFactory::class,
|
||||||
|
Service\VisitService::class => AnnotatedFactory::class,
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
RedirectMiddleware::class => AnnotatedFactory::class,
|
RedirectMiddleware::class => AnnotatedFactory::class,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use Shlinkio\Shlink\Common\Entity\AbstractEntity;
|
|||||||
* @author
|
* @author
|
||||||
* @link
|
* @link
|
||||||
*
|
*
|
||||||
* @ORM\Entity
|
* @ORM\Entity(repositoryClass="Shlinkio\Shlink\Core\Repository\VisitRepository")
|
||||||
* @ORM\Table(name="visits")
|
* @ORM\Table(name="visits")
|
||||||
*/
|
*/
|
||||||
class Visit extends AbstractEntity implements \JsonSerializable
|
class Visit extends AbstractEntity implements \JsonSerializable
|
||||||
@@ -42,7 +42,7 @@ class Visit extends AbstractEntity implements \JsonSerializable
|
|||||||
protected $shortUrl;
|
protected $shortUrl;
|
||||||
/**
|
/**
|
||||||
* @var VisitLocation
|
* @var VisitLocation
|
||||||
* @ORM\ManyToOne(targetEntity=VisitLocation::class)
|
* @ORM\ManyToOne(targetEntity=VisitLocation::class, cascade={"persist"})
|
||||||
* @ORM\JoinColumn(name="visit_location_id", referencedColumnName="id", nullable=true)
|
* @ORM\JoinColumn(name="visit_location_id", referencedColumnName="id", nullable=true)
|
||||||
*/
|
*/
|
||||||
protected $visitLocation;
|
protected $visitLocation;
|
||||||
|
|||||||
@@ -17,42 +17,37 @@ class VisitLocation extends AbstractEntity implements ArraySerializableInterface
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
* @ORM\Column()
|
* @ORM\Column(nullable=true)
|
||||||
*/
|
*/
|
||||||
protected $countryCode;
|
protected $countryCode;
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
* @ORM\Column()
|
* @ORM\Column(nullable=true)
|
||||||
*/
|
*/
|
||||||
protected $countryName;
|
protected $countryName;
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
* @ORM\Column()
|
* @ORM\Column(nullable=true)
|
||||||
*/
|
*/
|
||||||
protected $regionName;
|
protected $regionName;
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
* @ORM\Column()
|
* @ORM\Column(nullable=true)
|
||||||
*/
|
*/
|
||||||
protected $cityName;
|
protected $cityName;
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
* @ORM\Column()
|
* @ORM\Column(nullable=true)
|
||||||
*/
|
*/
|
||||||
protected $latitude;
|
protected $latitude;
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
* @ORM\Column()
|
* @ORM\Column(nullable=true)
|
||||||
*/
|
*/
|
||||||
protected $longitude;
|
protected $longitude;
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
* @ORM\Column()
|
* @ORM\Column(nullable=true)
|
||||||
*/
|
|
||||||
protected $areaCode;
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
* @ORM\Column()
|
|
||||||
*/
|
*/
|
||||||
protected $timezone;
|
protected $timezone;
|
||||||
|
|
||||||
@@ -164,24 +159,6 @@ class VisitLocation extends AbstractEntity implements ArraySerializableInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getAreaCode()
|
|
||||||
{
|
|
||||||
return $this->areaCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $areaCode
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setAreaCode($areaCode)
|
|
||||||
{
|
|
||||||
$this->areaCode = $areaCode;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
@@ -208,17 +185,17 @@ class VisitLocation extends AbstractEntity implements ArraySerializableInterface
|
|||||||
*/
|
*/
|
||||||
public function exchangeArray(array $array)
|
public function exchangeArray(array $array)
|
||||||
{
|
{
|
||||||
if (array_key_exists('countryCode', $array)) {
|
if (array_key_exists('country_code', $array)) {
|
||||||
$this->setCountryCode($array['countryCode']);
|
$this->setCountryCode($array['country_code']);
|
||||||
}
|
}
|
||||||
if (array_key_exists('countryName', $array)) {
|
if (array_key_exists('country_name', $array)) {
|
||||||
$this->setCountryName($array['countryName']);
|
$this->setCountryName($array['country_name']);
|
||||||
}
|
}
|
||||||
if (array_key_exists('regionName', $array)) {
|
if (array_key_exists('region_name', $array)) {
|
||||||
$this->setRegionName($array['regionName']);
|
$this->setRegionName($array['region_name']);
|
||||||
}
|
}
|
||||||
if (array_key_exists('cityName', $array)) {
|
if (array_key_exists('city', $array)) {
|
||||||
$this->setCityName($array['cityName']);
|
$this->setCityName($array['city']);
|
||||||
}
|
}
|
||||||
if (array_key_exists('latitude', $array)) {
|
if (array_key_exists('latitude', $array)) {
|
||||||
$this->setLatitude($array['latitude']);
|
$this->setLatitude($array['latitude']);
|
||||||
@@ -226,11 +203,8 @@ class VisitLocation extends AbstractEntity implements ArraySerializableInterface
|
|||||||
if (array_key_exists('longitude', $array)) {
|
if (array_key_exists('longitude', $array)) {
|
||||||
$this->setLongitude($array['longitude']);
|
$this->setLongitude($array['longitude']);
|
||||||
}
|
}
|
||||||
if (array_key_exists('areaCode', $array)) {
|
if (array_key_exists('time_zone', $array)) {
|
||||||
$this->setAreaCode($array['areaCode']);
|
$this->setTimezone($array['time_zone']);
|
||||||
}
|
|
||||||
if (array_key_exists('timezone', $array)) {
|
|
||||||
$this->setTimezone($array['timezone']);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +222,6 @@ class VisitLocation extends AbstractEntity implements ArraySerializableInterface
|
|||||||
'cityName' => $this->cityName,
|
'cityName' => $this->cityName,
|
||||||
'latitude' => $this->latitude,
|
'latitude' => $this->latitude,
|
||||||
'longitude' => $this->longitude,
|
'longitude' => $this->longitude,
|
||||||
'areaCode' => $this->areaCode,
|
|
||||||
'timezone' => $this->timezone,
|
'timezone' => $this->timezone,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shlinkio\Shlink\Core\Repository;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||||
|
|
||||||
|
class VisitRepository extends EntityRepository implements VisitRepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return Visit[]
|
||||||
|
*/
|
||||||
|
public function findUnlocatedVisits()
|
||||||
|
{
|
||||||
|
$qb = $this->createQueryBuilder('v');
|
||||||
|
$qb->where($qb->expr()->isNull('v.visitLocation'));
|
||||||
|
|
||||||
|
return $qb->getQuery()->getResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shlinkio\Shlink\Core\Repository;
|
||||||
|
|
||||||
|
use Doctrine\Common\Persistence\ObjectRepository;
|
||||||
|
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||||
|
|
||||||
|
interface VisitRepositoryInterface extends ObjectRepository
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return Visit[]
|
||||||
|
*/
|
||||||
|
public function findUnlocatedVisits();
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shlinkio\Shlink\Core\Service;
|
||||||
|
|
||||||
|
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||||
|
use Shlinkio\Shlink\Core\Repository\VisitRepository;
|
||||||
|
|
||||||
|
class VisitService implements VisitServiceInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var EntityManagerInterface
|
||||||
|
*/
|
||||||
|
private $em;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VisitService constructor.
|
||||||
|
* @param EntityManagerInterface $em
|
||||||
|
*
|
||||||
|
* @Inject({"em"})
|
||||||
|
*/
|
||||||
|
public function __construct(EntityManagerInterface $em)
|
||||||
|
{
|
||||||
|
$this->em = $em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Visit[]
|
||||||
|
*/
|
||||||
|
public function getUnlocatedVisits()
|
||||||
|
{
|
||||||
|
/** @var VisitRepository $repo */
|
||||||
|
$repo = $this->em->getRepository(Visit::class);
|
||||||
|
return $repo->findUnlocatedVisits();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Visit $visit
|
||||||
|
*/
|
||||||
|
public function saveVisit(Visit $visit)
|
||||||
|
{
|
||||||
|
$this->em->persist($visit);
|
||||||
|
$this->em->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shlinkio\Shlink\Core\Service;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||||
|
|
||||||
|
interface VisitServiceInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return Visit[]
|
||||||
|
*/
|
||||||
|
public function getUnlocatedVisits();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Visit $visit
|
||||||
|
*/
|
||||||
|
public function saveVisit(Visit $visit);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user