mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-25 18:45:27 -06:00
Created Core module
This commit is contained in:
@@ -1,16 +1,15 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\CLI\Command;
|
||||
|
||||
use Acelaya\UrlShortener\Exception\InvalidUrlException;
|
||||
use Acelaya\UrlShortener\Service\UrlShortener;
|
||||
use Acelaya\UrlShortener\Service\UrlShortenerInterface;
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Zend\Diactoros\Uri;
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\CLI\Command;
|
||||
|
||||
use Acelaya\UrlShortener\Service\VisitsTracker;
|
||||
use Acelaya\UrlShortener\Service\VisitsTrackerInterface;
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\CLI\Command;
|
||||
|
||||
use Acelaya\UrlShortener\Service\ShortUrlService;
|
||||
use Acelaya\UrlShortener\Service\ShortUrlServiceInterface;
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter;
|
||||
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\CLI\Command;
|
||||
|
||||
use Acelaya\UrlShortener\Exception\InvalidShortCodeException;
|
||||
use Acelaya\UrlShortener\Service\UrlShortener;
|
||||
use Acelaya\UrlShortener\Service\UrlShortenerInterface;
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
||||
33
module/Common/src/Entity/AbstractEntity.php
Normal file
33
module/Common/src/Entity/AbstractEntity.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
abstract class AbstractEntity
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue(strategy="IDENTITY")
|
||||
* @ORM\Column(name="id", type="bigint", options={"unsigned"=true})
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return $this
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
15
module/Core/config/routes.config.php
Normal file
15
module/Core/config/routes.config.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
use Shlinkio\Shlink\Core\Action\RedirectMiddleware;
|
||||
|
||||
return [
|
||||
|
||||
'routes' => [
|
||||
[
|
||||
'name' => 'long-url-redirect',
|
||||
'path' => '/{shortCode}',
|
||||
'middleware' => RedirectMiddleware::class,
|
||||
'allowed_methods' => ['GET'],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
20
module/Core/config/services.config.php
Normal file
20
module/Core/config/services.config.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
use Acelaya\ZsmAnnotatedServices\Factory\V3\AnnotatedFactory;
|
||||
use Shlinkio\Shlink\Core\Action\RedirectMiddleware;
|
||||
use Shlinkio\Shlink\Core\Service;
|
||||
|
||||
return [
|
||||
|
||||
'services' => [
|
||||
'factories' => [
|
||||
// Services
|
||||
Service\UrlShortener::class => AnnotatedFactory::class,
|
||||
Service\VisitsTracker::class => AnnotatedFactory::class,
|
||||
Service\ShortUrlService::class => AnnotatedFactory::class,
|
||||
|
||||
// Middleware
|
||||
RedirectMiddleware::class => AnnotatedFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
87
module/Core/src/Action/RedirectMiddleware.php
Normal file
87
module/Core/src/Action/RedirectMiddleware.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Action;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Zend\Diactoros\Response\RedirectResponse;
|
||||
use Zend\Stratigility\MiddlewareInterface;
|
||||
|
||||
class RedirectMiddleware implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @var UrlShortenerInterface
|
||||
*/
|
||||
private $urlShortener;
|
||||
/**
|
||||
* @var VisitsTracker|VisitsTrackerInterface
|
||||
*/
|
||||
private $visitTracker;
|
||||
|
||||
/**
|
||||
* RedirectMiddleware constructor.
|
||||
* @param UrlShortenerInterface|UrlShortener $urlShortener
|
||||
* @param VisitsTrackerInterface|VisitsTracker $visitTracker
|
||||
*
|
||||
* @Inject({UrlShortener::class, VisitsTracker::class})
|
||||
*/
|
||||
public function __construct(UrlShortenerInterface $urlShortener, VisitsTrackerInterface $visitTracker)
|
||||
{
|
||||
$this->urlShortener = $urlShortener;
|
||||
$this->visitTracker = $visitTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
$longUrl = $this->urlShortener->shortCodeToUrl($shortCode);
|
||||
|
||||
// If provided shortCode does not belong to a valid long URL, dispatch next middleware, which is 404
|
||||
// middleware
|
||||
if (! isset($longUrl)) {
|
||||
return $out($request, $response);
|
||||
}
|
||||
|
||||
// Track visit to this shortcode
|
||||
$this->visitTracker->track($shortCode);
|
||||
|
||||
// Return a redirect response to the long URL.
|
||||
// Use a temporary redirect to make sure browsers always hit the server for analytics purposes
|
||||
return new RedirectResponse($longUrl);
|
||||
} catch (\Exception $e) {
|
||||
// In case of error, dispatch 404 error
|
||||
return $out($request, $response);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
module/Core/src/ConfigProvider.php
Normal file
13
module/Core/src/ConfigProvider.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core;
|
||||
|
||||
use Zend\Config\Factory;
|
||||
use Zend\Stdlib\Glob;
|
||||
|
||||
class ConfigProvider
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
return Factory::fromFiles(Glob::glob(__DIR__ . '/../config/{,*.}config.php', Glob::GLOB_BRACE));
|
||||
}
|
||||
}
|
||||
103
module/Core/src/Entity/RestToken.php
Normal file
103
module/Core/src/Entity/RestToken.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
|
||||
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
||||
|
||||
/**
|
||||
* Class RestToken
|
||||
* @author
|
||||
* @link
|
||||
*
|
||||
* @ORM\Entity()
|
||||
* @ORM\Table(name="rest_tokens")
|
||||
*/
|
||||
class RestToken extends AbstractEntity
|
||||
{
|
||||
use StringUtilsTrait;
|
||||
|
||||
/**
|
||||
* The default interval is 20 minutes
|
||||
*/
|
||||
const DEFAULT_INTERVAL = 'PT20M';
|
||||
|
||||
/**
|
||||
* @var \DateTime
|
||||
* @ORM\Column(type="datetime", name="expiration_date", nullable=false)
|
||||
*/
|
||||
protected $expirationDate;
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(nullable=false)
|
||||
*/
|
||||
protected $token;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->updateExpiration();
|
||||
$this->setRandomTokenKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getExpirationDate()
|
||||
{
|
||||
return $this->expirationDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $expirationDate
|
||||
* @return $this
|
||||
*/
|
||||
public function setExpirationDate($expirationDate)
|
||||
{
|
||||
$this->expirationDate = $expirationDate;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getToken()
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $token
|
||||
* @return $this
|
||||
*/
|
||||
public function setToken($token)
|
||||
{
|
||||
$this->token = $token;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isExpired()
|
||||
{
|
||||
return new \DateTime() > $this->expirationDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the expiration of the token, setting it to the default interval in the future
|
||||
* @return $this
|
||||
*/
|
||||
public function updateExpiration()
|
||||
{
|
||||
return $this->setExpirationDate((new \DateTime())->add(new \DateInterval(self::DEFAULT_INTERVAL)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a random unique token key for this RestToken
|
||||
* @return RestToken
|
||||
*/
|
||||
public function setRandomTokenKey()
|
||||
{
|
||||
return $this->setToken($this->generateV4Uuid());
|
||||
}
|
||||
}
|
||||
138
module/Core/src/Entity/ShortUrl.php
Normal file
138
module/Core/src/Entity/ShortUrl.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
|
||||
|
||||
/**
|
||||
* Class ShortUrl
|
||||
* @author
|
||||
* @link
|
||||
*
|
||||
* @ORM\Entity(repositoryClass="Shlinkio\Shlink\Core\Repository\ShortUrlRepository")
|
||||
* @ORM\Table(name="short_urls")
|
||||
*/
|
||||
class ShortUrl extends AbstractEntity implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(name="original_url", type="string", nullable=false, length=1024)
|
||||
*/
|
||||
protected $originalUrl;
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(name="short_code", type="string", nullable=false, length=10, unique=true)
|
||||
*/
|
||||
protected $shortCode;
|
||||
/**
|
||||
* @var \DateTime
|
||||
* @ORM\Column(name="date_created", type="datetime")
|
||||
*/
|
||||
protected $dateCreated;
|
||||
/**
|
||||
* @var Collection|Visit[]
|
||||
* @ORM\OneToMany(targetEntity=Visit::class, mappedBy="shortUrl", fetch="EXTRA_LAZY")
|
||||
*/
|
||||
protected $visits;
|
||||
|
||||
/**
|
||||
* ShortUrl constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->setDateCreated(new \DateTime());
|
||||
$this->setVisits(new ArrayCollection());
|
||||
$this->setShortCode('');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOriginalUrl()
|
||||
{
|
||||
return $this->originalUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $originalUrl
|
||||
* @return $this
|
||||
*/
|
||||
public function setOriginalUrl($originalUrl)
|
||||
{
|
||||
$this->originalUrl = (string) $originalUrl;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getShortCode()
|
||||
{
|
||||
return $this->shortCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $shortCode
|
||||
* @return $this
|
||||
*/
|
||||
public function setShortCode($shortCode)
|
||||
{
|
||||
$this->shortCode = $shortCode;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getDateCreated()
|
||||
{
|
||||
return $this->dateCreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $dateCreated
|
||||
* @return $this
|
||||
*/
|
||||
public function setDateCreated($dateCreated)
|
||||
{
|
||||
$this->dateCreated = $dateCreated;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Visit[]|Collection
|
||||
*/
|
||||
public function getVisits()
|
||||
{
|
||||
return $this->visits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Visit[]|Collection $visits
|
||||
* @return $this
|
||||
*/
|
||||
public function setVisits($visits)
|
||||
{
|
||||
$this->visits = $visits;
|
||||
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 [
|
||||
'shortCode' => $this->shortCode,
|
||||
'originalUrl' => $this->originalUrl,
|
||||
'dateCreated' => isset($this->dateCreated) ? $this->dateCreated->format(\DateTime::ISO8601) : null,
|
||||
'visitsCount' => count($this->visits),
|
||||
];
|
||||
}
|
||||
}
|
||||
155
module/Core/src/Entity/Visit.php
Normal file
155
module/Core/src/Entity/Visit.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
|
||||
|
||||
/**
|
||||
* Class Visit
|
||||
* @author
|
||||
* @link
|
||||
*
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="visits")
|
||||
*/
|
||||
class Visit extends AbstractEntity implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="string", length=256, nullable=true)
|
||||
*/
|
||||
protected $referer;
|
||||
/**
|
||||
* @var \DateTime
|
||||
* @ORM\Column(type="datetime", nullable=false)
|
||||
*/
|
||||
protected $date;
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="string", length=256, name="remote_addr", nullable=true)
|
||||
*/
|
||||
protected $remoteAddr;
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="string", length=256, name="user_agent", nullable=true)
|
||||
*/
|
||||
protected $userAgent;
|
||||
/**
|
||||
* @var ShortUrl
|
||||
* @ORM\ManyToOne(targetEntity=ShortUrl::class)
|
||||
* @ORM\JoinColumn(name="short_url_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $shortUrl;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->date = new \DateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getReferer()
|
||||
{
|
||||
return $this->referer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $referer
|
||||
* @return $this
|
||||
*/
|
||||
public function setReferer($referer)
|
||||
{
|
||||
$this->referer = $referer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getDate()
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $date
|
||||
* @return $this
|
||||
*/
|
||||
public function setDate($date)
|
||||
{
|
||||
$this->date = $date;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShortUrl
|
||||
*/
|
||||
public function getShortUrl()
|
||||
{
|
||||
return $this->shortUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ShortUrl $shortUrl
|
||||
* @return $this
|
||||
*/
|
||||
public function setShortUrl($shortUrl)
|
||||
{
|
||||
$this->shortUrl = $shortUrl;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteAddr()
|
||||
{
|
||||
return $this->remoteAddr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $remoteAddr
|
||||
* @return $this
|
||||
*/
|
||||
public function setRemoteAddr($remoteAddr)
|
||||
{
|
||||
$this->remoteAddr = $remoteAddr;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUserAgent()
|
||||
{
|
||||
return $this->userAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $userAgent
|
||||
* @return $this
|
||||
*/
|
||||
public function setUserAgent($userAgent)
|
||||
{
|
||||
$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
module/Core/src/Exception/ExceptionInterface.php
Normal file
6
module/Core/src/Exception/ExceptionInterface.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Exception;
|
||||
|
||||
interface ExceptionInterface
|
||||
{
|
||||
}
|
||||
6
module/Core/src/Exception/InvalidArgumentException.php
Normal file
6
module/Core/src/Exception/InvalidArgumentException.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Exception;
|
||||
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
15
module/Core/src/Exception/InvalidShortCodeException.php
Normal file
15
module/Core/src/Exception/InvalidShortCodeException.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Exception;
|
||||
|
||||
class InvalidShortCodeException extends RuntimeException
|
||||
{
|
||||
public static function fromShortCode($shortCode, $charSet, \Exception $previous = null)
|
||||
{
|
||||
$code = isset($previous) ? $previous->getCode() : -1;
|
||||
return new static(
|
||||
sprintf('Provided short code "%s" does not match the char set "%s"', $shortCode, $charSet),
|
||||
$code,
|
||||
$previous
|
||||
);
|
||||
}
|
||||
}
|
||||
11
module/Core/src/Exception/InvalidUrlException.php
Normal file
11
module/Core/src/Exception/InvalidUrlException.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Exception;
|
||||
|
||||
class InvalidUrlException extends RuntimeException
|
||||
{
|
||||
public static function fromUrl($url, \Exception $previous = null)
|
||||
{
|
||||
$code = isset($previous) ? $previous->getCode() : -1;
|
||||
return new static(sprintf('Provided URL "%s" is not an exisitng and valid URL', $url), $code, $previous);
|
||||
}
|
||||
}
|
||||
6
module/Core/src/Exception/RuntimeException.php
Normal file
6
module/Core/src/Exception/RuntimeException.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Exception;
|
||||
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
61
module/Core/src/Repository/ShortUrlRepository.php
Normal file
61
module/Core/src/Repository/ShortUrlRepository.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Repository;
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
|
||||
class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
* @param string|null $searchTerm
|
||||
* @param string|array|null $orderBy
|
||||
* @return ShortUrl[]
|
||||
*/
|
||||
public function findList($limit = null, $offset = null, $searchTerm = null, $orderBy = null)
|
||||
{
|
||||
$qb = $this->createQueryBuilder('s');
|
||||
|
||||
if (isset($limit)) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
if (isset($offset)) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
if (isset($searchTerm)) {
|
||||
// TODO
|
||||
}
|
||||
if (isset($orderBy)) {
|
||||
if (is_string($orderBy)) {
|
||||
$qb->orderBy($orderBy);
|
||||
} elseif (is_array($orderBy)) {
|
||||
$key = key($orderBy);
|
||||
$qb->orderBy($key, $orderBy[$key]);
|
||||
}
|
||||
} else {
|
||||
$qb->orderBy('s.dateCreated');
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of elements in a list using provided filtering data
|
||||
*
|
||||
* @param null $searchTerm
|
||||
* @return int
|
||||
*/
|
||||
public function countList($searchTerm = null)
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->select('COUNT(s)')
|
||||
->from(ShortUrl::class, 's');
|
||||
|
||||
if (isset($searchTerm)) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Repository;
|
||||
|
||||
use Doctrine\Common\Persistence\ObjectRepository;
|
||||
use Shlinkio\Shlink\Common\Repository\PaginableRepositoryInterface;
|
||||
|
||||
interface ShortUrlRepositoryInterface extends ObjectRepository, PaginableRepositoryInterface
|
||||
{
|
||||
}
|
||||
43
module/Core/src/Service/ShortUrlService.php
Normal file
43
module/Core/src/Service/ShortUrlService.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Service;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
||||
use Zend\Paginator\Paginator;
|
||||
|
||||
class ShortUrlService implements ShortUrlServiceInterface
|
||||
{
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* ShortUrlService constructor.
|
||||
* @param EntityManagerInterface $em
|
||||
*
|
||||
* @Inject({"em"})
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $page
|
||||
* @return Paginator|ShortUrl[]
|
||||
*/
|
||||
public function listShortUrls($page = 1)
|
||||
{
|
||||
/** @var ShortUrlRepository $repo */
|
||||
$repo = $this->em->getRepository(ShortUrl::class);
|
||||
$paginator = new Paginator(new PaginableRepositoryAdapter($repo));
|
||||
$paginator->setItemCountPerPage(PaginableRepositoryAdapter::ITEMS_PER_PAGE)
|
||||
->setCurrentPageNumber($page);
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
}
|
||||
14
module/Core/src/Service/ShortUrlServiceInterface.php
Normal file
14
module/Core/src/Service/ShortUrlServiceInterface.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Service;
|
||||
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Zend\Paginator\Paginator;
|
||||
|
||||
interface ShortUrlServiceInterface
|
||||
{
|
||||
/**
|
||||
* @param int $page
|
||||
* @return ShortUrl[]|Paginator
|
||||
*/
|
||||
public function listShortUrls($page = 1);
|
||||
}
|
||||
154
module/Core/src/Service/UrlShortener.php
Normal file
154
module/Core/src/Service/UrlShortener.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Service;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
use Shlinkio\Shlink\Core\Exception\RuntimeException;
|
||||
|
||||
class UrlShortener implements UrlShortenerInterface
|
||||
{
|
||||
const DEFAULT_CHARS = '123456789bcdfghjkmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ';
|
||||
|
||||
/**
|
||||
* @var ClientInterface
|
||||
*/
|
||||
private $httpClient;
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $chars;
|
||||
|
||||
/**
|
||||
* UrlShortener constructor.
|
||||
* @param ClientInterface $httpClient
|
||||
* @param EntityManagerInterface $em
|
||||
* @param string $chars
|
||||
*
|
||||
* @Inject({"httpClient", "em", "config.url_shortener.shortcode_chars"})
|
||||
*/
|
||||
public function __construct(
|
||||
ClientInterface $httpClient,
|
||||
EntityManagerInterface $em,
|
||||
$chars = self::DEFAULT_CHARS
|
||||
) {
|
||||
$this->httpClient = $httpClient;
|
||||
$this->em = $em;
|
||||
$this->chars = empty($chars) ? self::DEFAULT_CHARS : $chars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and persists a unique shortcode generated for provided url
|
||||
*
|
||||
* @param UriInterface $url
|
||||
* @return string
|
||||
* @throws InvalidUrlException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function urlToShortCode(UriInterface $url)
|
||||
{
|
||||
// If the url already exists in the database, just return its short code
|
||||
$shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([
|
||||
'originalUrl' => $url
|
||||
]);
|
||||
if (isset($shortUrl)) {
|
||||
return $shortUrl->getShortCode();
|
||||
}
|
||||
|
||||
// Check that the URL exists
|
||||
$this->checkUrlExists($url);
|
||||
|
||||
// Transactionally insert the short url, then generate the short code and finally update the short code
|
||||
try {
|
||||
$this->em->beginTransaction();
|
||||
|
||||
// First, create the short URL with an empty short code
|
||||
$shortUrl = new ShortUrl();
|
||||
$shortUrl->setOriginalUrl($url);
|
||||
$this->em->persist($shortUrl);
|
||||
$this->em->flush();
|
||||
|
||||
// Generate the short code and persist it
|
||||
$shortCode = $this->convertAutoincrementIdToShortCode($shortUrl->getId());
|
||||
$shortUrl->setShortCode($shortCode);
|
||||
$this->em->flush();
|
||||
|
||||
$this->em->commit();
|
||||
return $shortCode;
|
||||
} catch (ORMException $e) {
|
||||
if ($this->em->getConnection()->isTransactionActive()) {
|
||||
$this->em->rollback();
|
||||
$this->em->close();
|
||||
}
|
||||
|
||||
throw new RuntimeException('An error occured while persisting the short URL', -1, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to perform a GET request to provided url, returning true on success and false on failure
|
||||
*
|
||||
* @param UriInterface $url
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkUrlExists(UriInterface $url)
|
||||
{
|
||||
try {
|
||||
$this->httpClient->request('GET', $url);
|
||||
} catch (GuzzleException $e) {
|
||||
throw InvalidUrlException::fromUrl($url, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the unique shortcode for an autoincrement ID
|
||||
*
|
||||
* @param int $id
|
||||
* @return string
|
||||
*/
|
||||
protected function convertAutoincrementIdToShortCode($id)
|
||||
{
|
||||
$id = intval($id) + 200000; // Increment the Id so that the generated shortcode is not too short
|
||||
$length = strlen($this->chars);
|
||||
$code = '';
|
||||
|
||||
while ($id > 0) {
|
||||
// Determine the value of the next higher character in the short code and prepend it
|
||||
$code = $this->chars[intval(fmod($id, $length))] . $code;
|
||||
$id = floor($id / $length);
|
||||
}
|
||||
|
||||
return $this->chars[intval($id)] . $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find the mapped URL for provided short code. Returns null if not found
|
||||
*
|
||||
* @param string $shortCode
|
||||
* @return string|null
|
||||
* @throws InvalidShortCodeException
|
||||
*/
|
||||
public function shortCodeToUrl($shortCode)
|
||||
{
|
||||
// Validate short code format
|
||||
if (! preg_match('|[' . $this->chars . "]+|", $shortCode)) {
|
||||
throw InvalidShortCodeException::fromShortCode($shortCode, $this->chars);
|
||||
}
|
||||
|
||||
/** @var ShortUrl $shortUrl */
|
||||
$shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([
|
||||
'shortCode' => $shortCode,
|
||||
]);
|
||||
return isset($shortUrl) ? $shortUrl->getOriginalUrl() : null;
|
||||
}
|
||||
}
|
||||
29
module/Core/src/Service/UrlShortenerInterface.php
Normal file
29
module/Core/src/Service/UrlShortenerInterface.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Service;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
use Shlinkio\Shlink\Core\Exception\RuntimeException;
|
||||
|
||||
interface UrlShortenerInterface
|
||||
{
|
||||
/**
|
||||
* Creates and persists a unique shortcode generated for provided url
|
||||
*
|
||||
* @param UriInterface $url
|
||||
* @return string
|
||||
* @throws InvalidUrlException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function urlToShortCode(UriInterface $url);
|
||||
|
||||
/**
|
||||
* Tries to find the mapped URL for provided short code. Returns null if not found
|
||||
*
|
||||
* @param string $shortCode
|
||||
* @return string|null
|
||||
* @throws InvalidShortCodeException
|
||||
*/
|
||||
public function shortCodeToUrl($shortCode);
|
||||
}
|
||||
86
module/Core/src/Service/VisitsTracker.php
Normal file
86
module/Core/src/Service/VisitsTracker.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Service;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
|
||||
use Zend\Paginator\Paginator;
|
||||
|
||||
class VisitsTracker implements VisitsTrackerInterface
|
||||
{
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* VisitsTracker constructor.
|
||||
* @param EntityManagerInterface $em
|
||||
*
|
||||
* @Inject({"em"})
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks a new visit to provided short code, using an array of data to look up information
|
||||
*
|
||||
* @param string $shortCode
|
||||
* @param array $visitorData Defaults to global $_SERVER
|
||||
*/
|
||||
public function track($shortCode, array $visitorData = null)
|
||||
{
|
||||
$visitorData = $visitorData ?: $_SERVER;
|
||||
|
||||
/** @var ShortUrl $shortUrl */
|
||||
$shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([
|
||||
'shortCode' => $shortCode,
|
||||
]);
|
||||
|
||||
$visit = new Visit();
|
||||
$visit->setShortUrl($shortUrl)
|
||||
->setUserAgent($this->getArrayValue($visitorData, 'HTTP_USER_AGENT'))
|
||||
->setReferer($this->getArrayValue($visitorData, 'HTTP_REFERER'))
|
||||
->setRemoteAddr($this->getArrayValue($visitorData, 'REMOTE_ADDR'));
|
||||
$this->em->persist($visit);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $array
|
||||
* @param $key
|
||||
* @param null $default
|
||||
* @return mixed|null
|
||||
*/
|
||||
protected function getArrayValue(array $array, $key, $default = null)
|
||||
{
|
||||
return isset($array[$key]) ? $array[$key] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the visits on certain shortcode
|
||||
*
|
||||
* @param $shortCode
|
||||
* @return Paginator|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'
|
||||
]);
|
||||
}
|
||||
}
|
||||
24
module/Core/src/Service/VisitsTrackerInterface.php
Normal file
24
module/Core/src/Service/VisitsTrackerInterface.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Core\Service;
|
||||
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Zend\Paginator\Paginator;
|
||||
|
||||
interface VisitsTrackerInterface
|
||||
{
|
||||
/**
|
||||
* Tracks a new visit to provided short code, using an array of data to look up information
|
||||
*
|
||||
* @param string $shortCode
|
||||
* @param array $visitorData Defaults to global $_SERVER
|
||||
*/
|
||||
public function track($shortCode, array $visitorData = null);
|
||||
|
||||
/**
|
||||
* Returns the visits on certain shortcode
|
||||
*
|
||||
* @param $shortCode
|
||||
* @return Paginator|Visit[]
|
||||
*/
|
||||
public function info($shortCode);
|
||||
}
|
||||
49
module/Core/test/Service/ShortUrlServiceTest.php
Normal file
49
module/Core/test/Service/ShortUrlServiceTest.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Core\Service;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
||||
|
||||
class ShortUrlServiceTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ShortUrlService
|
||||
*/
|
||||
protected $service;
|
||||
/**
|
||||
* @var ObjectProphecy|EntityManagerInterface
|
||||
*/
|
||||
protected $em;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->em = $this->prophesize(EntityManagerInterface::class);
|
||||
$this->service = new ShortUrlService($this->em->reveal());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function listedUrlsAreReturnedFromEntityManager()
|
||||
{
|
||||
$list = [
|
||||
new ShortUrl(),
|
||||
new ShortUrl(),
|
||||
new ShortUrl(),
|
||||
new ShortUrl(),
|
||||
];
|
||||
|
||||
$repo = $this->prophesize(ShortUrlRepository::class);
|
||||
$repo->findList(Argument::cetera())->willReturn($list)->shouldBeCalledTimes(1);
|
||||
$repo->countList(Argument::cetera())->willReturn(count($list))->shouldBeCalledTimes(1);
|
||||
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
|
||||
|
||||
$list = $this->service->listShortUrls();
|
||||
$this->assertEquals(4, $list->getCurrentItemCount());
|
||||
}
|
||||
}
|
||||
135
module/Core/test/Service/UrlShortenerTest.php
Normal file
135
module/Core/test/Service/UrlShortenerTest.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Core\Service;
|
||||
|
||||
use Doctrine\Common\Persistence\ObjectRepository;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Zend\Diactoros\Uri;
|
||||
|
||||
class UrlShortenerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var UrlShortener
|
||||
*/
|
||||
protected $urlShortener;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $em;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $httpClient;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->httpClient = $this->prophesize(ClientInterface::class);
|
||||
|
||||
$this->em = $this->prophesize(EntityManagerInterface::class);
|
||||
$conn = $this->prophesize(Connection::class);
|
||||
$conn->isTransactionActive()->willReturn(false);
|
||||
$this->em->getConnection()->willReturn($conn->reveal());
|
||||
$this->em->flush()->willReturn(null);
|
||||
$this->em->commit()->willReturn(null);
|
||||
$this->em->beginTransaction()->willReturn(null);
|
||||
$this->em->persist(Argument::any())->will(function ($arguments) {
|
||||
/** @var ShortUrl $shortUrl */
|
||||
$shortUrl = $arguments[0];
|
||||
$shortUrl->setId(10);
|
||||
});
|
||||
$repo = $this->prophesize(ObjectRepository::class);
|
||||
$repo->findOneBy(Argument::any())->willReturn(null);
|
||||
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
|
||||
|
||||
$this->urlShortener = new UrlShortener($this->httpClient->reveal(), $this->em->reveal());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function urlIsProperlyShortened()
|
||||
{
|
||||
// 10 -> 12C1c
|
||||
$shortCode = $this->urlShortener->urlToShortCode(new Uri('http://foobar.com/12345/hello?foo=bar'));
|
||||
$this->assertEquals('12C1c', $shortCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @expectedException \Acelaya\UrlShortener\Exception\RuntimeException
|
||||
*/
|
||||
public function exceptionIsThrownWhenOrmThrowsException()
|
||||
{
|
||||
$conn = $this->prophesize(Connection::class);
|
||||
$conn->isTransactionActive()->willReturn(true);
|
||||
$this->em->getConnection()->willReturn($conn->reveal());
|
||||
$this->em->rollback()->shouldBeCalledTimes(1);
|
||||
$this->em->close()->shouldBeCalledTimes(1);
|
||||
|
||||
$this->em->flush()->willThrow(new ORMException());
|
||||
$this->urlShortener->urlToShortCode(new Uri('http://foobar.com/12345/hello?foo=bar'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @expectedException \Acelaya\UrlShortener\Exception\InvalidUrlException
|
||||
*/
|
||||
public function exceptionIsThrownWhenUrlDoesNotExist()
|
||||
{
|
||||
$this->httpClient->request(Argument::cetera())->willThrow(
|
||||
new ClientException('', $this->prophesize(Request::class)->reveal())
|
||||
);
|
||||
$this->urlShortener->urlToShortCode(new Uri('http://foobar.com/12345/hello?foo=bar'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function whenShortUrlExistsItsShortcodeIsReturned()
|
||||
{
|
||||
$shortUrl = new ShortUrl();
|
||||
$shortUrl->setShortCode('expected_shortcode');
|
||||
$repo = $this->prophesize(ObjectRepository::class);
|
||||
$repo->findOneBy(Argument::any())->willReturn($shortUrl);
|
||||
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
|
||||
|
||||
$shortCode = $this->urlShortener->urlToShortCode(new Uri('http://foobar.com/12345/hello?foo=bar'));
|
||||
$this->assertEquals($shortUrl->getShortCode(), $shortCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function shortCodeIsProperlyParsed()
|
||||
{
|
||||
// 12C1c -> 10
|
||||
$shortUrl = new ShortUrl();
|
||||
$shortUrl->setShortCode('12C1c')
|
||||
->setOriginalUrl('expected_url');
|
||||
|
||||
$repo = $this->prophesize(ObjectRepository::class);
|
||||
$repo->findOneBy(['shortCode' => '12C1c'])->willReturn($shortUrl);
|
||||
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
|
||||
|
||||
$url = $this->urlShortener->shortCodeToUrl('12C1c');
|
||||
$this->assertEquals($shortUrl->getOriginalUrl(), $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @expectedException \Acelaya\UrlShortener\Exception\InvalidShortCodeException
|
||||
*/
|
||||
public function invalidCharSetThrowsException()
|
||||
{
|
||||
$this->urlShortener->shortCodeToUrl('&/(');
|
||||
}
|
||||
}
|
||||
30
module/Core/test/Service/VisitsTrackerTest.php
Normal file
30
module/Core/test/Service/VisitsTrackerTest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Core\Service;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
|
||||
class VisitsTrackerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function trackPersistsVisit()
|
||||
{
|
||||
$shortCode = '123ABC';
|
||||
$repo = $this->prophesize(EntityRepository::class);
|
||||
$repo->findOneBy(['shortCode' => $shortCode])->willReturn(new ShortUrl());
|
||||
|
||||
$em = $this->prophesize(EntityManager::class);
|
||||
$em->getRepository(ShortUrl::class)->willReturn($repo->reveal())->shouldBeCalledTimes(1);
|
||||
$em->persist(Argument::any())->shouldBeCalledTimes(1);
|
||||
$em->flush()->shouldBeCalledTimes(1);
|
||||
|
||||
$visitsTracker = new VisitsTracker($em->reveal());
|
||||
$visitsTracker->track($shortCode);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
|
||||
use Acelaya\UrlShortener\Exception\InvalidUrlException;
|
||||
use Acelaya\UrlShortener\Service\UrlShortener;
|
||||
use Acelaya\UrlShortener\Service\UrlShortenerInterface;
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
use Zend\Diactoros\Uri;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
|
||||
use Acelaya\UrlShortener\Exception\InvalidArgumentException;
|
||||
use Acelaya\UrlShortener\Service\VisitsTracker;
|
||||
use Acelaya\UrlShortener\Service\VisitsTrackerInterface;
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
|
||||
use Acelaya\UrlShortener\Service\ShortUrlService;
|
||||
use Acelaya\UrlShortener\Service\ShortUrlServiceInterface;
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
|
||||
use Acelaya\UrlShortener\Exception\InvalidShortCodeException;
|
||||
use Acelaya\UrlShortener\Service\UrlShortener;
|
||||
use Acelaya\UrlShortener\Service\UrlShortenerInterface;
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Rest\Exception;
|
||||
|
||||
use Acelaya\UrlShortener\Exception\ExceptionInterface;
|
||||
use Shlinkio\Shlink\Core\Exception\ExceptionInterface;
|
||||
|
||||
class AuthenticationException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Rest\Middleware;
|
||||
|
||||
use Acelaya\UrlShortener\Exception\InvalidArgumentException;
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Rest\Service\RestTokenService;
|
||||
use Shlinkio\Shlink\Rest\Service\RestTokenServiceInterface;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Rest\Service;
|
||||
|
||||
use Acelaya\UrlShortener\Entity\RestToken;
|
||||
use Acelaya\UrlShortener\Exception\InvalidArgumentException;
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Shlinkio\Shlink\Core\Entity\RestToken;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Rest\Exception\AuthenticationException;
|
||||
|
||||
class RestTokenService implements RestTokenServiceInterface
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Rest\Service;
|
||||
|
||||
use Acelaya\UrlShortener\Entity\RestToken;
|
||||
use Acelaya\UrlShortener\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Core\Entity\RestToken;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Rest\Exception\AuthenticationException;
|
||||
|
||||
interface RestTokenServiceInterface
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Rest\Util;
|
||||
|
||||
use Acelaya\UrlShortener\Exception as Core;
|
||||
use Shlinkio\Shlink\Core\Exception as Core;
|
||||
use Shlinkio\Shlink\Rest\Exception as Rest;
|
||||
|
||||
class RestUtils
|
||||
|
||||
Reference in New Issue
Block a user