From 8a7d5a499ee2ca137cf06dafe5298619a0cfbe3f Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Mon, 4 Jul 2016 08:33:22 +0200
Subject: [PATCH 01/18] Dropped unused middleware

---
 src/Middleware/CliParamsMiddleware.php | 71 --------------------------
 1 file changed, 71 deletions(-)
 delete mode 100644 src/Middleware/CliParamsMiddleware.php

diff --git a/src/Middleware/CliParamsMiddleware.php b/src/Middleware/CliParamsMiddleware.php
deleted file mode 100644
index 683a8e86..00000000
--- a/src/Middleware/CliParamsMiddleware.php
+++ /dev/null
@@ -1,71 +0,0 @@
-<?php
-namespace Acelaya\UrlShortener\Middleware;
-
-use Psr\Http\Message\ResponseInterface as Response;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Zend\Expressive\Router\RouteResult;
-use Zend\Stratigility\MiddlewareInterface;
-
-class CliParamsMiddleware implements MiddlewareInterface
-{
-    /**
-     * @var array
-     */
-    private $argv;
-    /**
-     * @var
-     */
-    private $currentSapi;
-
-    public function __construct(array $argv, $currentSapi)
-    {
-        $this->argv = $argv;
-        $this->currentSapi = $currentSapi;
-    }
-
-    /**
-     * 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)
-    {
-        // When not in CLI, just call next middleware
-        if ($this->currentSapi !== 'cli') {
-            return $out($request, $response);
-        }
-
-        /** @var RouteResult $routeResult */
-        $routeResult = $request->getAttribute(RouteResult::class);
-        if (! $routeResult->isSuccess()) {
-            return $out($request, $response);
-        }
-
-        // Inject ARGV params as request attributes
-        if ($routeResult->getMatchedRouteName() === 'cli-generate-shortcode') {
-            $request = $request->withAttribute('longUrl', isset($this->argv[2]) ? $this->argv[2] : null);
-        }
-
-        return $out($request, $response);
-    }
-}

From 1fbefbbd15bf365416fe522b53ccdd5493d7b07d Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Sun, 12 Jun 2016 17:51:30 +0200
Subject: [PATCH 02/18] Created shortcode creation rest endpoint

---
 composer.json                                 |  4 +-
 config/autoload/routes.global.php             |  9 ++
 .../Rest/CreateShortcodeMiddleware.php        | 98 +++++++++++++++++++
 src/Util/RestUtils.php                        | 26 +++++
 4 files changed, 135 insertions(+), 2 deletions(-)
 create mode 100644 src/Middleware/Rest/CreateShortcodeMiddleware.php
 create mode 100644 src/Util/RestUtils.php

diff --git a/composer.json b/composer.json
index 922e9067..15f92bee 100644
--- a/composer.json
+++ b/composer.json
@@ -11,7 +11,7 @@
         }
     ],
     "require": {
-        "php": "^5.5 || ^7.0",
+        "php": "^5.6 || ^7.0",
         "zendframework/zend-expressive": "^1.0",
         "zendframework/zend-expressive-helpers": "^2.0",
         "zendframework/zend-expressive-fastroute": "^1.1",
@@ -24,7 +24,7 @@
         "symfony/console": "^3.0"
     },
     "require-dev": {
-        "phpunit/phpunit": "^4.8",
+        "phpunit/phpunit": "^5.0",
         "squizlabs/php_codesniffer": "^2.3",
         "roave/security-advisories": "dev-master",
         "filp/whoops": "^2.0",
diff --git a/config/autoload/routes.global.php b/config/autoload/routes.global.php
index 40a3d20b..7f16c641 100644
--- a/config/autoload/routes.global.php
+++ b/config/autoload/routes.global.php
@@ -1,5 +1,6 @@
 <?php
 use Acelaya\UrlShortener\Middleware\Routable;
+use Acelaya\UrlShortener\Middleware\Rest;
 
 return [
 
@@ -10,6 +11,14 @@ return [
             'middleware' => Routable\RedirectMiddleware::class,
             'allowed_methods' => ['GET'],
         ],
+
+        // Rest
+        [
+            'name' => 'rest-create-shortcode',
+            'path' => '/rest/short-code',
+            'middleware' => Rest\CreateShortcodeMiddleware::class,
+            'allowed_methods' => ['POST'],
+        ],
     ],
 
 ];
diff --git a/src/Middleware/Rest/CreateShortcodeMiddleware.php b/src/Middleware/Rest/CreateShortcodeMiddleware.php
new file mode 100644
index 00000000..21ab4379
--- /dev/null
+++ b/src/Middleware/Rest/CreateShortcodeMiddleware.php
@@ -0,0 +1,98 @@
+<?php
+namespace Acelaya\UrlShortener\Middleware\Rest;
+
+use Acelaya\UrlShortener\Exception\InvalidUrlException;
+use Acelaya\UrlShortener\Service\UrlShortener;
+use Acelaya\UrlShortener\Service\UrlShortenerInterface;
+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\Diactoros\Uri;
+use Zend\Stratigility\MiddlewareInterface;
+
+class CreateShortcodeMiddleware implements MiddlewareInterface
+{
+    /**
+     * @var UrlShortener|UrlShortenerInterface
+     */
+    private $urlShortener;
+    /**
+     * @var array
+     */
+    private $domainConfig;
+
+    /**
+     * GenerateShortcodeMiddleware constructor.
+     *
+     * @param UrlShortenerInterface|UrlShortener $urlShortener
+     * @param array $domainConfig
+     *
+     * @Inject({UrlShortener::class, "config.url_shortener.domain"})
+     */
+    public function __construct(UrlShortenerInterface $urlShortener, array $domainConfig)
+    {
+        $this->urlShortener = $urlShortener;
+        $this->domainConfig = $domainConfig;
+    }
+
+    /**
+     * 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)
+    {
+        $postData = $request->getParsedBody();
+        if (! isset($postData['longUrl'])) {
+            return new JsonResponse([
+                'error' => RestUtils::INVALID_ARGUMENT_ERROR,
+                'message' => 'A URL was not provided',
+            ], 400);
+        }
+        $longUrl = $postData['longUrl'];
+
+        try {
+            $shortcode = $this->urlShortener->urlToShortCode(new Uri($longUrl));
+            $shortUrl = (new Uri())->withPath($shortcode)
+                                   ->withScheme($this->domainConfig['schema'])
+                                   ->withHost($this->domainConfig['hostname']);
+
+            return new JsonResponse([
+                'longUrl' => $longUrl,
+                'shortUrl' => $shortUrl->__toString(),
+            ]);
+        } catch (InvalidUrlException $e) {
+            return new JsonResponse([
+                'error' => RestUtils::getRestErrorCodeFromException($e),
+                'message' => sprintf('Provided URL "%s" is invalid. Try with a different one.', $longUrl),
+            ], 400);
+        } catch (\Exception $e) {
+            return new JsonResponse([
+                'error' => RestUtils::UNKNOWN_ERROR,
+                'message' => sprintf('Provided URL "%s" is invalid. Try with a different one.', $longUrl),
+            ], 500);
+        }
+    }
+}
diff --git a/src/Util/RestUtils.php b/src/Util/RestUtils.php
new file mode 100644
index 00000000..d4c7179d
--- /dev/null
+++ b/src/Util/RestUtils.php
@@ -0,0 +1,26 @@
+<?php
+namespace Acelaya\UrlShortener\Util;
+
+use Acelaya\UrlShortener\Exception\ExceptionInterface;
+use Acelaya\UrlShortener\Exception\InvalidShortCodeException;
+use Acelaya\UrlShortener\Exception\InvalidUrlException;
+
+class RestUtils
+{
+    const INVALID_SHORTCODE_ERROR = 'INVALID_SHORTCODE';
+    const INVALID_URL_ERROR = 'INVALID_URL';
+    const INVALID_ARGUMENT_ERROR = 'INVALID_ARGUMEN';
+    const UNKNOWN_ERROR = 'UNKNOWN_ERROR';
+
+    public static function getRestErrorCodeFromException(ExceptionInterface $e)
+    {
+        switch (true) {
+            case $e instanceof InvalidShortCodeException:
+                return self::INVALID_SHORTCODE_ERROR;
+            case $e instanceof InvalidUrlException:
+                return self::INVALID_URL_ERROR;
+            default:
+                return self::UNKNOWN_ERROR;
+        }
+    }
+}

From ab8ccd7df11c64a5abe4f103d0677243bba6f233 Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Sun, 12 Jun 2016 21:31:28 +0200
Subject: [PATCH 03/18] Added get URL rest endpoint

---
 config/autoload/routes.global.php             |  6 ++
 config/autoload/services.global.php           |  2 +
 .../Rest/CreateShortcodeMiddleware.php        |  2 +-
 src/Middleware/Rest/ResolveUrlMiddleware.php  | 85 +++++++++++++++++++
 4 files changed, 94 insertions(+), 1 deletion(-)
 create mode 100644 src/Middleware/Rest/ResolveUrlMiddleware.php

diff --git a/config/autoload/routes.global.php b/config/autoload/routes.global.php
index 7f16c641..60da6d2a 100644
--- a/config/autoload/routes.global.php
+++ b/config/autoload/routes.global.php
@@ -19,6 +19,12 @@ return [
             'middleware' => Rest\CreateShortcodeMiddleware::class,
             'allowed_methods' => ['POST'],
         ],
+        [
+            'name' => 'rest-resolve-url',
+            'path' => '/rest/short-code/{shortCode}',
+            'middleware' => Rest\ResolveUrlMiddleware::class,
+            'allowed_methods' => ['GET'],
+        ],
     ],
 
 ];
diff --git a/config/autoload/services.global.php b/config/autoload/services.global.php
index 617242b5..46890750 100644
--- a/config/autoload/services.global.php
+++ b/config/autoload/services.global.php
@@ -44,6 +44,8 @@ return [
 
             // Middleware
             Middleware\Routable\RedirectMiddleware::class => AnnotatedFactory::class,
+            Middleware\Rest\CreateShortcodeMiddleware::class => AnnotatedFactory::class,
+            Middleware\Rest\ResolveUrlMiddleware::class => AnnotatedFactory::class,
         ],
         'aliases' => [
             'em' => EntityManager::class,
diff --git a/src/Middleware/Rest/CreateShortcodeMiddleware.php b/src/Middleware/Rest/CreateShortcodeMiddleware.php
index 21ab4379..1e723d48 100644
--- a/src/Middleware/Rest/CreateShortcodeMiddleware.php
+++ b/src/Middleware/Rest/CreateShortcodeMiddleware.php
@@ -91,7 +91,7 @@ class CreateShortcodeMiddleware implements MiddlewareInterface
         } catch (\Exception $e) {
             return new JsonResponse([
                 'error' => RestUtils::UNKNOWN_ERROR,
-                'message' => sprintf('Provided URL "%s" is invalid. Try with a different one.', $longUrl),
+                'message' => 'Unexpected error occured',
             ], 500);
         }
     }
diff --git a/src/Middleware/Rest/ResolveUrlMiddleware.php b/src/Middleware/Rest/ResolveUrlMiddleware.php
new file mode 100644
index 00000000..1beee164
--- /dev/null
+++ b/src/Middleware/Rest/ResolveUrlMiddleware.php
@@ -0,0 +1,85 @@
+<?php
+namespace Acelaya\UrlShortener\Middleware\Rest;
+
+use Acelaya\UrlShortener\Exception\InvalidShortCodeException;
+use Acelaya\UrlShortener\Service\UrlShortener;
+use Acelaya\UrlShortener\Service\UrlShortenerInterface;
+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 ResolveUrlMiddleware implements MiddlewareInterface
+{
+    /**
+     * @var UrlShortenerInterface
+     */
+    private $urlShortener;
+
+    /**
+     * ResolveUrlMiddleware constructor.
+     * @param UrlShortenerInterface|UrlShortener $urlShortener
+     *
+     * @Inject({UrlShortener::class})
+     */
+    public function __construct(UrlShortenerInterface $urlShortener)
+    {
+        $this->urlShortener = $urlShortener;
+    }
+
+    /**
+     * 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 (! isset($longUrl)) {
+                return new JsonResponse([
+                    'error' => RestUtils::INVALID_ARGUMENT_ERROR,
+                    'message' => sprintf('No URL found for shortcode "%s"', $shortCode),
+                ], 400);
+            }
+
+            return new JsonResponse([
+                'longUrl' => $longUrl,
+            ]);
+        } catch (InvalidShortCodeException $e) {
+            return new JsonResponse([
+                'error' => RestUtils::getRestErrorCodeFromException($e),
+                'message' => sprintf('Provided short code "%s" has an invalid format', $shortCode),
+            ], 400);
+        } catch (\Exception $e) {
+            return new JsonResponse([
+                'error' => RestUtils::UNKNOWN_ERROR,
+                'message' => 'Unexpected error occured',
+            ], 500);
+        }
+    }
+}

From 305df3a95ba0e6386cad17cfa39edc40dc644da8 Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Sun, 12 Jun 2016 21:51:06 +0200
Subject: [PATCH 04/18] Implemented rest endpoint to return shortcode visits

---
 config/autoload/routes.global.php           | 10 ++-
 config/autoload/services.global.php         |  2 +
 src/Entity/Visit.php                        | 19 ++++-
 src/Exception/InvalidArgumentException.php  |  6 ++
 src/Middleware/Rest/GetVisitsMiddleware.php | 82 +++++++++++++++++++++
 src/Service/VisitsTracker.php               | 25 +++++++
 src/Service/VisitsTrackerInterface.php      | 10 +++
 src/Util/RestUtils.php                      | 14 ++--
 8 files changed, 158 insertions(+), 10 deletions(-)
 create mode 100644 src/Exception/InvalidArgumentException.php
 create mode 100644 src/Middleware/Rest/GetVisitsMiddleware.php

diff --git a/config/autoload/routes.global.php b/config/autoload/routes.global.php
index 60da6d2a..680a195d 100644
--- a/config/autoload/routes.global.php
+++ b/config/autoload/routes.global.php
@@ -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'],
+        ],
     ],
 
 ];
diff --git a/config/autoload/services.global.php b/config/autoload/services.global.php
index 46890750..8dada5e8 100644
--- a/config/autoload/services.global.php
+++ b/config/autoload/services.global.php
@@ -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,
         ]
     ],
 
diff --git a/src/Entity/Visit.php b/src/Entity/Visit.php
index 2e8fad6f..42aebec5 100644
--- a/src/Entity/Visit.php
+++ b/src/Entity/Visit.php
@@ -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,
+        ];
+    }
 }
diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php
new file mode 100644
index 00000000..4b851930
--- /dev/null
+++ b/src/Exception/InvalidArgumentException.php
@@ -0,0 +1,6 @@
+<?php
+namespace Acelaya\UrlShortener\Exception;
+
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/src/Middleware/Rest/GetVisitsMiddleware.php b/src/Middleware/Rest/GetVisitsMiddleware.php
new file mode 100644
index 00000000..b932622d
--- /dev/null
+++ b/src/Middleware/Rest/GetVisitsMiddleware.php
@@ -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);
+        }
+    }
+}
diff --git a/src/Service/VisitsTracker.php b/src/Service/VisitsTracker.php
index 453627d9..4d091288 100644
--- a/src/Service/VisitsTracker.php
+++ b/src/Service/VisitsTracker.php
@@ -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'
+        ]);
+    }
 }
diff --git a/src/Service/VisitsTrackerInterface.php b/src/Service/VisitsTrackerInterface.php
index 3b2fc874..0d524223 100644
--- a/src/Service/VisitsTrackerInterface.php
+++ b/src/Service/VisitsTrackerInterface.php
@@ -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);
 }
diff --git a/src/Util/RestUtils.php b/src/Util/RestUtils.php
index d4c7179d..301c2e57 100644
--- a/src/Util/RestUtils.php
+++ b/src/Util/RestUtils.php
@@ -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;
         }

From 4129d35447ee650810c4fc1a3112ab7ac94016d8 Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Sat, 18 Jun 2016 09:43:29 +0200
Subject: [PATCH 05/18] Added list short URLs endpoint to rest api

---
 config/autoload/routes.global.php             |  6 ++
 config/autoload/services.global.php           |  2 +
 src/Entity/ShortUrl.php                       | 19 ++++-
 .../Rest/ListShortcodesMiddleware.php         | 74 +++++++++++++++++++
 src/Service/ShortUrlService.php               | 33 +++++++++
 src/Service/ShortUrlServiceInterface.php      | 12 +++
 tests/Service/ShortUrlServiceTest.php         | 45 +++++++++++
 7 files changed, 190 insertions(+), 1 deletion(-)
 create mode 100644 src/Middleware/Rest/ListShortcodesMiddleware.php
 create mode 100644 src/Service/ShortUrlService.php
 create mode 100644 src/Service/ShortUrlServiceInterface.php
 create mode 100644 tests/Service/ShortUrlServiceTest.php

diff --git a/config/autoload/routes.global.php b/config/autoload/routes.global.php
index 680a195d..b2976df5 100644
--- a/config/autoload/routes.global.php
+++ b/config/autoload/routes.global.php
@@ -25,6 +25,12 @@ return [
             'middleware' => Rest\ResolveUrlMiddleware::class,
             'allowed_methods' => ['GET'],
         ],
+        [
+            'name' => 'rest-list-shortened-url',
+            'path' => '/rest/short-codes',
+            'middleware' => Rest\ListShortcodesMiddleware::class,
+            'allowed_methods' => ['GET'],
+        ],
         [
             'name' => 'rest-get-visits',
             'path' => '/rest/visits/{shortCode}',
diff --git a/config/autoload/services.global.php b/config/autoload/services.global.php
index 8dada5e8..92a4adce 100644
--- a/config/autoload/services.global.php
+++ b/config/autoload/services.global.php
@@ -37,6 +37,7 @@ return [
             GuzzleHttp\Client::class => InvokableFactory::class,
             Service\UrlShortener::class => AnnotatedFactory::class,
             Service\VisitsTracker::class => AnnotatedFactory::class,
+            Service\ShortUrlService::class => AnnotatedFactory::class,
             Cache::class => CacheFactory::class,
 
             // Cli commands
@@ -47,6 +48,7 @@ return [
             Middleware\Rest\CreateShortcodeMiddleware::class => AnnotatedFactory::class,
             Middleware\Rest\ResolveUrlMiddleware::class => AnnotatedFactory::class,
             Middleware\Rest\GetVisitsMiddleware::class => AnnotatedFactory::class,
+            Middleware\Rest\ListShortcodesMiddleware::class => AnnotatedFactory::class,
         ],
         'aliases' => [
             'em' => EntityManager::class,
diff --git a/src/Entity/ShortUrl.php b/src/Entity/ShortUrl.php
index d1d5c4f7..fe96d651 100644
--- a/src/Entity/ShortUrl.php
+++ b/src/Entity/ShortUrl.php
@@ -13,7 +13,7 @@ use Doctrine\ORM\Mapping as ORM;
  * @ORM\Entity
  * @ORM\Table(name="short_urls")
  */
-class ShortUrl extends AbstractEntity
+class ShortUrl extends AbstractEntity implements \JsonSerializable
 {
     /**
      * @var string
@@ -117,4 +117,21 @@ class ShortUrl extends AbstractEntity
         $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),
+        ];
+    }
 }
diff --git a/src/Middleware/Rest/ListShortcodesMiddleware.php b/src/Middleware/Rest/ListShortcodesMiddleware.php
new file mode 100644
index 00000000..d437b742
--- /dev/null
+++ b/src/Middleware/Rest/ListShortcodesMiddleware.php
@@ -0,0 +1,74 @@
+<?php
+namespace Acelaya\UrlShortener\Middleware\Rest;
+
+use Acelaya\UrlShortener\Service\ShortUrlService;
+use Acelaya\UrlShortener\Service\ShortUrlServiceInterface;
+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 ListShortcodesMiddleware implements MiddlewareInterface
+{
+    /**
+     * @var ShortUrlServiceInterface
+     */
+    private $shortUrlService;
+
+    /**
+     * ListShortcodesMiddleware constructor.
+     * @param ShortUrlServiceInterface|ShortUrlService $shortUrlService
+     *
+     * @Inject({ShortUrlService::class})
+     */
+    public function __construct(ShortUrlServiceInterface $shortUrlService)
+    {
+        $this->shortUrlService = $shortUrlService;
+    }
+
+    /**
+     * 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)
+    {
+        try {
+            $shortUrls = $this->shortUrlService->listShortUrls();
+
+            return new JsonResponse([
+                'shortUrls' => [
+                    'data' => $shortUrls,
+//                    'pagination' => [],
+                ]
+            ]);
+        } catch (\Exception $e) {
+            return new JsonResponse([
+                'error' => RestUtils::UNKNOWN_ERROR,
+                'message' => 'Unexpected error occured',
+            ], 500);
+        }
+    }
+}
diff --git a/src/Service/ShortUrlService.php b/src/Service/ShortUrlService.php
new file mode 100644
index 00000000..f6dc57fa
--- /dev/null
+++ b/src/Service/ShortUrlService.php
@@ -0,0 +1,33 @@
+<?php
+namespace Acelaya\UrlShortener\Service;
+
+use Acelaya\UrlShortener\Entity\ShortUrl;
+use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
+use Doctrine\ORM\EntityManagerInterface;
+
+class ShortUrlService implements ShortUrlServiceInterface
+{
+    /**
+     * @var EntityManagerInterface
+     */
+    private $em;
+
+    /**
+     * ShortUrlService constructor.
+     * @param EntityManagerInterface $em
+     *
+     * @Inject({"em"})
+     */
+    public function __construct(EntityManagerInterface $em)
+    {
+        $this->em = $em;
+    }
+
+    /**
+     * @return ShortUrl[]
+     */
+    public function listShortUrls()
+    {
+        return $this->em->getRepository(ShortUrl::class)->findAll();
+    }
+}
diff --git a/src/Service/ShortUrlServiceInterface.php b/src/Service/ShortUrlServiceInterface.php
new file mode 100644
index 00000000..5a943ba0
--- /dev/null
+++ b/src/Service/ShortUrlServiceInterface.php
@@ -0,0 +1,12 @@
+<?php
+namespace Acelaya\UrlShortener\Service;
+
+use Acelaya\UrlShortener\Entity\ShortUrl;
+
+interface ShortUrlServiceInterface
+{
+    /**
+     * @return ShortUrl[]
+     */
+    public function listShortUrls();
+}
diff --git a/tests/Service/ShortUrlServiceTest.php b/tests/Service/ShortUrlServiceTest.php
new file mode 100644
index 00000000..6d325d17
--- /dev/null
+++ b/tests/Service/ShortUrlServiceTest.php
@@ -0,0 +1,45 @@
+<?php
+namespace AcelayaTest\UrlShortener\Service;
+
+use Acelaya\UrlShortener\Entity\ShortUrl;
+use Acelaya\UrlShortener\Service\ShortUrlService;
+use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\ORM\EntityRepository;
+use PHPUnit_Framework_TestCase as TestCase;
+use Prophecy\Prophecy\ObjectProphecy;
+
+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()
+    {
+        $repo = $this->prophesize(EntityRepository::class);
+        $repo->findAll()->willReturn([
+            new ShortUrl(),
+            new ShortUrl(),
+            new ShortUrl(),
+            new ShortUrl(),
+        ])->shouldBeCalledTimes(1);
+        $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
+
+        $list = $this->service->listShortUrls();
+        $this->assertCount(4, $list);
+    }
+}

From 67ef171262197762cb6513c3ee4de60a2fa1f1e5 Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Sun, 3 Jul 2016 08:40:39 +0200
Subject: [PATCH 06/18] Improved middleware pipeline and added cross-domain
 headers for ajax requests

---
 .../autoload/middleware-pipeline.global.php   | 15 ++++++
 config/autoload/services.global.php           |  1 +
 src/Middleware/CrossDomainMiddleware.php      | 51 +++++++++++++++++++
 src/Middleware/Rest/GetVisitsMiddleware.php   |  2 +-
 4 files changed, 68 insertions(+), 1 deletion(-)
 create mode 100644 src/Middleware/CrossDomainMiddleware.php

diff --git a/config/autoload/middleware-pipeline.global.php b/config/autoload/middleware-pipeline.global.php
index 8a393ce1..ab903ac9 100644
--- a/config/autoload/middleware-pipeline.global.php
+++ b/config/autoload/middleware-pipeline.global.php
@@ -1,4 +1,5 @@
 <?php
+use Acelaya\UrlShortener\Middleware;
 use Zend\Expressive\Container\ApplicationFactory;
 use Zend\Expressive\Helper;
 
@@ -15,6 +16,20 @@ return [
         'routing' => [
             'middleware' => [
                 ApplicationFactory::ROUTING_MIDDLEWARE,
+            ],
+            'priority' => 10,
+        ],
+
+        'rest' => [
+            'path' => '/rest',
+            'middleware' => [
+                Middleware\CrossDomainMiddleware::class,
+            ],
+            'priority' => 5,
+        ],
+
+        'post-routing' => [
+            'middleware' => [
                 Helper\UrlHelperMiddleware::class,
                 ApplicationFactory::DISPATCH_MIDDLEWARE,
             ],
diff --git a/config/autoload/services.global.php b/config/autoload/services.global.php
index 92a4adce..08fedf55 100644
--- a/config/autoload/services.global.php
+++ b/config/autoload/services.global.php
@@ -49,6 +49,7 @@ return [
             Middleware\Rest\ResolveUrlMiddleware::class => AnnotatedFactory::class,
             Middleware\Rest\GetVisitsMiddleware::class => AnnotatedFactory::class,
             Middleware\Rest\ListShortcodesMiddleware::class => AnnotatedFactory::class,
+            Middleware\CrossDomainMiddleware::class => InvokableFactory::class,
         ],
         'aliases' => [
             'em' => EntityManager::class,
diff --git a/src/Middleware/CrossDomainMiddleware.php b/src/Middleware/CrossDomainMiddleware.php
new file mode 100644
index 00000000..c762ed83
--- /dev/null
+++ b/src/Middleware/CrossDomainMiddleware.php
@@ -0,0 +1,51 @@
+<?php
+namespace Acelaya\UrlShortener\Middleware;
+
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Zend\Stratigility\MiddlewareInterface;
+
+class CrossDomainMiddleware implements MiddlewareInterface
+{
+    /**
+     * 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)
+    {
+        /** @var Response $response */
+        $response = $out($request, $response);
+
+        if ($request->hasHeader('X-Requested-With')
+            && strtolower($request->getHeaderLine('X-Requested-With')) === 'xmlhttprequest'
+        ) {
+            $response = $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
+                                 ->withHeader('Access-Control-Max-Age', '1000')
+                                 ->withHeader('Access-Control-Allow-Origin', '*')
+                                 ->withHeader('Access-Control-Allow-Headers', '*');
+        }
+
+        return $response;
+    }
+}
diff --git a/src/Middleware/Rest/GetVisitsMiddleware.php b/src/Middleware/Rest/GetVisitsMiddleware.php
index b932622d..1a1b973b 100644
--- a/src/Middleware/Rest/GetVisitsMiddleware.php
+++ b/src/Middleware/Rest/GetVisitsMiddleware.php
@@ -57,7 +57,7 @@ class GetVisitsMiddleware implements MiddlewareInterface
     public function __invoke(Request $request, Response $response, callable $out = null)
     {
         $shortCode = $request->getAttribute('shortCode');
-        
+
         try {
             $visits = $this->visitsTracker->info($shortCode);
 

From 35f1a4b6722f076f7d60d4a30caf6890a0230da4 Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Mon, 4 Jul 2016 08:57:37 +0200
Subject: [PATCH 07/18] Created stuff to handle pagination on list results

---
 composer.json                                 |  1 +
 src/Repository/PaginableRepository.php        | 14 +++++++
 src/Repository/ShortUrlRepository.php         | 42 +++++++++++++++++++
 .../ShortUrlRepositoryInterface.php           |  8 ++++
 src/Service/ShortUrlService.php               |  3 +-
 src/Service/ShortUrlServiceInterface.php      |  3 +-
 src/Service/VisitsTracker.php                 |  3 +-
 src/Service/VisitsTrackerInterface.php        |  3 +-
 8 files changed, 73 insertions(+), 4 deletions(-)
 create mode 100644 src/Repository/PaginableRepository.php
 create mode 100644 src/Repository/ShortUrlRepository.php
 create mode 100644 src/Repository/ShortUrlRepositoryInterface.php

diff --git a/composer.json b/composer.json
index 15f92bee..374fed26 100644
--- a/composer.json
+++ b/composer.json
@@ -18,6 +18,7 @@
         "zendframework/zend-expressive-twigrenderer": "^1.0",
         "zendframework/zend-stdlib": "^2.7",
         "zendframework/zend-servicemanager": "^3.0",
+        "zendframework/zend-paginator": "^2.6",
         "doctrine/orm": "^2.5",
         "guzzlehttp/guzzle": "^6.2",
         "acelaya/zsm-annotated-services": "^0.2.0",
diff --git a/src/Repository/PaginableRepository.php b/src/Repository/PaginableRepository.php
new file mode 100644
index 00000000..ad993961
--- /dev/null
+++ b/src/Repository/PaginableRepository.php
@@ -0,0 +1,14 @@
+<?php
+namespace Acelaya\UrlShortener\Repository;
+
+interface PaginableRepository
+{
+    /**
+     * @param int|null $limit
+     * @param int|null $offset
+     * @param string|null $searchTerm
+     * @param string|array|null $orderBy
+     * @return array
+     */
+    public function findList($limit = null, $offset = null, $searchTerm = null, $orderBy = null);
+}
diff --git a/src/Repository/ShortUrlRepository.php b/src/Repository/ShortUrlRepository.php
new file mode 100644
index 00000000..cfc23d58
--- /dev/null
+++ b/src/Repository/ShortUrlRepository.php
@@ -0,0 +1,42 @@
+<?php
+namespace Acelaya\UrlShortener\Repository;
+
+use Acelaya\UrlShortener\Entity\ShortUrl;
+use Doctrine\ORM\EntityRepository;
+
+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();
+    }
+}
diff --git a/src/Repository/ShortUrlRepositoryInterface.php b/src/Repository/ShortUrlRepositoryInterface.php
new file mode 100644
index 00000000..37b013d9
--- /dev/null
+++ b/src/Repository/ShortUrlRepositoryInterface.php
@@ -0,0 +1,8 @@
+<?php
+namespace Acelaya\UrlShortener\Repository;
+
+use Doctrine\Common\Persistence\ObjectRepository;
+
+interface ShortUrlRepositoryInterface extends ObjectRepository, PaginableRepository
+{
+}
diff --git a/src/Service/ShortUrlService.php b/src/Service/ShortUrlService.php
index f6dc57fa..2ffc640c 100644
--- a/src/Service/ShortUrlService.php
+++ b/src/Service/ShortUrlService.php
@@ -4,6 +4,7 @@ namespace Acelaya\UrlShortener\Service;
 use Acelaya\UrlShortener\Entity\ShortUrl;
 use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
 use Doctrine\ORM\EntityManagerInterface;
+use Zend\Paginator\Paginator;
 
 class ShortUrlService implements ShortUrlServiceInterface
 {
@@ -24,7 +25,7 @@ class ShortUrlService implements ShortUrlServiceInterface
     }
 
     /**
-     * @return ShortUrl[]
+     * @return Paginator|ShortUrl[]
      */
     public function listShortUrls()
     {
diff --git a/src/Service/ShortUrlServiceInterface.php b/src/Service/ShortUrlServiceInterface.php
index 5a943ba0..9f0a219c 100644
--- a/src/Service/ShortUrlServiceInterface.php
+++ b/src/Service/ShortUrlServiceInterface.php
@@ -2,11 +2,12 @@
 namespace Acelaya\UrlShortener\Service;
 
 use Acelaya\UrlShortener\Entity\ShortUrl;
+use Zend\Paginator\Paginator;
 
 interface ShortUrlServiceInterface
 {
     /**
-     * @return ShortUrl[]
+     * @return Paginator|ShortUrl[]
      */
     public function listShortUrls();
 }
diff --git a/src/Service/VisitsTracker.php b/src/Service/VisitsTracker.php
index 4d091288..7fcdf5e6 100644
--- a/src/Service/VisitsTracker.php
+++ b/src/Service/VisitsTracker.php
@@ -7,6 +7,7 @@ use Acelaya\UrlShortener\Exception\InvalidArgumentException;
 use Acelaya\UrlShortener\Exception\InvalidShortCodeException;
 use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
 use Doctrine\ORM\EntityManagerInterface;
+use Zend\Paginator\Paginator;
 
 class VisitsTracker implements VisitsTrackerInterface
 {
@@ -65,7 +66,7 @@ class VisitsTracker implements VisitsTrackerInterface
      * Returns the visits on certain shortcode
      *
      * @param $shortCode
-     * @return Visit[]
+     * @return Paginator|Visit[]
      */
     public function info($shortCode)
     {
diff --git a/src/Service/VisitsTrackerInterface.php b/src/Service/VisitsTrackerInterface.php
index 0d524223..ce0a61cf 100644
--- a/src/Service/VisitsTrackerInterface.php
+++ b/src/Service/VisitsTrackerInterface.php
@@ -2,6 +2,7 @@
 namespace Acelaya\UrlShortener\Service;
 
 use Acelaya\UrlShortener\Entity\Visit;
+use Zend\Paginator\Paginator;
 
 interface VisitsTrackerInterface
 {
@@ -17,7 +18,7 @@ interface VisitsTrackerInterface
      * Returns the visits on certain shortcode
      *
      * @param $shortCode
-     * @return Visit[]
+     * @return Paginator|Visit[]
      */
     public function info($shortCode);
 }

From cc1829f9ed45d38c563f965d05c4ce95239c01ac Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Mon, 4 Jul 2016 09:15:50 +0200
Subject: [PATCH 08/18] Added pagination to ShortUrls list

---
 src/Entity/ShortUrl.php                       |  2 +-
 .../Rest/ListShortcodesMiddleware.php         |  8 ++-
 .../Adapter/PaginableRepositoryAdapter.php    | 56 +++++++++++++++++++
 ...y.php => PaginableRepositoryInterface.php} | 12 +++-
 src/Repository/ShortUrlRepository.php         | 20 +++++++
 .../ShortUrlRepositoryInterface.php           |  2 +-
 src/Service/ShortUrlService.php               | 13 ++++-
 src/Service/ShortUrlServiceInterface.php      |  5 +-
 8 files changed, 109 insertions(+), 9 deletions(-)
 create mode 100644 src/Paginator/Adapter/PaginableRepositoryAdapter.php
 rename src/Repository/{PaginableRepository.php => PaginableRepositoryInterface.php} (51%)

diff --git a/src/Entity/ShortUrl.php b/src/Entity/ShortUrl.php
index fe96d651..9f63af68 100644
--- a/src/Entity/ShortUrl.php
+++ b/src/Entity/ShortUrl.php
@@ -10,7 +10,7 @@ use Doctrine\ORM\Mapping as ORM;
  * @author
  * @link
  *
- * @ORM\Entity
+ * @ORM\Entity(repositoryClass="Acelaya\UrlShortener\Repository\ShortUrlRepository")
  * @ORM\Table(name="short_urls")
  */
 class ShortUrl extends AbstractEntity implements \JsonSerializable
diff --git a/src/Middleware/Rest/ListShortcodesMiddleware.php b/src/Middleware/Rest/ListShortcodesMiddleware.php
index d437b742..5f3b92a0 100644
--- a/src/Middleware/Rest/ListShortcodesMiddleware.php
+++ b/src/Middleware/Rest/ListShortcodesMiddleware.php
@@ -8,6 +8,7 @@ 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\Stdlib\ArrayUtils;
 use Zend\Stratigility\MiddlewareInterface;
 
 class ListShortcodesMiddleware implements MiddlewareInterface
@@ -60,8 +61,11 @@ class ListShortcodesMiddleware implements MiddlewareInterface
 
             return new JsonResponse([
                 'shortUrls' => [
-                    'data' => $shortUrls,
-//                    'pagination' => [],
+                    'data' => ArrayUtils::iteratorToArray($shortUrls->getCurrentItems()),
+                    'pagination' => [
+                        'currentPage' => $shortUrls->getCurrentPageNumber(),
+                        'pagesCount' => $shortUrls->count(),
+                    ],
                 ]
             ]);
         } catch (\Exception $e) {
diff --git a/src/Paginator/Adapter/PaginableRepositoryAdapter.php b/src/Paginator/Adapter/PaginableRepositoryAdapter.php
new file mode 100644
index 00000000..243e4a1b
--- /dev/null
+++ b/src/Paginator/Adapter/PaginableRepositoryAdapter.php
@@ -0,0 +1,56 @@
+<?php
+namespace Acelaya\UrlShortener\Paginator\Adapter;
+
+use Acelaya\UrlShortener\Repository\PaginableRepositoryInterface;
+use Zend\Paginator\Adapter\AdapterInterface;
+
+class PaginableRepositoryAdapter implements AdapterInterface
+{
+    const ITEMS_PER_PAGE = 10;
+
+    /**
+     * @var PaginableRepositoryInterface
+     */
+    private $paginableRepository;
+    /**
+     * @var null
+     */
+    private $searchTerm;
+    /**
+     * @var null
+     */
+    private $orderBy;
+
+    public function __construct(PaginableRepositoryInterface $paginableRepository, $searchTerm = null, $orderBy = null)
+    {
+        $this->paginableRepository = $paginableRepository;
+        $this->searchTerm = $searchTerm;
+        $this->orderBy = $orderBy;
+    }
+
+    /**
+     * Returns a collection of items for a page.
+     *
+     * @param  int $offset Page offset
+     * @param  int $itemCountPerPage Number of items per page
+     * @return array
+     */
+    public function getItems($offset, $itemCountPerPage)
+    {
+        return $this->paginableRepository->findList($itemCountPerPage, $offset, $this->searchTerm, $this->orderBy);
+    }
+
+    /**
+     * Count elements of an object
+     * @link http://php.net/manual/en/countable.count.php
+     * @return int The custom count as an integer.
+     * </p>
+     * <p>
+     * The return value is cast to an integer.
+     * @since 5.1.0
+     */
+    public function count()
+    {
+        return $this->paginableRepository->countList($this->searchTerm);
+    }
+}
diff --git a/src/Repository/PaginableRepository.php b/src/Repository/PaginableRepositoryInterface.php
similarity index 51%
rename from src/Repository/PaginableRepository.php
rename to src/Repository/PaginableRepositoryInterface.php
index ad993961..99c8696e 100644
--- a/src/Repository/PaginableRepository.php
+++ b/src/Repository/PaginableRepositoryInterface.php
@@ -1,9 +1,11 @@
 <?php
 namespace Acelaya\UrlShortener\Repository;
 
-interface PaginableRepository
+interface PaginableRepositoryInterface
 {
     /**
+     * Gets a list of elements using provided filtering data
+     *
      * @param int|null $limit
      * @param int|null $offset
      * @param string|null $searchTerm
@@ -11,4 +13,12 @@ interface PaginableRepository
      * @return array
      */
     public function findList($limit = null, $offset = null, $searchTerm = null, $orderBy = null);
+
+    /**
+     * Counts the number of elements in a list using provided filtering data
+     *
+     * @param null $searchTerm
+     * @return int
+     */
+    public function countList($searchTerm = null);
 }
diff --git a/src/Repository/ShortUrlRepository.php b/src/Repository/ShortUrlRepository.php
index cfc23d58..7ba68d82 100644
--- a/src/Repository/ShortUrlRepository.php
+++ b/src/Repository/ShortUrlRepository.php
@@ -3,6 +3,7 @@ namespace Acelaya\UrlShortener\Repository;
 
 use Acelaya\UrlShortener\Entity\ShortUrl;
 use Doctrine\ORM\EntityRepository;
+use Doctrine\ORM\QueryBuilder;
 
 class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryInterface
 {
@@ -39,4 +40,23 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
 
         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();
+    }
 }
diff --git a/src/Repository/ShortUrlRepositoryInterface.php b/src/Repository/ShortUrlRepositoryInterface.php
index 37b013d9..be7f3fff 100644
--- a/src/Repository/ShortUrlRepositoryInterface.php
+++ b/src/Repository/ShortUrlRepositoryInterface.php
@@ -3,6 +3,6 @@ namespace Acelaya\UrlShortener\Repository;
 
 use Doctrine\Common\Persistence\ObjectRepository;
 
-interface ShortUrlRepositoryInterface extends ObjectRepository, PaginableRepository
+interface ShortUrlRepositoryInterface extends ObjectRepository, PaginableRepositoryInterface
 {
 }
diff --git a/src/Service/ShortUrlService.php b/src/Service/ShortUrlService.php
index 2ffc640c..9dbc176b 100644
--- a/src/Service/ShortUrlService.php
+++ b/src/Service/ShortUrlService.php
@@ -2,6 +2,8 @@
 namespace Acelaya\UrlShortener\Service;
 
 use Acelaya\UrlShortener\Entity\ShortUrl;
+use Acelaya\UrlShortener\Paginator\Adapter\PaginableRepositoryAdapter;
+use Acelaya\UrlShortener\Repository\ShortUrlRepository;
 use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
 use Doctrine\ORM\EntityManagerInterface;
 use Zend\Paginator\Paginator;
@@ -25,10 +27,17 @@ class ShortUrlService implements ShortUrlServiceInterface
     }
 
     /**
+     * @param int $page
      * @return Paginator|ShortUrl[]
      */
-    public function listShortUrls()
+    public function listShortUrls($page = 1)
     {
-        return $this->em->getRepository(ShortUrl::class)->findAll();
+        /** @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;
     }
 }
diff --git a/src/Service/ShortUrlServiceInterface.php b/src/Service/ShortUrlServiceInterface.php
index 9f0a219c..a9d182d2 100644
--- a/src/Service/ShortUrlServiceInterface.php
+++ b/src/Service/ShortUrlServiceInterface.php
@@ -7,7 +7,8 @@ use Zend\Paginator\Paginator;
 interface ShortUrlServiceInterface
 {
     /**
-     * @return Paginator|ShortUrl[]
+     * @param int $page
+     * @return ShortUrl[]|Paginator
      */
-    public function listShortUrls();
+    public function listShortUrls($page = 1);
 }

From 30773c66d05716a9c64c0a895158d9efd6f9f7ad Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Mon, 4 Jul 2016 09:18:10 +0200
Subject: [PATCH 09/18] Fixed ShortUrlServiceTest

---
 tests/Service/ShortUrlServiceTest.php | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/tests/Service/ShortUrlServiceTest.php b/tests/Service/ShortUrlServiceTest.php
index 6d325d17..3b77ee6a 100644
--- a/tests/Service/ShortUrlServiceTest.php
+++ b/tests/Service/ShortUrlServiceTest.php
@@ -2,10 +2,11 @@
 namespace AcelayaTest\UrlShortener\Service;
 
 use Acelaya\UrlShortener\Entity\ShortUrl;
+use Acelaya\UrlShortener\Repository\ShortUrlRepository;
 use Acelaya\UrlShortener\Service\ShortUrlService;
 use Doctrine\ORM\EntityManagerInterface;
-use Doctrine\ORM\EntityRepository;
 use PHPUnit_Framework_TestCase as TestCase;
+use Prophecy\Argument;
 use Prophecy\Prophecy\ObjectProphecy;
 
 class ShortUrlServiceTest extends TestCase
@@ -30,16 +31,19 @@ class ShortUrlServiceTest extends TestCase
      */
     public function listedUrlsAreReturnedFromEntityManager()
     {
-        $repo = $this->prophesize(EntityRepository::class);
-        $repo->findAll()->willReturn([
+        $list = [
             new ShortUrl(),
             new ShortUrl(),
             new ShortUrl(),
             new ShortUrl(),
-        ])->shouldBeCalledTimes(1);
+        ];
+
+        $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->assertCount(4, $list);
+        $this->assertEquals(4, $list->getCurrentItemCount());
     }
 }

From b4e6fe7135b488a3e530e707855eb51bcdee5cce Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Mon, 4 Jul 2016 12:50:06 +0200
Subject: [PATCH 10/18] Created trait to serialize paginators

---
 .../Rest/ListShortcodesMiddleware.php         | 17 ++++++-----------
 .../Util/PaginatorSerializerTrait.php         | 19 +++++++++++++++++++
 2 files changed, 25 insertions(+), 11 deletions(-)
 create mode 100644 src/Paginator/Util/PaginatorSerializerTrait.php

diff --git a/src/Middleware/Rest/ListShortcodesMiddleware.php b/src/Middleware/Rest/ListShortcodesMiddleware.php
index 5f3b92a0..6a4a627f 100644
--- a/src/Middleware/Rest/ListShortcodesMiddleware.php
+++ b/src/Middleware/Rest/ListShortcodesMiddleware.php
@@ -1,6 +1,7 @@
 <?php
 namespace Acelaya\UrlShortener\Middleware\Rest;
 
+use Acelaya\UrlShortener\Paginator\Util\PaginatorSerializerTrait;
 use Acelaya\UrlShortener\Service\ShortUrlService;
 use Acelaya\UrlShortener\Service\ShortUrlServiceInterface;
 use Acelaya\UrlShortener\Util\RestUtils;
@@ -13,6 +14,8 @@ use Zend\Stratigility\MiddlewareInterface;
 
 class ListShortcodesMiddleware implements MiddlewareInterface
 {
+    use PaginatorSerializerTrait;
+
     /**
      * @var ShortUrlServiceInterface
      */
@@ -57,17 +60,9 @@ class ListShortcodesMiddleware implements MiddlewareInterface
     public function __invoke(Request $request, Response $response, callable $out = null)
     {
         try {
-            $shortUrls = $this->shortUrlService->listShortUrls();
-
-            return new JsonResponse([
-                'shortUrls' => [
-                    'data' => ArrayUtils::iteratorToArray($shortUrls->getCurrentItems()),
-                    'pagination' => [
-                        'currentPage' => $shortUrls->getCurrentPageNumber(),
-                        'pagesCount' => $shortUrls->count(),
-                    ],
-                ]
-            ]);
+            $query = $request->getQueryParams();
+            $shortUrls = $this->shortUrlService->listShortUrls(isset($query['page']) ? $query['page'] : 1);
+            return new JsonResponse(['shortUrls' => $this->serializePaginator($shortUrls)]);
         } catch (\Exception $e) {
             return new JsonResponse([
                 'error' => RestUtils::UNKNOWN_ERROR,
diff --git a/src/Paginator/Util/PaginatorSerializerTrait.php b/src/Paginator/Util/PaginatorSerializerTrait.php
new file mode 100644
index 00000000..573832ca
--- /dev/null
+++ b/src/Paginator/Util/PaginatorSerializerTrait.php
@@ -0,0 +1,19 @@
+<?php
+namespace Acelaya\UrlShortener\Paginator\Util;
+
+use Zend\Paginator\Paginator;
+use Zend\Stdlib\ArrayUtils;
+
+trait PaginatorSerializerTrait
+{
+    protected function serializePaginator(Paginator $paginator)
+    {
+        return [
+            'data' => ArrayUtils::iteratorToArray($paginator->getCurrentItems()),
+            'pagination' => [
+                'currentPage' => $paginator->getCurrentPageNumber(),
+                'pagesCount' => $paginator->count(),
+            ],
+        ];
+    }
+}

From bbef3444c214eeb29964d97a7dd9ebef6f8d31bd Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Mon, 4 Jul 2016 13:14:01 +0200
Subject: [PATCH 11/18] Added errorhanler local config distributable file

---
 config/autoload/errorhandler.local.php.dist | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)
 create mode 100644 config/autoload/errorhandler.local.php.dist

diff --git a/config/autoload/errorhandler.local.php.dist b/config/autoload/errorhandler.local.php.dist
new file mode 100644
index 00000000..92a92497
--- /dev/null
+++ b/config/autoload/errorhandler.local.php.dist
@@ -0,0 +1,21 @@
+<?php
+
+return [
+    'services' => [
+        'invokables' => [
+            'Zend\Expressive\Whoops' => Whoops\Run::class,
+            'Zend\Expressive\WhoopsPageHandler' => Whoops\Handler\PrettyPageHandler::class,
+        ],
+        'factories' => [
+            'Zend\Expressive\FinalHandler' => Zend\Expressive\Container\WhoopsErrorHandlerFactory::class,
+        ],
+    ],
+
+    'whoops' => [
+        'json_exceptions' => [
+            'display'    => true,
+            'show_trace' => true,
+            'ajax_only'  => true,
+        ],
+    ],
+];

From 56b2bd3d56073ec3ba4f61d14e824cb21c9cc496 Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Mon, 4 Jul 2016 14:04:10 +0200
Subject: [PATCH 12/18] Created entity to persist rest tokens

---
 .env.dist                       |  4 ++
 config/autoload/rest.global.php |  9 ++++
 src/Entity/RestToken.php        | 89 +++++++++++++++++++++++++++++++++
 3 files changed, 102 insertions(+)
 create mode 100644 config/autoload/rest.global.php
 create mode 100644 src/Entity/RestToken.php

diff --git a/.env.dist b/.env.dist
index a25e8c2c..d6271d57 100644
--- a/.env.dist
+++ b/.env.dist
@@ -8,3 +8,7 @@ SHORTCODE_CHARS=
 DB_USER=
 DB_PASSWORD=
 DB_NAME=
+
+# Rest authentication
+REST_USER=
+REST_PASSWORD=
diff --git a/config/autoload/rest.global.php b/config/autoload/rest.global.php
new file mode 100644
index 00000000..6e3fc216
--- /dev/null
+++ b/config/autoload/rest.global.php
@@ -0,0 +1,9 @@
+<?php
+return [
+
+    'rest' => [
+        'username' => getenv('REST_USER'),
+        'password' => getenv('REST_PASSWORD'),
+    ],
+
+];
diff --git a/src/Entity/RestToken.php b/src/Entity/RestToken.php
new file mode 100644
index 00000000..d23dc10f
--- /dev/null
+++ b/src/Entity/RestToken.php
@@ -0,0 +1,89 @@
+<?php
+namespace Acelaya\UrlShortener\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Class RestToken
+ * @author
+ * @link
+ *
+ * @ORM\Entity()
+ * @ORM\Table(name="rest_tokens")
+ */
+class RestToken extends AbstractEntity
+{
+    /**
+     * 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();
+    }
+
+    /**
+     * @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)));
+    }
+}

From dfc5bfd0f2b9576aa88aefdedb943d2296ad96de Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Mon, 4 Jul 2016 14:45:18 +0200
Subject: [PATCH 13/18] Created rest route to perform authentication

---
 config/autoload/routes.global.php             |  6 ++
 config/autoload/services.global.php           |  2 +
 src/Entity/RestToken.php                      | 13 +++
 src/Exception/AuthenticationException.php     | 10 +++
 .../Rest/AuthenticateMiddleware.php           | 77 ++++++++++++++++
 src/Service/RestTokenService.php              | 87 +++++++++++++++++++
 src/Service/RestTokenServiceInterface.php     | 25 ++++++
 src/Util/RestUtils.php                        |  3 +
 src/Util/StringUtilsTrait.php                 | 40 +++++++++
 9 files changed, 263 insertions(+)
 create mode 100644 src/Exception/AuthenticationException.php
 create mode 100644 src/Middleware/Rest/AuthenticateMiddleware.php
 create mode 100644 src/Service/RestTokenService.php
 create mode 100644 src/Service/RestTokenServiceInterface.php
 create mode 100644 src/Util/StringUtilsTrait.php

diff --git a/config/autoload/routes.global.php b/config/autoload/routes.global.php
index b2976df5..7ffdbc74 100644
--- a/config/autoload/routes.global.php
+++ b/config/autoload/routes.global.php
@@ -13,6 +13,12 @@ return [
         ],
 
         // Rest
+        [
+            'name' => 'rest-authenticate',
+            'path' => '/rest/authenticate',
+            'middleware' => Rest\AuthenticateMiddleware::class,
+            'allowed_methods' => ['POST'],
+        ],
         [
             'name' => 'rest-create-shortcode',
             'path' => '/rest/short-codes',
diff --git a/config/autoload/services.global.php b/config/autoload/services.global.php
index 08fedf55..d5de3d4a 100644
--- a/config/autoload/services.global.php
+++ b/config/autoload/services.global.php
@@ -38,6 +38,7 @@ return [
             Service\UrlShortener::class => AnnotatedFactory::class,
             Service\VisitsTracker::class => AnnotatedFactory::class,
             Service\ShortUrlService::class => AnnotatedFactory::class,
+            Service\RestTokenService::class => AnnotatedFactory::class,
             Cache::class => CacheFactory::class,
 
             // Cli commands
@@ -45,6 +46,7 @@ return [
 
             // Middleware
             Middleware\Routable\RedirectMiddleware::class => AnnotatedFactory::class,
+            Middleware\Rest\AuthenticateMiddleware::class => AnnotatedFactory::class,
             Middleware\Rest\CreateShortcodeMiddleware::class => AnnotatedFactory::class,
             Middleware\Rest\ResolveUrlMiddleware::class => AnnotatedFactory::class,
             Middleware\Rest\GetVisitsMiddleware::class => AnnotatedFactory::class,
diff --git a/src/Entity/RestToken.php b/src/Entity/RestToken.php
index d23dc10f..90a70f0e 100644
--- a/src/Entity/RestToken.php
+++ b/src/Entity/RestToken.php
@@ -1,6 +1,7 @@
 <?php
 namespace Acelaya\UrlShortener\Entity;
 
+use Acelaya\UrlShortener\Util\StringUtilsTrait;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
@@ -13,6 +14,8 @@ use Doctrine\ORM\Mapping as ORM;
  */
 class RestToken extends AbstractEntity
 {
+    use StringUtilsTrait;
+
     /**
      * The default interval is 20 minutes
      */
@@ -32,6 +35,7 @@ class RestToken extends AbstractEntity
     public function __construct()
     {
         $this->updateExpiration();
+        $this->setRandomTokenKey();
     }
 
     /**
@@ -86,4 +90,13 @@ class RestToken extends AbstractEntity
     {
         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());
+    }
 }
diff --git a/src/Exception/AuthenticationException.php b/src/Exception/AuthenticationException.php
new file mode 100644
index 00000000..0876be75
--- /dev/null
+++ b/src/Exception/AuthenticationException.php
@@ -0,0 +1,10 @@
+<?php
+namespace Acelaya\UrlShortener\Exception;
+
+class AuthenticationException extends \RuntimeException implements ExceptionInterface
+{
+    public static function fromCredentials($username, $password)
+    {
+        return new self(sprintf('Invalid credentials. Username -> "%s". Password -> "%s"', $username, $password));
+    }
+}
diff --git a/src/Middleware/Rest/AuthenticateMiddleware.php b/src/Middleware/Rest/AuthenticateMiddleware.php
new file mode 100644
index 00000000..0189b249
--- /dev/null
+++ b/src/Middleware/Rest/AuthenticateMiddleware.php
@@ -0,0 +1,77 @@
+<?php
+namespace Acelaya\UrlShortener\Middleware\Rest;
+
+use Acelaya\UrlShortener\Exception\AuthenticationException;
+use Acelaya\UrlShortener\Service\RestTokenService;
+use Acelaya\UrlShortener\Service\RestTokenServiceInterface;
+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 AuthenticateMiddleware implements MiddlewareInterface
+{
+    /**
+     * @var RestTokenServiceInterface
+     */
+    private $restTokenService;
+
+    /**
+     * AuthenticateMiddleware constructor.
+     * @param RestTokenServiceInterface|RestTokenService $restTokenService
+     *
+     * @Inject({RestTokenService::class})
+     */
+    public function __construct(RestTokenServiceInterface $restTokenService)
+    {
+        $this->restTokenService = $restTokenService;
+    }
+
+    /**
+     * 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)
+    {
+        $authData = $request->getParsedBody();
+        if (! isset($authData['username'], $authData['password'])) {
+            return new JsonResponse([
+                'error' => RestUtils::INVALID_ARGUMENT_ERROR,
+                'message' => 'You have to provide both "username" and "password"'
+            ], 400);
+        }
+
+        try {
+            $token = $this->restTokenService->createToken($authData['username'], $authData['password']);
+            return new JsonResponse(['token' => $token->getToken()]);
+        } catch (AuthenticationException $e) {
+            return new JsonResponse([
+                'error' => RestUtils::getRestErrorCodeFromException($e),
+                'message' => 'Invalid username and/or password',
+            ], 401);
+        }
+    }
+}
diff --git a/src/Service/RestTokenService.php b/src/Service/RestTokenService.php
new file mode 100644
index 00000000..aa9ea0b8
--- /dev/null
+++ b/src/Service/RestTokenService.php
@@ -0,0 +1,87 @@
+<?php
+namespace Acelaya\UrlShortener\Service;
+
+use Acelaya\UrlShortener\Entity\RestToken;
+use Acelaya\UrlShortener\Exception\AuthenticationException;
+use Acelaya\UrlShortener\Exception\InvalidArgumentException;
+use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
+use Doctrine\ORM\EntityManagerInterface;
+
+class RestTokenService implements RestTokenServiceInterface
+{
+    /**
+     * @var EntityManagerInterface
+     */
+    private $em;
+    /**
+     * @var array
+     */
+    private $restConfig;
+
+    /**
+     * ShortUrlService constructor.
+     * @param EntityManagerInterface $em
+     *
+     * @param array $restConfig
+     * @Inject({"em", "config.rest"})
+     */
+    public function __construct(EntityManagerInterface $em, array $restConfig)
+    {
+        $this->em = $em;
+        $this->restConfig = $restConfig;
+    }
+
+    /**
+     * @param string $token
+     * @return RestToken
+     * @throws InvalidArgumentException
+     */
+    public function getByToken($token)
+    {
+        $restToken = $this->em->getRepository(RestToken::class)->findOneBy([
+            'token' => $token,
+        ]);
+        if (! isset($restToken)) {
+            throw new InvalidArgumentException(sprintf('RestToken not found for token "%s"', $token));
+        }
+
+        return $restToken;
+    }
+
+    /**
+     * Creates and returns a new RestToken if username and password are correct
+     * @param $username
+     * @param $password
+     * @return RestToken
+     * @throws AuthenticationException
+     */
+    public function createToken($username, $password)
+    {
+        $this->processCredentials($username, $password);
+
+        $restToken = new RestToken();
+        $this->em->persist($restToken);
+        $this->em->flush();
+
+        return $restToken;
+    }
+
+    /**
+     * @param string $username
+     * @param string $password
+     */
+    protected function processCredentials($username, $password)
+    {
+        $configUsername = strtolower(trim($this->restConfig['username']));
+        $providedUsername = strtolower(trim($username));
+        $configPassword = trim($this->restConfig['password']);
+        $providedPassword = trim($password);
+
+        if ($configUsername === $providedUsername && $configPassword === $providedPassword) {
+            return;
+        }
+
+        // If credentials are not correct, throw exception
+        throw AuthenticationException::fromCredentials($providedUsername, $providedPassword);
+    }
+}
diff --git a/src/Service/RestTokenServiceInterface.php b/src/Service/RestTokenServiceInterface.php
new file mode 100644
index 00000000..fb45483d
--- /dev/null
+++ b/src/Service/RestTokenServiceInterface.php
@@ -0,0 +1,25 @@
+<?php
+namespace Acelaya\UrlShortener\Service;
+
+use Acelaya\UrlShortener\Entity\RestToken;
+use Acelaya\UrlShortener\Exception\AuthenticationException;
+use Acelaya\UrlShortener\Exception\InvalidArgumentException;
+
+interface RestTokenServiceInterface
+{
+    /**
+     * @param string $token
+     * @return RestToken
+     * @throws InvalidArgumentException
+     */
+    public function getByToken($token);
+
+    /**
+     * Creates and returns a new RestToken if username and password are correct
+     * @param $username
+     * @param $password
+     * @return RestToken
+     * @throws AuthenticationException
+     */
+    public function createToken($username, $password);
+}
diff --git a/src/Util/RestUtils.php b/src/Util/RestUtils.php
index 301c2e57..94ab47ec 100644
--- a/src/Util/RestUtils.php
+++ b/src/Util/RestUtils.php
@@ -8,6 +8,7 @@ class RestUtils
     const INVALID_SHORTCODE_ERROR = 'INVALID_SHORTCODE';
     const INVALID_URL_ERROR = 'INVALID_URL';
     const INVALID_ARGUMENT_ERROR = 'INVALID_ARGUMENT';
+    const INVALID_CREDENTIALS = 'INVALID_CREDENTIALS';
     const UNKNOWN_ERROR = 'UNKNOWN_ERROR';
 
     public static function getRestErrorCodeFromException(Exception\ExceptionInterface $e)
@@ -19,6 +20,8 @@ class RestUtils
                 return self::INVALID_URL_ERROR;
             case $e instanceof Exception\InvalidArgumentException:
                 return self::INVALID_ARGUMENT_ERROR;
+            case $e instanceof Exception\AuthenticationException:
+                return self::INVALID_CREDENTIALS;
             default:
                 return self::UNKNOWN_ERROR;
         }
diff --git a/src/Util/StringUtilsTrait.php b/src/Util/StringUtilsTrait.php
new file mode 100644
index 00000000..2b4bf625
--- /dev/null
+++ b/src/Util/StringUtilsTrait.php
@@ -0,0 +1,40 @@
+<?php
+namespace Acelaya\UrlShortener\Util;
+
+trait StringUtilsTrait
+{
+    protected function generateRandomString($length = 10)
+    {
+        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+        $charactersLength = strlen($characters);
+        $randomString = '';
+        for ($i = 0; $i < $length; $i++) {
+            $randomString .= $characters[rand(0, $charactersLength - 1)];
+        }
+
+        return $randomString;
+    }
+
+    protected function generateV4Uuid()
+    {
+        return sprintf(
+            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
+            // 32 bits for "time_low"
+            mt_rand(0, 0xffff),
+            mt_rand(0, 0xffff),
+            // 16 bits for "time_mid"
+            mt_rand(0, 0xffff),
+            // 16 bits for "time_hi_and_version",
+            // four most significant bits holds version number 4
+            mt_rand(0, 0x0fff) | 0x4000,
+            // 16 bits, 8 bits for "clk_seq_hi_res",
+            // 8 bits for "clk_seq_low",
+            // two most significant bits holds zero and one for variant DCE1.1
+            mt_rand(0, 0x3fff) | 0x8000,
+            // 48 bits for "node"
+            mt_rand(0, 0xffff),
+            mt_rand(0, 0xffff),
+            mt_rand(0, 0xffff)
+        );
+    }
+}

From 431169eb8c0788b929cfc28e32da32267bcbbd26 Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Mon, 4 Jul 2016 17:54:24 +0200
Subject: [PATCH 14/18] Created middleware that checks authentication

---
 .../autoload/middleware-pipeline.global.php   |   1 +
 config/autoload/services.global.php           |   1 +
 .../CheckAuthenticationMiddleware.php         | 100 ++++++++++++++++++
 src/Service/RestTokenService.php              |  11 ++
 src/Service/RestTokenServiceInterface.php     |   7 ++
 src/Util/RestUtils.php                        |   5 +-
 6 files changed, 123 insertions(+), 2 deletions(-)
 create mode 100644 src/Middleware/CheckAuthenticationMiddleware.php

diff --git a/config/autoload/middleware-pipeline.global.php b/config/autoload/middleware-pipeline.global.php
index ab903ac9..fc6f85f0 100644
--- a/config/autoload/middleware-pipeline.global.php
+++ b/config/autoload/middleware-pipeline.global.php
@@ -23,6 +23,7 @@ return [
         'rest' => [
             'path' => '/rest',
             'middleware' => [
+                Middleware\CheckAuthenticationMiddleware::class,
                 Middleware\CrossDomainMiddleware::class,
             ],
             'priority' => 5,
diff --git a/config/autoload/services.global.php b/config/autoload/services.global.php
index d5de3d4a..b08229e7 100644
--- a/config/autoload/services.global.php
+++ b/config/autoload/services.global.php
@@ -52,6 +52,7 @@ return [
             Middleware\Rest\GetVisitsMiddleware::class => AnnotatedFactory::class,
             Middleware\Rest\ListShortcodesMiddleware::class => AnnotatedFactory::class,
             Middleware\CrossDomainMiddleware::class => InvokableFactory::class,
+            Middleware\CheckAuthenticationMiddleware::class => AnnotatedFactory::class,
         ],
         'aliases' => [
             'em' => EntityManager::class,
diff --git a/src/Middleware/CheckAuthenticationMiddleware.php b/src/Middleware/CheckAuthenticationMiddleware.php
new file mode 100644
index 00000000..92081ad8
--- /dev/null
+++ b/src/Middleware/CheckAuthenticationMiddleware.php
@@ -0,0 +1,100 @@
+<?php
+namespace Acelaya\UrlShortener\Middleware;
+
+use Acelaya\UrlShortener\Exception\InvalidArgumentException;
+use Acelaya\UrlShortener\Service\RestTokenService;
+use Acelaya\UrlShortener\Service\RestTokenServiceInterface;
+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\Expressive\Router\RouteResult;
+use Zend\Stratigility\MiddlewareInterface;
+
+class CheckAuthenticationMiddleware implements MiddlewareInterface
+{
+    const AUTH_TOKEN_HEADER = 'X-Auth-Token';
+
+    /**
+     * @var RestTokenServiceInterface
+     */
+    private $restTokenService;
+
+    /**
+     * CheckAuthenticationMiddleware constructor.
+     * @param RestTokenServiceInterface|RestTokenService $restTokenService
+     *
+     * @Inject({RestTokenService::class})
+     */
+    public function __construct(RestTokenServiceInterface $restTokenService)
+    {
+        $this->restTokenService = $restTokenService;
+    }
+
+    /**
+     * 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)
+    {
+        // If current route is the authenticate route, continue to the next middleware
+        /** @var RouteResult $routeResult */
+        $routeResult = $request->getAttribute(RouteResult::class);
+        if (isset($routeResult) && $routeResult->getMatchedRouteName() === 'rest-authenticate') {
+            return $out($request, $response);
+        }
+
+        // Check that the auth header was provided, and that it belongs to a non-expired token
+        if (! $request->hasHeader(self::AUTH_TOKEN_HEADER)) {
+            return $this->createTokenErrorResponse();
+        }
+
+        $authToken = $request->getHeaderLine(self::AUTH_TOKEN_HEADER);
+        try {
+            $restToken = $this->restTokenService->getByToken($authToken);
+            if ($restToken->isExpired()) {
+                return $this->createTokenErrorResponse();
+            }
+
+            // Update the token expiration and continue to next middleware
+            $this->restTokenService->updateExpiration($restToken);
+            return $out($request, $response);
+        } catch (InvalidArgumentException $e) {
+            return $this->createTokenErrorResponse();
+        }
+    }
+
+    protected function createTokenErrorResponse()
+    {
+        return new JsonResponse([
+            'error' => RestUtils::INVALID_AUTH_TOKEN_ERROR,
+            'message' => sprintf(
+                'Missing or invalid auth token provided. Perform a new authentication request and send provided token '
+                . 'on every new request on the "%s" header',
+                self::AUTH_TOKEN_HEADER
+            ),
+        ], 401);
+    }
+}
diff --git a/src/Service/RestTokenService.php b/src/Service/RestTokenService.php
index aa9ea0b8..26d7f34c 100644
--- a/src/Service/RestTokenService.php
+++ b/src/Service/RestTokenService.php
@@ -84,4 +84,15 @@ class RestTokenService implements RestTokenServiceInterface
         // If credentials are not correct, throw exception
         throw AuthenticationException::fromCredentials($providedUsername, $providedPassword);
     }
+
+    /**
+     * Updates the expiration of provided token, extending its life
+     *
+     * @param RestToken $token
+     */
+    public function updateExpiration(RestToken $token)
+    {
+        $token->updateExpiration();
+        $this->em->flush();
+    }
 }
diff --git a/src/Service/RestTokenServiceInterface.php b/src/Service/RestTokenServiceInterface.php
index fb45483d..0cdec822 100644
--- a/src/Service/RestTokenServiceInterface.php
+++ b/src/Service/RestTokenServiceInterface.php
@@ -22,4 +22,11 @@ interface RestTokenServiceInterface
      * @throws AuthenticationException
      */
     public function createToken($username, $password);
+
+    /**
+     * Updates the expiration of provided token, extending its life
+     *
+     * @param RestToken $token
+     */
+    public function updateExpiration(RestToken $token);
 }
diff --git a/src/Util/RestUtils.php b/src/Util/RestUtils.php
index 94ab47ec..f0c37a00 100644
--- a/src/Util/RestUtils.php
+++ b/src/Util/RestUtils.php
@@ -8,7 +8,8 @@ class RestUtils
     const INVALID_SHORTCODE_ERROR = 'INVALID_SHORTCODE';
     const INVALID_URL_ERROR = 'INVALID_URL';
     const INVALID_ARGUMENT_ERROR = 'INVALID_ARGUMENT';
-    const INVALID_CREDENTIALS = 'INVALID_CREDENTIALS';
+    const INVALID_CREDENTIALS_ERROR = 'INVALID_CREDENTIALS';
+    const INVALID_AUTH_TOKEN_ERROR = 'INVALID_AUTH_TOKEN_ERROR';
     const UNKNOWN_ERROR = 'UNKNOWN_ERROR';
 
     public static function getRestErrorCodeFromException(Exception\ExceptionInterface $e)
@@ -21,7 +22,7 @@ class RestUtils
             case $e instanceof Exception\InvalidArgumentException:
                 return self::INVALID_ARGUMENT_ERROR;
             case $e instanceof Exception\AuthenticationException:
-                return self::INVALID_CREDENTIALS;
+                return self::INVALID_CREDENTIALS_ERROR;
             default:
                 return self::UNKNOWN_ERROR;
         }

From bd36c65a7347fbea387f2a6c65c4dd59dc76c34b Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Tue, 5 Jul 2016 19:08:34 +0200
Subject: [PATCH 15/18] Fixed some cross-origin issues

---
 config/autoload/routes.global.php                 |  2 +-
 src/Middleware/CrossDomainMiddleware.php          | 13 +++++++------
 src/Middleware/Rest/AuthenticateMiddleware.php    |  4 ++++
 src/Middleware/Rest/CreateShortcodeMiddleware.php |  5 +++--
 4 files changed, 15 insertions(+), 9 deletions(-)

diff --git a/config/autoload/routes.global.php b/config/autoload/routes.global.php
index 7ffdbc74..06e5f733 100644
--- a/config/autoload/routes.global.php
+++ b/config/autoload/routes.global.php
@@ -17,7 +17,7 @@ return [
             'name' => 'rest-authenticate',
             'path' => '/rest/authenticate',
             'middleware' => Rest\AuthenticateMiddleware::class,
-            'allowed_methods' => ['POST'],
+            'allowed_methods' => ['POST', 'OPTIONS'],
         ],
         [
             'name' => 'rest-create-shortcode',
diff --git a/src/Middleware/CrossDomainMiddleware.php b/src/Middleware/CrossDomainMiddleware.php
index c762ed83..c76d4d73 100644
--- a/src/Middleware/CrossDomainMiddleware.php
+++ b/src/Middleware/CrossDomainMiddleware.php
@@ -37,15 +37,16 @@ class CrossDomainMiddleware implements MiddlewareInterface
         /** @var Response $response */
         $response = $out($request, $response);
 
-        if ($request->hasHeader('X-Requested-With')
-            && strtolower($request->getHeaderLine('X-Requested-With')) === 'xmlhttprequest'
-        ) {
+        if (strtolower($request->getMethod()) === 'options') {
             $response = $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
                                  ->withHeader('Access-Control-Max-Age', '1000')
-                                 ->withHeader('Access-Control-Allow-Origin', '*')
-                                 ->withHeader('Access-Control-Allow-Headers', '*');
+                                 ->withHeader(
+                                     // Allow all requested headers
+                                     'Access-Control-Allow-Headers',
+                                     $request->getHeaderLine('Access-Control-Request-Headers')
+                                 );
         }
 
-        return $response;
+        return $response->withHeader('Access-Control-Allow-Origin', '*');
     }
 }
diff --git a/src/Middleware/Rest/AuthenticateMiddleware.php b/src/Middleware/Rest/AuthenticateMiddleware.php
index 0189b249..85d12330 100644
--- a/src/Middleware/Rest/AuthenticateMiddleware.php
+++ b/src/Middleware/Rest/AuthenticateMiddleware.php
@@ -56,6 +56,10 @@ class AuthenticateMiddleware implements MiddlewareInterface
      */
     public function __invoke(Request $request, Response $response, callable $out = null)
     {
+        if (strtolower($request->getMethod()) === 'options') {
+            return $response;
+        }
+
         $authData = $request->getParsedBody();
         if (! isset($authData['username'], $authData['password'])) {
             return new JsonResponse([
diff --git a/src/Middleware/Rest/CreateShortcodeMiddleware.php b/src/Middleware/Rest/CreateShortcodeMiddleware.php
index 1e723d48..b68c551c 100644
--- a/src/Middleware/Rest/CreateShortcodeMiddleware.php
+++ b/src/Middleware/Rest/CreateShortcodeMiddleware.php
@@ -74,14 +74,15 @@ class CreateShortcodeMiddleware implements MiddlewareInterface
         $longUrl = $postData['longUrl'];
 
         try {
-            $shortcode = $this->urlShortener->urlToShortCode(new Uri($longUrl));
-            $shortUrl = (new Uri())->withPath($shortcode)
+            $shortCode = $this->urlShortener->urlToShortCode(new Uri($longUrl));
+            $shortUrl = (new Uri())->withPath($shortCode)
                                    ->withScheme($this->domainConfig['schema'])
                                    ->withHost($this->domainConfig['hostname']);
 
             return new JsonResponse([
                 'longUrl' => $longUrl,
                 'shortUrl' => $shortUrl->__toString(),
+                'shortCode' => $shortCode,
             ]);
         } catch (InvalidUrlException $e) {
             return new JsonResponse([

From baf5936cf18d778e2e5a9d7b64c52836d5f700bd Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Tue, 5 Jul 2016 19:19:23 +0200
Subject: [PATCH 16/18] More cross-domain improvements

---
 config/autoload/routes.global.php             |  6 +--
 .../CheckAuthenticationMiddleware.php         |  6 ++-
 .../Rest/AbstractRestMiddleware.php           | 51 +++++++++++++++++++
 .../Rest/AuthenticateMiddleware.php           | 30 ++---------
 .../Rest/CreateShortcodeMiddleware.php        | 26 ++--------
 src/Middleware/Rest/GetVisitsMiddleware.php   | 26 ++--------
 .../Rest/ListShortcodesMiddleware.php         | 26 ++--------
 src/Middleware/Rest/ResolveUrlMiddleware.php  | 26 ++--------
 8 files changed, 73 insertions(+), 124 deletions(-)
 create mode 100644 src/Middleware/Rest/AbstractRestMiddleware.php

diff --git a/config/autoload/routes.global.php b/config/autoload/routes.global.php
index 06e5f733..87133f09 100644
--- a/config/autoload/routes.global.php
+++ b/config/autoload/routes.global.php
@@ -23,13 +23,13 @@ return [
             'name' => 'rest-create-shortcode',
             'path' => '/rest/short-codes',
             'middleware' => Rest\CreateShortcodeMiddleware::class,
-            'allowed_methods' => ['POST'],
+            'allowed_methods' => ['POST', 'OPTIONS'],
         ],
         [
             'name' => 'rest-resolve-url',
             'path' => '/rest/short-codes/{shortCode}',
             'middleware' => Rest\ResolveUrlMiddleware::class,
-            'allowed_methods' => ['GET'],
+            'allowed_methods' => ['GET', 'OPTIONS'],
         ],
         [
             'name' => 'rest-list-shortened-url',
@@ -41,7 +41,7 @@ return [
             'name' => 'rest-get-visits',
             'path' => '/rest/visits/{shortCode}',
             'middleware' => Rest\GetVisitsMiddleware::class,
-            'allowed_methods' => ['GET'],
+            'allowed_methods' => ['GET', 'OPTIONS'],
         ],
     ],
 
diff --git a/src/Middleware/CheckAuthenticationMiddleware.php b/src/Middleware/CheckAuthenticationMiddleware.php
index 92081ad8..8f7327e7 100644
--- a/src/Middleware/CheckAuthenticationMiddleware.php
+++ b/src/Middleware/CheckAuthenticationMiddleware.php
@@ -59,10 +59,12 @@ class CheckAuthenticationMiddleware implements MiddlewareInterface
      */
     public function __invoke(Request $request, Response $response, callable $out = null)
     {
-        // If current route is the authenticate route, continue to the next middleware
+        // If current route is the authenticate route or an OPTIONS request, continue to the next middleware
         /** @var RouteResult $routeResult */
         $routeResult = $request->getAttribute(RouteResult::class);
-        if (isset($routeResult) && $routeResult->getMatchedRouteName() === 'rest-authenticate') {
+        if ((isset($routeResult) && $routeResult->getMatchedRouteName() === 'rest-authenticate')
+            || strtolower($request->getMethod()) === 'options'
+        ) {
             return $out($request, $response);
         }
 
diff --git a/src/Middleware/Rest/AbstractRestMiddleware.php b/src/Middleware/Rest/AbstractRestMiddleware.php
new file mode 100644
index 00000000..1168ff60
--- /dev/null
+++ b/src/Middleware/Rest/AbstractRestMiddleware.php
@@ -0,0 +1,51 @@
+<?php
+namespace Acelaya\UrlShortener\Middleware\Rest;
+
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Zend\Stratigility\MiddlewareInterface;
+
+abstract class AbstractRestMiddleware implements MiddlewareInterface
+{
+    /**
+     * 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)
+    {
+        if (strtolower($request->getMethod()) === 'options') {
+            return $response;
+        }
+
+        return $this->dispatch($request, $response, $out);
+    }
+
+    /**
+     * @param Request $request
+     * @param Response $response
+     * @param callable|null $out
+     * @return null|Response
+     */
+    abstract protected function dispatch(Request $request, Response $response, callable $out = null);
+}
diff --git a/src/Middleware/Rest/AuthenticateMiddleware.php b/src/Middleware/Rest/AuthenticateMiddleware.php
index 85d12330..88c9df60 100644
--- a/src/Middleware/Rest/AuthenticateMiddleware.php
+++ b/src/Middleware/Rest/AuthenticateMiddleware.php
@@ -9,9 +9,8 @@ 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 AuthenticateMiddleware implements MiddlewareInterface
+class AuthenticateMiddleware extends AbstractRestMiddleware
 {
     /**
      * @var RestTokenServiceInterface
@@ -30,36 +29,13 @@ class AuthenticateMiddleware implements MiddlewareInterface
     }
 
     /**
-     * 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
+     * @param callable|null $out
      * @return null|Response
      */
-    public function __invoke(Request $request, Response $response, callable $out = null)
+    public function dispatch(Request $request, Response $response, callable $out = null)
     {
-        if (strtolower($request->getMethod()) === 'options') {
-            return $response;
-        }
-
         $authData = $request->getParsedBody();
         if (! isset($authData['username'], $authData['password'])) {
             return new JsonResponse([
diff --git a/src/Middleware/Rest/CreateShortcodeMiddleware.php b/src/Middleware/Rest/CreateShortcodeMiddleware.php
index b68c551c..f5ee3228 100644
--- a/src/Middleware/Rest/CreateShortcodeMiddleware.php
+++ b/src/Middleware/Rest/CreateShortcodeMiddleware.php
@@ -10,9 +10,8 @@ use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
 use Zend\Diactoros\Response\JsonResponse;
 use Zend\Diactoros\Uri;
-use Zend\Stratigility\MiddlewareInterface;
 
-class CreateShortcodeMiddleware implements MiddlewareInterface
+class CreateShortcodeMiddleware extends AbstractRestMiddleware
 {
     /**
      * @var UrlShortener|UrlShortenerInterface
@@ -38,31 +37,12 @@ class CreateShortcodeMiddleware implements MiddlewareInterface
     }
 
     /**
-     * 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
+     * @param callable|null $out
      * @return null|Response
      */
-    public function __invoke(Request $request, Response $response, callable $out = null)
+    public function dispatch(Request $request, Response $response, callable $out = null)
     {
         $postData = $request->getParsedBody();
         if (! isset($postData['longUrl'])) {
diff --git a/src/Middleware/Rest/GetVisitsMiddleware.php b/src/Middleware/Rest/GetVisitsMiddleware.php
index 1a1b973b..a6ca954e 100644
--- a/src/Middleware/Rest/GetVisitsMiddleware.php
+++ b/src/Middleware/Rest/GetVisitsMiddleware.php
@@ -9,9 +9,8 @@ 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
+class GetVisitsMiddleware extends AbstractRestMiddleware
 {
     /**
      * @var VisitsTrackerInterface
@@ -30,31 +29,12 @@ class GetVisitsMiddleware implements MiddlewareInterface
     }
 
     /**
-     * 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
+     * @param callable|null $out
      * @return null|Response
      */
-    public function __invoke(Request $request, Response $response, callable $out = null)
+    public function dispatch(Request $request, Response $response, callable $out = null)
     {
         $shortCode = $request->getAttribute('shortCode');
 
diff --git a/src/Middleware/Rest/ListShortcodesMiddleware.php b/src/Middleware/Rest/ListShortcodesMiddleware.php
index 6a4a627f..6b74241c 100644
--- a/src/Middleware/Rest/ListShortcodesMiddleware.php
+++ b/src/Middleware/Rest/ListShortcodesMiddleware.php
@@ -10,9 +10,8 @@ use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
 use Zend\Diactoros\Response\JsonResponse;
 use Zend\Stdlib\ArrayUtils;
-use Zend\Stratigility\MiddlewareInterface;
 
-class ListShortcodesMiddleware implements MiddlewareInterface
+class ListShortcodesMiddleware extends AbstractRestMiddleware
 {
     use PaginatorSerializerTrait;
 
@@ -33,31 +32,12 @@ class ListShortcodesMiddleware implements MiddlewareInterface
     }
 
     /**
-     * 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
+     * @param callable|null $out
      * @return null|Response
      */
-    public function __invoke(Request $request, Response $response, callable $out = null)
+    public function dispatch(Request $request, Response $response, callable $out = null)
     {
         try {
             $query = $request->getQueryParams();
diff --git a/src/Middleware/Rest/ResolveUrlMiddleware.php b/src/Middleware/Rest/ResolveUrlMiddleware.php
index 1beee164..4529e973 100644
--- a/src/Middleware/Rest/ResolveUrlMiddleware.php
+++ b/src/Middleware/Rest/ResolveUrlMiddleware.php
@@ -9,9 +9,8 @@ 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 ResolveUrlMiddleware implements MiddlewareInterface
+class ResolveUrlMiddleware extends AbstractRestMiddleware
 {
     /**
      * @var UrlShortenerInterface
@@ -30,31 +29,12 @@ class ResolveUrlMiddleware implements MiddlewareInterface
     }
 
     /**
-     * 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
+     * @param callable|null $out
      * @return null|Response
      */
-    public function __invoke(Request $request, Response $response, callable $out = null)
+    public function dispatch(Request $request, Response $response, callable $out = null)
     {
         $shortCode = $request->getAttribute('shortCode');
 

From f691bb00d13d965046dbecc323a1f68f3a592ae0 Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Tue, 5 Jul 2016 19:28:47 +0200
Subject: [PATCH 17/18] Created rest documentation

---
 data/docs/rest.md | 278 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 278 insertions(+)
 create mode 100644 data/docs/rest.md

diff --git a/data/docs/rest.md b/data/docs/rest.md
new file mode 100644
index 00000000..f93f6c48
--- /dev/null
+++ b/data/docs/rest.md
@@ -0,0 +1,278 @@
+
+# REST API documentation
+
+## Error management
+
+Statuses:
+
+* 400 -> controlled error
+* 401 -> authentication error
+* 500 -> unexpected error
+
+[TODO]
+
+## Authentication
+
+[TODO]
+
+## Endpoints
+
+#### Authenticate
+
+**REQUEST**
+
+* `POST` -> `/rest/authenticate`
+* Params:
+    * username: `string`
+    * password: `string`
+
+**SUCCESS RESPONSE**
+
+```json
+{
+    "token": "9f741eb0-33d7-4c56-b8f7-3719e9929946"
+}
+```
+
+**ERROR RESPONSE**
+
+```json
+{
+    "error": "INVALID_ARGUMENT",
+    "message": "You have to provide both \"username\" and \"password\""
+}
+```
+
+Posible errors:
+
+* **INVALID_ARGUMENT**: Username or password were not provided. 
+* **INVALID_CREDENTIALS**: Username or password are incorrect. 
+
+
+#### Create shortcode
+
+**REQUEST**
+
+* `POST` -> `/rest/short-codes`
+* Params:
+    * longUrl: `string` -> The URL to shorten
+* Headers:
+    * X-Auth-Token: `string` -> The token provided in the authentication request
+    
+**SUCCESS RESPONSE**
+
+```json
+{
+    "longUrl": "https://www.facebook.com/something/something",
+    "shortUrl": "https://doma.in/rY9Kr",
+    "shortCode": "rY9Kr"
+}
+```
+
+**ERROR RESPONSE**
+
+```json
+{
+    "error": "INVALID_URL",
+    "message": "Provided URL \"wfwef\" is invalid. Try with a different one."
+}
+```
+
+Posible errors:
+
+* **INVALID_ARGUMENT**: The longUrl was not provided. 
+* **INVALID_URL**: Provided longUrl has an invalid format or does not resolve. 
+* **UNKNOWN_ERROR**: Something unexpected happened. 
+
+
+#### Resolve URL
+
+**REQUEST**
+
+* `GET` -> `/rest/short-codes/{shortCode}`
+* Route params:
+    * shortCode: `string` -> The short code we want to resolve
+* Headers:
+    * X-Auth-Token: `string` -> The token provided in the authentication request
+    
+**SUCCESS RESPONSE**
+
+```json
+{
+    "longUrl": "https://www.facebook.com/something/something"
+}
+```
+
+**ERROR RESPONSE**
+
+```json
+{
+    "error": "INVALID_SHORTCODE",
+    "message": "Provided short code \"abc123\" has an invalid format"
+}
+```
+
+Posible errors:
+
+* **INVALID_ARGUMENT**: No longUrl was found for provided shortCode. 
+* **INVALID_SHORTCODE**: Provided shortCode does not match the character set used by the app to generate short codes. 
+* **UNKNOWN_ERROR**: Something unexpected happened. 
+
+
+#### List shortened URLs
+
+**REQUEST**
+
+* `GET` -> `/rest/short-codes`
+* Query params:
+    * page: `integer` -> The page to list. Defaults to 1 if not provided.
+* Headers:
+    * X-Auth-Token: `string` -> The token provided in the authentication request
+    
+**SUCCESS RESPONSE**
+
+```json
+{
+    "shortUrls": {
+        "data": [
+            {
+                "shortCode": "abc123",
+                "originalUrl": "http://www.alejandrocelaya.com",
+                "dateCreated": "2016-04-30T18:01:47+0200",
+                "visitsCount": 4
+            },
+            {
+                "shortCode": "def456",
+                "originalUrl": "http://www.alejandrocelaya.com/en",
+                "dateCreated": "2016-04-30T18:03:43+0200",
+                "visitsCount": 0
+            },
+            {
+                "shortCode": "ghi789",
+                "originalUrl": "http://www.alejandrocelaya.com/es",
+                "dateCreated": "2016-04-30T18:10:38+0200",
+                "visitsCount": 0
+            },
+            {
+                "shortCode": "jkl987",
+                "originalUrl": "http://www.alejandrocelaya.com/es/",
+                "dateCreated": "2016-04-30T18:10:57+0200",
+                "visitsCount": 0
+            },
+            {
+                "shortCode": "mno654",
+                "originalUrl": "http://blog.alejandrocelaya.com/2016/04/09/improving-zend-service-manager-workflow-with-annotations/",
+                "dateCreated": "2016-04-30T19:21:05+0200",
+                "visitsCount": 1
+            },
+            {
+                "shortCode": "pqr321",
+                "originalUrl": "http://www.google.com",
+                "dateCreated": "2016-05-01T11:19:53+0200",
+                "visitsCount": 0
+            },
+            {
+                "shortCode": "stv159",
+                "originalUrl": "http://www.acelaya.com",
+                "dateCreated": "2016-06-12T17:49:21+0200",
+                "visitsCount": 0
+            },
+            {
+                "shortCode": "wxy753",
+                "originalUrl": "http://www.atomic-reader.com",
+                "dateCreated": "2016-06-12T17:50:27+0200",
+                "visitsCount": 0
+            },
+            {
+                "shortCode": "zab852",
+                "originalUrl": "http://foo.com",
+                "dateCreated": "2016-07-03T09:07:36+0200",
+                "visitsCount": 0
+            },
+            {
+                "shortCode": "cde963",
+                "originalUrl": "https://www.facebook.com.com",
+                "dateCreated": "2016-07-03T09:12:35+0200",
+                "visitsCount": 0
+            }
+        ],
+        "pagination": {
+            "currentPage": 4,
+            "pagesCount": 15
+        }
+    }
+}
+```
+
+**ERROR RESPONSE**
+
+```json
+{
+    "error": "UNKNOWN_ERROR",
+    "message": "Unexpected error occured"
+}
+```
+
+Posible errors:
+
+* **UNKNOWN_ERROR**: Something unexpected happened. 
+
+
+#### Get visits
+
+**REQUEST**
+
+* `GET` -> `/rest/visits/{shortCode}`
+* Route params:
+    * shortCode: `string` -> The shortCode from which we eant to get the visits.
+* Headers:
+    * X-Auth-Token: `string` -> The token provided in the authentication request
+    
+**SUCCESS RESPONSE**
+
+```json
+{
+    "shortUrls": {
+        "data": [
+            {
+                "referer": null,
+                "date": "2016-06-18T09:32:22+0200",
+                "remoteAddr": "127.0.0.1",
+                "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"
+            },
+            {
+                "referer": null,
+                "date": "2016-04-30T19:20:06+0200",
+                "remoteAddr": "127.0.0.1",
+                "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"
+            },
+            {
+                "referer": "google.com",
+                "date": "2016-04-30T19:19:57+0200",
+                "remoteAddr": "1.2.3.4",
+                "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"
+            },
+            {
+                "referer": null,
+                "date": "2016-04-30T19:17:35+0200",
+                "remoteAddr": "127.0.0.1",
+                "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"
+            }
+        ],
+    }
+}
+```
+
+**ERROR RESPONSE**
+
+```json
+{
+    "error": "INVALID_ARGUMENT",
+    "message": "Provided short code \"abc123\" is invalid"
+}
+```
+
+Posible errors:
+
+* **INVALID_ARGUMENT**: The shortcode does not belong to any short URL
+* **UNKNOWN_ERROR**: Something unexpected happened.

From 371e264ebeac10ab9c817d857362697cc72b9bde Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Tue, 5 Jul 2016 19:54:16 +0200
Subject: [PATCH 18/18] Removed PHP5.5 from travis environments

---
 .travis.yml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index 77f1332b..eb2ead28 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,7 +6,6 @@ branches:
     - develop
 
 php:
-  - 5.5
   - 5.6
   - 7
   - hhvm