mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-14 04:00:57 +03:00
Implemented rest endpoint to return shortcode visits
This commit is contained in:
parent
ab8ccd7df1
commit
305df3a95b
8 changed files with 158 additions and 10 deletions
|
@ -15,16 +15,22 @@ return [
|
|||
// Rest
|
||||
[
|
||||
'name' => 'rest-create-shortcode',
|
||||
'path' => '/rest/short-code',
|
||||
'path' => '/rest/short-codes',
|
||||
'middleware' => Rest\CreateShortcodeMiddleware::class,
|
||||
'allowed_methods' => ['POST'],
|
||||
],
|
||||
[
|
||||
'name' => 'rest-resolve-url',
|
||||
'path' => '/rest/short-code/{shortCode}',
|
||||
'path' => '/rest/short-codes/{shortCode}',
|
||||
'middleware' => Rest\ResolveUrlMiddleware::class,
|
||||
'allowed_methods' => ['GET'],
|
||||
],
|
||||
[
|
||||
'name' => 'rest-get-visits',
|
||||
'path' => '/rest/visits/{shortCode}',
|
||||
'middleware' => Rest\GetVisitsMiddleware::class,
|
||||
'allowed_methods' => ['GET'],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -46,11 +46,13 @@ return [
|
|||
Middleware\Routable\RedirectMiddleware::class => AnnotatedFactory::class,
|
||||
Middleware\Rest\CreateShortcodeMiddleware::class => AnnotatedFactory::class,
|
||||
Middleware\Rest\ResolveUrlMiddleware::class => AnnotatedFactory::class,
|
||||
Middleware\Rest\GetVisitsMiddleware::class => AnnotatedFactory::class,
|
||||
],
|
||||
'aliases' => [
|
||||
'em' => EntityManager::class,
|
||||
'httpClient' => GuzzleHttp\Client::class,
|
||||
Router\RouterInterface::class => Router\FastRouteRouter::class,
|
||||
AnnotatedFactory::CACHE_SERVICE => Cache::class,
|
||||
]
|
||||
],
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use Doctrine\ORM\Mapping as ORM;
|
|||
* @ORM\Entity
|
||||
* @ORM\Table(name="visits")
|
||||
*/
|
||||
class Visit extends AbstractEntity
|
||||
class Visit extends AbstractEntity implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
|
@ -134,4 +134,21 @@ class Visit extends AbstractEntity
|
|||
$this->userAgent = $userAgent;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify data which should be serialized to JSON
|
||||
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||
* @return mixed data which can be serialized by <b>json_encode</b>,
|
||||
* which is a value of any type other than a resource.
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return [
|
||||
'referer' => $this->referer,
|
||||
'date' => isset($this->date) ? $this->date->format(\DateTime::ISO8601) : null,
|
||||
'remoteAddr' => $this->remoteAddr,
|
||||
'userAgent' => $this->userAgent,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
6
src/Exception/InvalidArgumentException.php
Normal file
6
src/Exception/InvalidArgumentException.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
namespace Acelaya\UrlShortener\Exception;
|
||||
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
82
src/Middleware/Rest/GetVisitsMiddleware.php
Normal file
82
src/Middleware/Rest/GetVisitsMiddleware.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
namespace Acelaya\UrlShortener\Middleware\Rest;
|
||||
|
||||
use Acelaya\UrlShortener\Exception\InvalidArgumentException;
|
||||
use Acelaya\UrlShortener\Service\VisitsTracker;
|
||||
use Acelaya\UrlShortener\Service\VisitsTrackerInterface;
|
||||
use Acelaya\UrlShortener\Util\RestUtils;
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
use Zend\Stratigility\MiddlewareInterface;
|
||||
|
||||
class GetVisitsMiddleware implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @var VisitsTrackerInterface
|
||||
*/
|
||||
private $visitsTracker;
|
||||
|
||||
/**
|
||||
* GetVisitsMiddleware constructor.
|
||||
* @param VisitsTrackerInterface|VisitsTracker $visitsTracker
|
||||
*
|
||||
* @Inject({VisitsTracker::class})
|
||||
*/
|
||||
public function __construct(VisitsTrackerInterface $visitsTracker)
|
||||
{
|
||||
$this->visitsTracker = $visitsTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming request and/or response.
|
||||
*
|
||||
* Accepts a server-side request and a response instance, and does
|
||||
* something with them.
|
||||
*
|
||||
* If the response is not complete and/or further processing would not
|
||||
* interfere with the work done in the middleware, or if the middleware
|
||||
* wants to delegate to another process, it can use the `$out` callable
|
||||
* if present.
|
||||
*
|
||||
* If the middleware does not return a value, execution of the current
|
||||
* request is considered complete, and the response instance provided will
|
||||
* be considered the response to return.
|
||||
*
|
||||
* Alternately, the middleware may return a response instance.
|
||||
*
|
||||
* Often, middleware will `return $out();`, with the assumption that a
|
||||
* later middleware will return a response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param null|callable $out
|
||||
* @return null|Response
|
||||
*/
|
||||
public function __invoke(Request $request, Response $response, callable $out = null)
|
||||
{
|
||||
$shortCode = $request->getAttribute('shortCode');
|
||||
|
||||
try {
|
||||
$visits = $this->visitsTracker->info($shortCode);
|
||||
|
||||
return new JsonResponse([
|
||||
'visits' => [
|
||||
'data' => $visits,
|
||||
// 'pagination' => [],
|
||||
]
|
||||
]);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::getRestErrorCodeFromException($e),
|
||||
'message' => sprintf('Provided short code "%s" is invalid', $shortCode),
|
||||
], 400);
|
||||
} catch (\Exception $e) {
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::UNKNOWN_ERROR,
|
||||
'message' => 'Unexpected error occured',
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ namespace Acelaya\UrlShortener\Service;
|
|||
|
||||
use Acelaya\UrlShortener\Entity\ShortUrl;
|
||||
use Acelaya\UrlShortener\Entity\Visit;
|
||||
use Acelaya\UrlShortener\Exception\InvalidArgumentException;
|
||||
use Acelaya\UrlShortener\Exception\InvalidShortCodeException;
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
|
@ -58,4 +60,27 @@ class VisitsTracker implements VisitsTrackerInterface
|
|||
{
|
||||
return isset($array[$key]) ? $array[$key] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the visits on certain shortcode
|
||||
*
|
||||
* @param $shortCode
|
||||
* @return Visit[]
|
||||
*/
|
||||
public function info($shortCode)
|
||||
{
|
||||
/** @var ShortUrl $shortUrl */
|
||||
$shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([
|
||||
'shortCode' => $shortCode,
|
||||
]);
|
||||
if (! isset($shortUrl)) {
|
||||
throw new InvalidArgumentException(sprintf('Short code "%s" not found', $shortCode));
|
||||
}
|
||||
|
||||
return $this->em->getRepository(Visit::class)->findBy([
|
||||
'shortUrl' => $shortUrl,
|
||||
], [
|
||||
'date' => 'DESC'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
namespace Acelaya\UrlShortener\Service;
|
||||
|
||||
use Acelaya\UrlShortener\Entity\Visit;
|
||||
|
||||
interface VisitsTrackerInterface
|
||||
{
|
||||
/**
|
||||
|
@ -10,4 +12,12 @@ interface VisitsTrackerInterface
|
|||
* @param array $visitorData Defaults to global $_SERVER
|
||||
*/
|
||||
public function track($shortCode, array $visitorData = null);
|
||||
|
||||
/**
|
||||
* Returns the visits on certain shortcode
|
||||
*
|
||||
* @param $shortCode
|
||||
* @return Visit[]
|
||||
*/
|
||||
public function info($shortCode);
|
||||
}
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
<?php
|
||||
namespace Acelaya\UrlShortener\Util;
|
||||
|
||||
use Acelaya\UrlShortener\Exception\ExceptionInterface;
|
||||
use Acelaya\UrlShortener\Exception\InvalidShortCodeException;
|
||||
use Acelaya\UrlShortener\Exception\InvalidUrlException;
|
||||
use Acelaya\UrlShortener\Exception;
|
||||
|
||||
class RestUtils
|
||||
{
|
||||
const INVALID_SHORTCODE_ERROR = 'INVALID_SHORTCODE';
|
||||
const INVALID_URL_ERROR = 'INVALID_URL';
|
||||
const INVALID_ARGUMENT_ERROR = 'INVALID_ARGUMEN';
|
||||
const INVALID_ARGUMENT_ERROR = 'INVALID_ARGUMENT';
|
||||
const UNKNOWN_ERROR = 'UNKNOWN_ERROR';
|
||||
|
||||
public static function getRestErrorCodeFromException(ExceptionInterface $e)
|
||||
public static function getRestErrorCodeFromException(Exception\ExceptionInterface $e)
|
||||
{
|
||||
switch (true) {
|
||||
case $e instanceof InvalidShortCodeException:
|
||||
case $e instanceof Exception\InvalidShortCodeException:
|
||||
return self::INVALID_SHORTCODE_ERROR;
|
||||
case $e instanceof InvalidUrlException:
|
||||
case $e instanceof Exception\InvalidUrlException:
|
||||
return self::INVALID_URL_ERROR;
|
||||
case $e instanceof Exception\InvalidArgumentException:
|
||||
return self::INVALID_ARGUMENT_ERROR;
|
||||
default:
|
||||
return self::UNKNOWN_ERROR;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue