diff --git a/composer.json b/composer.json index 374fed26..0211ed05 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,8 @@ { - "name": "acelaya/url-shortener", + "name": "shlinkio/shlink", "type": "project", - "homepage": "https://github.com/acelaya/url-shortener", + "homepage": "http://shlink.io", + "description": "A PHP-based URL shortener application with analytics and management", "license": "MIT", "authors": [ { @@ -19,6 +20,8 @@ "zendframework/zend-stdlib": "^2.7", "zendframework/zend-servicemanager": "^3.0", "zendframework/zend-paginator": "^2.6", + "zendframework/zend-config": "^2.6", + "mtymek/expressive-config-manager": "^0.4", "doctrine/orm": "^2.5", "guzzlehttp/guzzle": "^6.2", "acelaya/zsm-annotated-services": "^0.2.0", @@ -34,12 +37,18 @@ }, "autoload": { "psr-4": { - "Acelaya\\UrlShortener\\": "src" + "Shlinkio\\Shlink\\CLI\\": "module/CLI/src", + "Shlinkio\\Shlink\\Rest\\": "module/Rest/src", + "Shlinkio\\Shlink\\Core\\": "module/Core/src", + "Shlinkio\\Shlink\\Common\\": "module/Common/src" } }, "autoload-dev": { "psr-4": { - "AcelayaTest\\UrlShortener\\": "tests" + "ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test", + "ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test", + "ShlinkioTest\\Shlink\\Core\\": "module/Core/test", + "ShlinkioTest\\Shlink\\Common\\": "module/Common/test" } }, "scripts": { diff --git a/config/autoload/middleware-pipeline.global.php b/config/autoload/middleware-pipeline.global.php index fc6f85f0..ca116a95 100644 --- a/config/autoload/middleware-pipeline.global.php +++ b/config/autoload/middleware-pipeline.global.php @@ -1,5 +1,4 @@ 10, ], - 'rest' => [ - 'path' => '/rest', - 'middleware' => [ - Middleware\CheckAuthenticationMiddleware::class, - Middleware\CrossDomainMiddleware::class, - ], - 'priority' => 5, - ], - 'post-routing' => [ 'middleware' => [ Helper\UrlHelperMiddleware::class, diff --git a/config/autoload/services.global.php b/config/autoload/services.global.php index 4d6cc40e..dc99033c 100644 --- a/config/autoload/services.global.php +++ b/config/autoload/services.global.php @@ -1,13 +1,4 @@ [ 'factories' => [ Expressive\Application::class => Container\ApplicationFactory::class, - Console\Application::class => CLI\Factory\ApplicationFactory::class, // Url helpers Helper\UrlHelper::class => Helper\UrlHelperFactory::class, @@ -33,38 +23,10 @@ return [ // View 'Zend\Expressive\FinalHandler' => Container\TemplatedErrorHandlerFactory::class, Template\TemplateRendererInterface::class => Twig\TwigRendererFactory::class, - - // Services - EntityManager::class => EntityManagerFactory::class, - GuzzleHttp\Client::class => InvokableFactory::class, - 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 - CLI\Command\GenerateShortcodeCommand::class => AnnotatedFactory::class, - CLI\Command\ResolveUrlCommand::class => AnnotatedFactory::class, - CLI\Command\ListShortcodesCommand::class => AnnotatedFactory::class, - CLI\Command\GetVisitsCommand::class => AnnotatedFactory::class, - - // 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, - Middleware\Rest\ListShortcodesMiddleware::class => AnnotatedFactory::class, - Middleware\CrossDomainMiddleware::class => InvokableFactory::class, - Middleware\CheckAuthenticationMiddleware::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/config/autoload/templates.global.php b/config/autoload/templates.global.php index 2beb9918..f102baa0 100644 --- a/config/autoload/templates.global.php +++ b/config/autoload/templates.global.php @@ -2,12 +2,6 @@ return [ - 'templates' => [ - 'paths' => [ - 'templates' - ], - ], - 'twig' => [ 'cache_dir' => 'data/cache/twig', 'extensions' => [ diff --git a/config/autoload/zend-expressive.global.php b/config/autoload/zend-expressive.global.php index db5e3f38..aa2e9d3b 100644 --- a/config/autoload/zend-expressive.global.php +++ b/config/autoload/zend-expressive.global.php @@ -1,14 +1,8 @@ false, + 'config_cache_enabled' => true, - 'config_cache_enabled' => false, - - 'zend-expressive' => [ - 'error_handler' => [ - 'template_404' => 'error/404.html.twig', - 'template_error' => 'error/error.html.twig', - ], - ], ]; diff --git a/cli-config.php b/config/cli-config.php similarity index 83% rename from cli-config.php rename to config/cli-config.php index be9b8b82..30e31a93 100644 --- a/cli-config.php +++ b/config/cli-config.php @@ -4,7 +4,7 @@ use Doctrine\ORM\Tools\Console\ConsoleRunner; use Interop\Container\ContainerInterface; /** @var ContainerInterface $container */ -$container = include __DIR__ . '/config/container.php'; +$container = include __DIR__ . '/container.php'; /** @var EntityManager $em */ $em = $container->get(EntityManager::class); diff --git a/config/config.php b/config/config.php index a3d0e7ac..736b8fd4 100644 --- a/config/config.php +++ b/config/config.php @@ -1,6 +1,10 @@ getMergedConfig(); +}); diff --git a/config/autoload/cli.global.php b/module/CLI/config/cli.config.php similarity index 87% rename from config/autoload/cli.global.php rename to module/CLI/config/cli.config.php index 1276cc9e..6c34f19c 100644 --- a/config/autoload/cli.global.php +++ b/module/CLI/config/cli.config.php @@ -1,5 +1,5 @@ [ + 'factories' => [ + Console\Application::class => CLI\Factory\ApplicationFactory::class, + + CLI\Command\GenerateShortcodeCommand::class => AnnotatedFactory::class, + CLI\Command\ResolveUrlCommand::class => AnnotatedFactory::class, + CLI\Command\ListShortcodesCommand::class => AnnotatedFactory::class, + CLI\Command\GetVisitsCommand::class => AnnotatedFactory::class, + ], + ], + +]; diff --git a/src/CLI/Command/GenerateShortcodeCommand.php b/module/CLI/src/Command/GenerateShortcodeCommand.php similarity index 91% rename from src/CLI/Command/GenerateShortcodeCommand.php rename to module/CLI/src/Command/GenerateShortcodeCommand.php index ffc355db..91cbd228 100644 --- a/src/CLI/Command/GenerateShortcodeCommand.php +++ b/module/CLI/src/Command/GenerateShortcodeCommand.php @@ -1,16 +1,15 @@ factory = new ApplicationFactory(); + } + + /** + * @test + */ + public function serviceIsCreated() + { + $instance = $this->factory->__invoke($this->createServiceManager(), ''); + $this->assertInstanceOf(Application::class, $instance); + } + + /** + * @test + */ + public function allCommandsWhichAreServicesAreAdded() + { + $sm = $this->createServiceManager([ + 'commands' => [ + 'foo', + 'bar', + 'baz', + ], + ]); + $sm->setService('foo', $this->prophesize(Command::class)->reveal()); + $sm->setService('baz', $this->prophesize(Command::class)->reveal()); + + /** @var Application $instance */ + $instance = $this->factory->__invoke($sm, ''); + $this->assertInstanceOf(Application::class, $instance); + $this->assertCount(2, $instance->all()); + } + + protected function createServiceManager($config = []) + { + return new ServiceManager(['services' => [ + 'config' => [ + 'cli' => $config, + ], + ]]); + } +} diff --git a/module/Common/config/services.config.php b/module/Common/config/services.config.php new file mode 100644 index 00000000..b3c4e14d --- /dev/null +++ b/module/Common/config/services.config.php @@ -0,0 +1,24 @@ + [ + 'factories' => [ + EntityManager::class => EntityManagerFactory::class, + GuzzleHttp\Client::class => InvokableFactory::class, + Cache::class => CacheFactory::class, + ], + 'aliases' => [ + 'em' => EntityManager::class, + 'httpClient' => GuzzleHttp\Client::class, + AnnotatedFactory::CACHE_SERVICE => Cache::class, + ], + ], + +]; diff --git a/module/Common/src/ConfigProvider.php b/module/Common/src/ConfigProvider.php new file mode 100644 index 00000000..9af040c2 --- /dev/null +++ b/module/Common/src/ConfigProvider.php @@ -0,0 +1,13 @@ + [ + [ + 'name' => 'long-url-redirect', + 'path' => '/{shortCode}', + 'middleware' => RedirectMiddleware::class, + 'allowed_methods' => ['GET'], + ], + ], + +]; diff --git a/module/Core/config/services.config.php b/module/Core/config/services.config.php new file mode 100644 index 00000000..cab7ca41 --- /dev/null +++ b/module/Core/config/services.config.php @@ -0,0 +1,20 @@ + [ + 'factories' => [ + // Services + Service\UrlShortener::class => AnnotatedFactory::class, + Service\VisitsTracker::class => AnnotatedFactory::class, + Service\ShortUrlService::class => AnnotatedFactory::class, + + // Middleware + RedirectMiddleware::class => AnnotatedFactory::class, + ], + ], + +]; diff --git a/module/Core/config/templates.config.php b/module/Core/config/templates.config.php new file mode 100644 index 00000000..da3623c1 --- /dev/null +++ b/module/Core/config/templates.config.php @@ -0,0 +1,11 @@ + [ + 'paths' => [ + 'module/Core/templates', + ], + ], + +]; diff --git a/module/Core/config/zend-expressive.config.php b/module/Core/config/zend-expressive.config.php new file mode 100644 index 00000000..c5fefe8f --- /dev/null +++ b/module/Core/config/zend-expressive.config.php @@ -0,0 +1,12 @@ + [ + 'error_handler' => [ + 'template_404' => 'core/error/404.html.twig', + 'template_error' => 'core/error/error.html.twig', + ], + ], + +]; diff --git a/src/Middleware/Routable/RedirectMiddleware.php b/module/Core/src/Action/RedirectMiddleware.php similarity index 91% rename from src/Middleware/Routable/RedirectMiddleware.php rename to module/Core/src/Action/RedirectMiddleware.php index 85b42eb6..5b1907ca 100644 --- a/src/Middleware/Routable/RedirectMiddleware.php +++ b/module/Core/src/Action/RedirectMiddleware.php @@ -1,13 +1,13 @@ httpClient = $httpClient; $this->em = $em; - $this->chars = $chars; + $this->chars = empty($chars) ? self::DEFAULT_CHARS : $chars; } /** diff --git a/src/Service/UrlShortenerInterface.php b/module/Core/src/Service/UrlShortenerInterface.php similarity index 74% rename from src/Service/UrlShortenerInterface.php rename to module/Core/src/Service/UrlShortenerInterface.php index 6623f392..6cbb6723 100644 --- a/src/Service/UrlShortenerInterface.php +++ b/module/Core/src/Service/UrlShortenerInterface.php @@ -1,10 +1,10 @@ [ + 'rest' => [ + 'path' => '/rest', + 'middleware' => [ + Middleware\CheckAuthenticationMiddleware::class, + Middleware\CrossDomainMiddleware::class, + ], + 'priority' => 5, + ], + ], +]; diff --git a/config/autoload/rest.global.php b/module/Rest/config/rest.config.php similarity index 100% rename from config/autoload/rest.global.php rename to module/Rest/config/rest.config.php diff --git a/config/autoload/routes.global.php b/module/Rest/config/routes.config.php similarity index 54% rename from config/autoload/routes.global.php rename to module/Rest/config/routes.config.php index 87133f09..e8abb6fe 100644 --- a/config/autoload/routes.global.php +++ b/module/Rest/config/routes.config.php @@ -1,46 +1,37 @@ [ - [ - 'name' => 'long-url-redirect', - 'path' => '/{shortCode}', - 'middleware' => Routable\RedirectMiddleware::class, - 'allowed_methods' => ['GET'], - ], - - // Rest [ 'name' => 'rest-authenticate', 'path' => '/rest/authenticate', - 'middleware' => Rest\AuthenticateMiddleware::class, + 'middleware' => Action\AuthenticateMiddleware::class, 'allowed_methods' => ['POST', 'OPTIONS'], ], [ 'name' => 'rest-create-shortcode', 'path' => '/rest/short-codes', - 'middleware' => Rest\CreateShortcodeMiddleware::class, + 'middleware' => Action\CreateShortcodeMiddleware::class, 'allowed_methods' => ['POST', 'OPTIONS'], ], [ 'name' => 'rest-resolve-url', 'path' => '/rest/short-codes/{shortCode}', - 'middleware' => Rest\ResolveUrlMiddleware::class, + 'middleware' => Action\ResolveUrlMiddleware::class, 'allowed_methods' => ['GET', 'OPTIONS'], ], [ - 'name' => 'rest-list-shortened-url', + 'name' => 'rest-lActionist-shortened-url', 'path' => '/rest/short-codes', - 'middleware' => Rest\ListShortcodesMiddleware::class, + 'middleware' => Action\ListShortcodesMiddleware::class, 'allowed_methods' => ['GET'], ], [ 'name' => 'rest-get-visits', 'path' => '/rest/visits/{shortCode}', - 'middleware' => Rest\GetVisitsMiddleware::class, + 'middleware' => Action\GetVisitsMiddleware::class, 'allowed_methods' => ['GET', 'OPTIONS'], ], ], diff --git a/module/Rest/config/services.config.php b/module/Rest/config/services.config.php new file mode 100644 index 00000000..aff4cc96 --- /dev/null +++ b/module/Rest/config/services.config.php @@ -0,0 +1,25 @@ + [ + 'factories' => [ + Service\RestTokenService::class => AnnotatedFactory::class, + + Action\AuthenticateMiddleware::class => AnnotatedFactory::class, + Action\CreateShortcodeMiddleware::class => AnnotatedFactory::class, + Action\ResolveUrlMiddleware::class => AnnotatedFactory::class, + Action\GetVisitsMiddleware::class => AnnotatedFactory::class, + Action\ListShortcodesMiddleware::class => AnnotatedFactory::class, + + Middleware\CrossDomainMiddleware::class => InvokableFactory::class, + Middleware\CheckAuthenticationMiddleware::class => AnnotatedFactory::class, + ], + ], + +]; diff --git a/src/Middleware/Rest/AbstractRestMiddleware.php b/module/Rest/src/Action/AbstractRestMiddleware.php similarity index 97% rename from src/Middleware/Rest/AbstractRestMiddleware.php rename to module/Rest/src/Action/AbstractRestMiddleware.php index 1168ff60..9ef870f4 100644 --- a/src/Middleware/Rest/AbstractRestMiddleware.php +++ b/module/Rest/src/Action/AbstractRestMiddleware.php @@ -1,5 +1,5 @@ middleware = new CrossDomainMiddleware(); + } + + /** + * @test + */ + public function anyRequestIncludesTheAllowAccessHeader() + { + $response = $this->middleware->__invoke( + ServerRequestFactory::fromGlobals(), + new Response(), + function ($req, $resp) { + return $resp; + } + ); + + $headers = $response->getHeaders(); + $this->assertArrayHasKey('Access-Control-Allow-Origin', $headers); + $this->assertArrayNotHasKey('Access-Control-Allow-Headers', $headers); + } + + /** + * @test + */ + public function optionsRequestIncludesMoreHeaders() + { + $request = ServerRequestFactory::fromGlobals(['REQUEST_METHOD' => 'OPTIONS']); + + $response = $this->middleware->__invoke($request, new Response(), function ($req, $resp) { + return $resp; + }); + + $headers = $response->getHeaders(); + $this->assertArrayHasKey('Access-Control-Allow-Origin', $headers); + $this->assertArrayHasKey('Access-Control-Allow-Headers', $headers); + } +} diff --git a/phpcs.xml b/phpcs.xml index 51649659..ae134872 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -16,8 +16,7 @@ - src - tests + module config public/index.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0721b191..65d36865 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,16 @@ - - ./tests + + ./module/Common/test + + + ./module/Core/test + + + ./module/Rest/test + + + ./module/CLI/test diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php deleted file mode 100644 index d6bdc788..00000000 --- a/src/Exception/ExceptionInterface.php +++ /dev/null @@ -1,6 +0,0 @@ -