From b732f1df0db969d2a33104a17f3de6394f850b8b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya <alejandro@alejandrocelaya.com> Date: Mon, 12 Aug 2019 20:00:15 +0200 Subject: [PATCH] Moved IpGeolocation module to external library --- composer.json | 3 +- module/IpGeolocation/LICENSE | 21 --- module/IpGeolocation/README.md | 30 ---- .../config/dependencies.config.php | 46 ------ .../IpGeolocation/config/geolite2.config.php | 35 ----- module/IpGeolocation/src/ConfigProvider.php | 14 -- .../src/Exception/ExceptionInterface.php | 10 -- .../src/Exception/RuntimeException.php | 10 -- .../src/Exception/WrongIpException.php | 17 --- .../IpGeolocation/src/GeoLite2/DbUpdater.php | 106 -------------- .../src/GeoLite2/DbUpdaterInterface.php | 16 --- .../src/GeoLite2/GeoLite2Options.php | 46 ------ module/IpGeolocation/src/Model/Location.php | 80 ----------- .../src/Resolver/ChainIpLocationResolver.php | 37 ----- .../src/Resolver/EmptyIpLocationResolver.php | 18 --- .../src/Resolver/GeoLite2LocationResolver.php | 56 -------- .../src/Resolver/IpApiLocationResolver.php | 54 ------- .../Resolver/IpLocationResolverInterface.php | 15 -- .../IpGeolocation/test-resources/.gitignore | 1 - .../test-resources/GeoLite2-City.tar.gz | Bin 202 -> 0 bytes .../IpGeolocation/test/ConfigProviderTest.php | 28 ---- .../test/Exception/WrongIpExceptionTest.php | 32 ----- .../test/GeoLite2/DbUpdaterTest.php | 132 ------------------ .../Resolver/ChainIpLocationResolverTest.php | 77 ---------- .../Resolver/EmptyIpLocationResolverTest.php | 41 ------ .../Resolver/GeoLite2LocationResolverTest.php | 66 --------- .../Resolver/IpApiLocationResolverTest.php | 56 -------- phpunit.xml.dist | 3 - 28 files changed, 1 insertion(+), 1049 deletions(-) delete mode 100644 module/IpGeolocation/LICENSE delete mode 100644 module/IpGeolocation/README.md delete mode 100644 module/IpGeolocation/config/dependencies.config.php delete mode 100644 module/IpGeolocation/config/geolite2.config.php delete mode 100644 module/IpGeolocation/src/ConfigProvider.php delete mode 100644 module/IpGeolocation/src/Exception/ExceptionInterface.php delete mode 100644 module/IpGeolocation/src/Exception/RuntimeException.php delete mode 100644 module/IpGeolocation/src/Exception/WrongIpException.php delete mode 100644 module/IpGeolocation/src/GeoLite2/DbUpdater.php delete mode 100644 module/IpGeolocation/src/GeoLite2/DbUpdaterInterface.php delete mode 100644 module/IpGeolocation/src/GeoLite2/GeoLite2Options.php delete mode 100644 module/IpGeolocation/src/Model/Location.php delete mode 100644 module/IpGeolocation/src/Resolver/ChainIpLocationResolver.php delete mode 100644 module/IpGeolocation/src/Resolver/EmptyIpLocationResolver.php delete mode 100644 module/IpGeolocation/src/Resolver/GeoLite2LocationResolver.php delete mode 100644 module/IpGeolocation/src/Resolver/IpApiLocationResolver.php delete mode 100644 module/IpGeolocation/src/Resolver/IpLocationResolverInterface.php delete mode 100644 module/IpGeolocation/test-resources/.gitignore delete mode 100644 module/IpGeolocation/test-resources/GeoLite2-City.tar.gz delete mode 100644 module/IpGeolocation/test/ConfigProviderTest.php delete mode 100644 module/IpGeolocation/test/Exception/WrongIpExceptionTest.php delete mode 100644 module/IpGeolocation/test/GeoLite2/DbUpdaterTest.php delete mode 100644 module/IpGeolocation/test/Resolver/ChainIpLocationResolverTest.php delete mode 100644 module/IpGeolocation/test/Resolver/EmptyIpLocationResolverTest.php delete mode 100644 module/IpGeolocation/test/Resolver/GeoLite2LocationResolverTest.php delete mode 100644 module/IpGeolocation/test/Resolver/IpApiLocationResolverTest.php diff --git a/composer.json b/composer.json index 9c110af8..c1ed913e 100644 --- a/composer.json +++ b/composer.json @@ -35,6 +35,7 @@ "predis/predis": "^1.1", "shlinkio/shlink-common": "^1.0", "shlinkio/shlink-installer": "^1.2.1", + "shlinkio/shlink-ip-geolocation": "^1.0", "symfony/console": "^4.3", "symfony/filesystem": "^4.3", "symfony/lock": "^4.3", @@ -76,7 +77,6 @@ "Shlinkio\\Shlink\\Rest\\": "module/Rest/src", "Shlinkio\\Shlink\\Core\\": "module/Core/src", "Shlinkio\\Shlink\\EventDispatcher\\": "module/EventDispatcher/src", - "Shlinkio\\Shlink\\IpGeolocation\\": "module/IpGeolocation/src/", "Shlinkio\\Shlink\\PreviewGenerator\\": "module/PreviewGenerator/src/" }, "files": [ @@ -93,7 +93,6 @@ "module/Core/test-db" ], "ShlinkioTest\\Shlink\\EventDispatcher\\": "module/EventDispatcher/test", - "ShlinkioTest\\Shlink\\IpGeolocation\\": "module/IpGeolocation/test", "ShlinkioTest\\Shlink\\PreviewGenerator\\": "module/PreviewGenerator/test" } }, diff --git a/module/IpGeolocation/LICENSE b/module/IpGeolocation/LICENSE deleted file mode 100644 index 31778387..00000000 --- a/module/IpGeolocation/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2019 Alejandro Celaya - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/module/IpGeolocation/README.md b/module/IpGeolocation/README.md deleted file mode 100644 index 03922172..00000000 --- a/module/IpGeolocation/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Shlink IP Address Geolocation module - -Shlink module with tools to geolocate an IP address using different strategies. - -Most of the elements it provides require a [PSR-11] container, and it's easy to integrate on [expressive] applications thanks to the `ConfigProvider` it includes. - -## Install - -Install this library using composer: - - composer require shlinkio/shlink-ip-geolocation - -> This library is also an expressive module which provides its own `ConfigProvider`. Add it to your configuration to get everything automatically set up. - -## *TODO* - -```php -<?php -declare(strict_types=1); - -return [ - - 'geolite2' => [ - 'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb', - 'temp_dir' => sys_get_temp_dir(), - // 'download_from' => 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz', - ], - -]; -``` diff --git a/module/IpGeolocation/config/dependencies.config.php b/module/IpGeolocation/config/dependencies.config.php deleted file mode 100644 index 51474dd4..00000000 --- a/module/IpGeolocation/config/dependencies.config.php +++ /dev/null @@ -1,46 +0,0 @@ -<?php -declare(strict_types=1); - -namespace Shlinkio\Shlink\IpGeolocation; - -use GeoIp2\Database\Reader; -use GuzzleHttp\Client as GuzzleClient; -use Symfony\Component\Filesystem\Filesystem; -use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory; -use Zend\ServiceManager\Factory\InvokableFactory; - -return [ - - 'dependencies' => [ - 'factories' => [ - Resolver\IpApiLocationResolver::class => ConfigAbstractFactory::class, - Resolver\GeoLite2LocationResolver::class => ConfigAbstractFactory::class, - Resolver\EmptyIpLocationResolver::class => InvokableFactory::class, - Resolver\ChainIpLocationResolver::class => ConfigAbstractFactory::class, - - GeoLite2\GeoLite2Options::class => ConfigAbstractFactory::class, - GeoLite2\DbUpdater::class => ConfigAbstractFactory::class, - ], - 'aliases' => [ - Resolver\IpLocationResolverInterface::class => Resolver\ChainIpLocationResolver::class, - ], - ], - - ConfigAbstractFactory::class => [ - Resolver\IpApiLocationResolver::class => [GuzzleClient::class], - Resolver\GeoLite2LocationResolver::class => [Reader::class], - Resolver\ChainIpLocationResolver::class => [ - Resolver\GeoLite2LocationResolver::class, - Resolver\IpApiLocationResolver::class, - Resolver\EmptyIpLocationResolver::class, - ], - - GeoLite2\GeoLite2Options::class => ['config.geolite2'], - GeoLite2\DbUpdater::class => [ - GuzzleClient::class, - Filesystem::class, - GeoLite2\GeoLite2Options::class, - ], - ], - -]; diff --git a/module/IpGeolocation/config/geolite2.config.php b/module/IpGeolocation/config/geolite2.config.php deleted file mode 100644 index aa910a50..00000000 --- a/module/IpGeolocation/config/geolite2.config.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php -declare(strict_types=1); - -namespace Shlinkio\Shlink\IpGeolocation; - -use GeoIp2\Database\Reader; -use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory; -use Zend\ServiceManager\Proxy\LazyServiceFactory; - -return [ - - 'dependencies' => [ - 'factories' => [ - Reader::class => ConfigAbstractFactory::class, - ], - 'delegators' => [ - // The GeoLite2 db reader has to be lazy so that it does not try to load the DB file at app bootstrapping. - // By doing so, it would fail the first time shlink tries to download it. - Reader::class => [ - LazyServiceFactory::class, - ], - ], - - 'lazy_services' => [ - 'class_map' => [ - Reader::class => Reader::class, - ], - ], - ], - - ConfigAbstractFactory::class => [ - Reader::class => ['config.geolite2.db_location'], - ], - -]; diff --git a/module/IpGeolocation/src/ConfigProvider.php b/module/IpGeolocation/src/ConfigProvider.php deleted file mode 100644 index d4bf15c6..00000000 --- a/module/IpGeolocation/src/ConfigProvider.php +++ /dev/null @@ -1,14 +0,0 @@ -<?php -declare(strict_types=1); - -namespace Shlinkio\Shlink\IpGeolocation; - -use function Shlinkio\Shlink\Common\loadConfigFromGlob; - -class ConfigProvider -{ - public function __invoke(): array - { - return loadConfigFromGlob(__DIR__ . '/../config/{,*.}config.php'); - } -} diff --git a/module/IpGeolocation/src/Exception/ExceptionInterface.php b/module/IpGeolocation/src/Exception/ExceptionInterface.php deleted file mode 100644 index 12b2ccdc..00000000 --- a/module/IpGeolocation/src/Exception/ExceptionInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php -declare(strict_types=1); - -namespace Shlinkio\Shlink\IpGeolocation\Exception; - -use Throwable; - -interface ExceptionInterface extends Throwable -{ -} diff --git a/module/IpGeolocation/src/Exception/RuntimeException.php b/module/IpGeolocation/src/Exception/RuntimeException.php deleted file mode 100644 index d55a5af9..00000000 --- a/module/IpGeolocation/src/Exception/RuntimeException.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php -declare(strict_types=1); - -namespace Shlinkio\Shlink\IpGeolocation\Exception; - -use RuntimeException as SplRuntimeException; - -class RuntimeException extends SplRuntimeException implements ExceptionInterface -{ -} diff --git a/module/IpGeolocation/src/Exception/WrongIpException.php b/module/IpGeolocation/src/Exception/WrongIpException.php deleted file mode 100644 index 8314e662..00000000 --- a/module/IpGeolocation/src/Exception/WrongIpException.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php -declare(strict_types=1); - -namespace Shlinkio\Shlink\IpGeolocation\Exception; - -use Shlinkio\Shlink\IpGeolocation\Exception\RuntimeException; -use Throwable; - -use function sprintf; - -class WrongIpException extends RuntimeException implements ExceptionInterface -{ - public static function fromIpAddress($ipAddress, ?Throwable $prev = null): self - { - return new self(sprintf('Provided IP "%s" is invalid', $ipAddress), 0, $prev); - } -} diff --git a/module/IpGeolocation/src/GeoLite2/DbUpdater.php b/module/IpGeolocation/src/GeoLite2/DbUpdater.php deleted file mode 100644 index 8f8af889..00000000 --- a/module/IpGeolocation/src/GeoLite2/DbUpdater.php +++ /dev/null @@ -1,106 +0,0 @@ -<?php -declare(strict_types=1); - -namespace Shlinkio\Shlink\IpGeolocation\GeoLite2; - -use Fig\Http\Message\RequestMethodInterface as RequestMethod; -use GuzzleHttp\ClientInterface; -use GuzzleHttp\Exception\GuzzleException; -use GuzzleHttp\RequestOptions; -use PharData; -use Shlinkio\Shlink\IpGeolocation\Exception\RuntimeException; -use Symfony\Component\Filesystem\Exception as FilesystemException; -use Symfony\Component\Filesystem\Filesystem; -use Throwable; - -use function sprintf; - -class DbUpdater implements DbUpdaterInterface -{ - private const DB_COMPRESSED_FILE = 'GeoLite2-City.tar.gz'; - private const DB_DECOMPRESSED_FILE = 'GeoLite2-City.mmdb'; - - /** @var ClientInterface */ - private $httpClient; - /** @var Filesystem */ - private $filesystem; - /** @var GeoLite2Options */ - private $options; - - public function __construct(ClientInterface $httpClient, Filesystem $filesystem, GeoLite2Options $options) - { - $this->httpClient = $httpClient; - $this->filesystem = $filesystem; - $this->options = $options; - } - - /** - * @throws RuntimeException - */ - public function downloadFreshCopy(?callable $handleProgress = null): void - { - $tempDir = $this->options->getTempDir(); - $compressedFile = sprintf('%s/%s', $tempDir, self::DB_COMPRESSED_FILE); - - $this->downloadDbFile($compressedFile, $handleProgress); - $tempFullPath = $this->extractDbFile($compressedFile, $tempDir); - $this->copyNewDbFile($tempFullPath); - $this->deleteTempFiles([$compressedFile, $tempFullPath]); - } - - private function downloadDbFile(string $dest, ?callable $handleProgress = null): void - { - try { - $this->httpClient->request(RequestMethod::METHOD_GET, $this->options->getDownloadFrom(), [ - RequestOptions::SINK => $dest, - RequestOptions::PROGRESS => $handleProgress, - ]); - } catch (Throwable | GuzzleException $e) { - throw new RuntimeException( - 'An error occurred while trying to download a fresh copy of the GeoLite2 database', - 0, - $e - ); - } - } - - private function extractDbFile(string $compressedFile, string $tempDir): string - { - try { - $phar = new PharData($compressedFile); - $internalPathToDb = sprintf('%s/%s', $phar->getBasename(), self::DB_DECOMPRESSED_FILE); - $phar->extractTo($tempDir, $internalPathToDb, true); - - return sprintf('%s/%s', $tempDir, $internalPathToDb); - } catch (Throwable $e) { - throw new RuntimeException( - sprintf('An error occurred while trying to extract the GeoLite2 database from %s', $compressedFile), - 0, - $e - ); - } - } - - private function copyNewDbFile(string $from): void - { - try { - $this->filesystem->copy($from, $this->options->getDbLocation(), true); - } catch (FilesystemException\FileNotFoundException | FilesystemException\IOException $e) { - throw new RuntimeException('An error occurred while trying to copy GeoLite2 db file to destination', 0, $e); - } - } - - private function deleteTempFiles(array $files): void - { - try { - $this->filesystem->remove($files); - } catch (FilesystemException\IOException $e) { - // Ignore any error produced when trying to delete temp files - } - } - - public function databaseFileExists(): bool - { - return $this->filesystem->exists($this->options->getDbLocation()); - } -} diff --git a/module/IpGeolocation/src/GeoLite2/DbUpdaterInterface.php b/module/IpGeolocation/src/GeoLite2/DbUpdaterInterface.php deleted file mode 100644 index f787dca3..00000000 --- a/module/IpGeolocation/src/GeoLite2/DbUpdaterInterface.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -declare(strict_types=1); - -namespace Shlinkio\Shlink\IpGeolocation\GeoLite2; - -use Shlinkio\Shlink\IpGeolocation\Exception\RuntimeException; - -interface DbUpdaterInterface -{ - public function databaseFileExists(): bool; - - /** - * @throws RuntimeException - */ - public function downloadFreshCopy(?callable $handleProgress = null): void; -} diff --git a/module/IpGeolocation/src/GeoLite2/GeoLite2Options.php b/module/IpGeolocation/src/GeoLite2/GeoLite2Options.php deleted file mode 100644 index 63a7b5b7..00000000 --- a/module/IpGeolocation/src/GeoLite2/GeoLite2Options.php +++ /dev/null @@ -1,46 +0,0 @@ -<?php -declare(strict_types=1); - -namespace Shlinkio\Shlink\IpGeolocation\GeoLite2; - -use Zend\Stdlib\AbstractOptions; - -class GeoLite2Options extends AbstractOptions -{ - private $dbLocation = ''; - private $tempDir = ''; - private $downloadFrom = 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz'; - - public function getDbLocation(): string - { - return $this->dbLocation; - } - - protected function setDbLocation(string $dbLocation): self - { - $this->dbLocation = $dbLocation; - return $this; - } - - public function getTempDir(): string - { - return $this->tempDir; - } - - protected function setTempDir(string $tempDir): self - { - $this->tempDir = $tempDir; - return $this; - } - - public function getDownloadFrom(): string - { - return $this->downloadFrom; - } - - protected function setDownloadFrom(string $downloadFrom): self - { - $this->downloadFrom = $downloadFrom; - return $this; - } -} diff --git a/module/IpGeolocation/src/Model/Location.php b/module/IpGeolocation/src/Model/Location.php deleted file mode 100644 index a41e0cf4..00000000 --- a/module/IpGeolocation/src/Model/Location.php +++ /dev/null @@ -1,80 +0,0 @@ -<?php -declare(strict_types=1); - -namespace Shlinkio\Shlink\IpGeolocation\Model; - -final class Location -{ - /** @var string */ - private $countryCode; - /** @var string */ - private $countryName; - /** @var string */ - private $regionName; - /** @var string */ - private $city; - /** @var float */ - private $latitude; - /** @var float */ - private $longitude; - /** @var string */ - private $timeZone; - - public function __construct( - string $countryCode, - string $countryName, - string $regionName, - string $city, - float $latitude, - float $longitude, - string $timeZone - ) { - $this->countryCode = $countryCode; - $this->countryName = $countryName; - $this->regionName = $regionName; - $this->city = $city; - $this->latitude = $latitude; - $this->longitude = $longitude; - $this->timeZone = $timeZone; - } - - public static function emptyInstance(): self - { - return new self('', '', '', '', 0.0, 0.0, ''); - } - - public function countryCode(): string - { - return $this->countryCode; - } - - public function countryName(): string - { - return $this->countryName; - } - - public function regionName(): string - { - return $this->regionName; - } - - public function city(): string - { - return $this->city; - } - - public function latitude(): float - { - return $this->latitude; - } - - public function longitude(): float - { - return $this->longitude; - } - - public function timeZone(): string - { - return $this->timeZone; - } -} diff --git a/module/IpGeolocation/src/Resolver/ChainIpLocationResolver.php b/module/IpGeolocation/src/Resolver/ChainIpLocationResolver.php deleted file mode 100644 index 5efb729c..00000000 --- a/module/IpGeolocation/src/Resolver/ChainIpLocationResolver.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php -declare(strict_types=1); - -namespace Shlinkio\Shlink\IpGeolocation\Resolver; - -use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException; -use Shlinkio\Shlink\IpGeolocation\Model; - -class ChainIpLocationResolver implements IpLocationResolverInterface -{ - /** @var IpLocationResolverInterface[] */ - private $resolvers; - - public function __construct(IpLocationResolverInterface ...$resolvers) - { - $this->resolvers = $resolvers; - } - - /** - * @throws WrongIpException - */ - public function resolveIpLocation(string $ipAddress): Model\Location - { - $error = null; - - foreach ($this->resolvers as $resolver) { - try { - return $resolver->resolveIpLocation($ipAddress); - } catch (WrongIpException $e) { - $error = $e; - } - } - - // If this instruction is reached, it means no resolver was capable of resolving the address - throw WrongIpException::fromIpAddress($ipAddress, $error); - } -} diff --git a/module/IpGeolocation/src/Resolver/EmptyIpLocationResolver.php b/module/IpGeolocation/src/Resolver/EmptyIpLocationResolver.php deleted file mode 100644 index b0242f68..00000000 --- a/module/IpGeolocation/src/Resolver/EmptyIpLocationResolver.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php -declare(strict_types=1); - -namespace Shlinkio\Shlink\IpGeolocation\Resolver; - -use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException; -use Shlinkio\Shlink\IpGeolocation\Model; - -class EmptyIpLocationResolver implements IpLocationResolverInterface -{ - /** - * @throws WrongIpException - */ - public function resolveIpLocation(string $ipAddress): Model\Location - { - return Model\Location::emptyInstance(); - } -} diff --git a/module/IpGeolocation/src/Resolver/GeoLite2LocationResolver.php b/module/IpGeolocation/src/Resolver/GeoLite2LocationResolver.php deleted file mode 100644 index 725a733a..00000000 --- a/module/IpGeolocation/src/Resolver/GeoLite2LocationResolver.php +++ /dev/null @@ -1,56 +0,0 @@ -<?php -declare(strict_types=1); - -namespace Shlinkio\Shlink\IpGeolocation\Resolver; - -use GeoIp2\Database\Reader; -use GeoIp2\Exception\AddressNotFoundException; -use GeoIp2\Model\City; -use GeoIp2\Record\Subdivision; -use MaxMind\Db\Reader\InvalidDatabaseException; -use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException; -use Shlinkio\Shlink\IpGeolocation\Model; - -use function Functional\first; - -class GeoLite2LocationResolver implements IpLocationResolverInterface -{ - /** @var Reader */ - private $geoLiteDbReader; - - public function __construct(Reader $geoLiteDbReader) - { - $this->geoLiteDbReader = $geoLiteDbReader; - } - - /** - * @throws WrongIpException - */ - public function resolveIpLocation(string $ipAddress): Model\Location - { - try { - $city = $this->geoLiteDbReader->city($ipAddress); - return $this->mapFields($city); - } catch (AddressNotFoundException $e) { - throw WrongIpException::fromIpAddress($ipAddress, $e); - } catch (InvalidDatabaseException $e) { - throw new WrongIpException('Provided GeoLite2 db file is invalid', 0, $e); - } - } - - private function mapFields(City $city): Model\Location - { - /** @var Subdivision $region */ - $region = first($city->subdivisions); - - return new Model\Location( - $city->country->isoCode ?? '', - $city->country->name ?? '', - $region->name ?? '', - $city->city->name ?? '', - (float) ($city->location->latitude ?? ''), - (float) ($city->location->longitude ?? ''), - $city->location->timeZone ?? '' - ); - } -} diff --git a/module/IpGeolocation/src/Resolver/IpApiLocationResolver.php b/module/IpGeolocation/src/Resolver/IpApiLocationResolver.php deleted file mode 100644 index 7478a81f..00000000 --- a/module/IpGeolocation/src/Resolver/IpApiLocationResolver.php +++ /dev/null @@ -1,54 +0,0 @@ -<?php -declare(strict_types=1); - -namespace Shlinkio\Shlink\IpGeolocation\Resolver; - -use GuzzleHttp\Client; -use GuzzleHttp\Exception\GuzzleException; -use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; -use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException; -use Shlinkio\Shlink\IpGeolocation\Model; - -use function Shlinkio\Shlink\Common\json_decode; -use function sprintf; - -class IpApiLocationResolver implements IpLocationResolverInterface -{ - private const SERVICE_PATTERN = 'http://ip-api.com/json/%s'; - - /** @var Client */ - private $httpClient; - - public function __construct(Client $httpClient) - { - $this->httpClient = $httpClient; - } - - /** - * @throws WrongIpException - */ - public function resolveIpLocation(string $ipAddress): Model\Location - { - try { - $response = $this->httpClient->get(sprintf(self::SERVICE_PATTERN, $ipAddress)); - return $this->mapFields(json_decode((string) $response->getBody())); - } catch (GuzzleException $e) { - throw WrongIpException::fromIpAddress($ipAddress, $e); - } catch (InvalidArgumentException $e) { - throw new WrongIpException('IP-API returned invalid body while locating IP address', 0, $e); - } - } - - private function mapFields(array $entry): Model\Location - { - return new Model\Location( - (string) ($entry['countryCode'] ?? ''), - (string) ($entry['country'] ?? ''), - (string) ($entry['regionName'] ?? ''), - (string) ($entry['city'] ?? ''), - (float) ($entry['lat'] ?? 0.0), - (float) ($entry['lon'] ?? 0.0), - (string) ($entry['timezone'] ?? '') - ); - } -} diff --git a/module/IpGeolocation/src/Resolver/IpLocationResolverInterface.php b/module/IpGeolocation/src/Resolver/IpLocationResolverInterface.php deleted file mode 100644 index 36938b98..00000000 --- a/module/IpGeolocation/src/Resolver/IpLocationResolverInterface.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php -declare(strict_types=1); - -namespace Shlinkio\Shlink\IpGeolocation\Resolver; - -use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException; -use Shlinkio\Shlink\IpGeolocation\Model; - -interface IpLocationResolverInterface -{ - /** - * @throws WrongIpException - */ - public function resolveIpLocation(string $ipAddress): Model\Location; -} diff --git a/module/IpGeolocation/test-resources/.gitignore b/module/IpGeolocation/test-resources/.gitignore deleted file mode 100644 index 3ffd27ba..00000000 --- a/module/IpGeolocation/test-resources/.gitignore +++ /dev/null @@ -1 +0,0 @@ -geolite2-testing-db diff --git a/module/IpGeolocation/test-resources/GeoLite2-City.tar.gz b/module/IpGeolocation/test-resources/GeoLite2-City.tar.gz deleted file mode 100644 index c945d2954ff2b596fd58f14cfe460588e2d100f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 202 zcmb2|=3vn2dJ)aQ{Pxm$u0sk84Ta}zTlE80&i=_DyQ}0ir`YDywY<}0tA20OcyYl# zL*U}~-5gpgYQ*;GIe$r$_-og;O5)k+jW1?qS*Gs}v|gQeTzlc<Ba&;^7)5F13a7Sg zsf~X!@3383?-V8NziO*LEvwt`{_3xOt#@DM|E#K;H@mGu=55)}Pn(T*9hhuVduM-c z-OKr}mi$$BTY8Oi<*&*|#r5~<H|jZWof=!alWVDb&2J|LWN@#HY4*~05eym(3;@!s BU*-S+ diff --git a/module/IpGeolocation/test/ConfigProviderTest.php b/module/IpGeolocation/test/ConfigProviderTest.php deleted file mode 100644 index a6dbdcbd..00000000 --- a/module/IpGeolocation/test/ConfigProviderTest.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php -declare(strict_types=1); - -namespace ShlinkioTest\Shlink\IpGeolocation; - -use PHPUnit\Framework\TestCase; -use Shlinkio\Shlink\IpGeolocation\ConfigProvider; -use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory; - -class ConfigProviderTest extends TestCase -{ - /** @var ConfigProvider */ - private $configProvider; - - public function setUp(): void - { - $this->configProvider = new ConfigProvider(); - } - - /** @test */ - public function configIsReturned(): void - { - $config = $this->configProvider->__invoke(); - - $this->assertArrayHasKey('dependencies', $config); - $this->assertArrayHasKey(ConfigAbstractFactory::class, $config); - } -} diff --git a/module/IpGeolocation/test/Exception/WrongIpExceptionTest.php b/module/IpGeolocation/test/Exception/WrongIpExceptionTest.php deleted file mode 100644 index 0833763d..00000000 --- a/module/IpGeolocation/test/Exception/WrongIpExceptionTest.php +++ /dev/null @@ -1,32 +0,0 @@ -<?php -declare(strict_types=1); - -namespace ShlinkioTest\Shlink\IpGeolocation\Exception; - -use Exception; -use PHPUnit\Framework\TestCase; -use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException; - -class WrongIpExceptionTest extends TestCase -{ - /** @test */ - public function fromIpAddressProperlyCreatesExceptionWithoutPrev(): void - { - $e = WrongIpException::fromIpAddress('1.2.3.4'); - - $this->assertEquals('Provided IP "1.2.3.4" is invalid', $e->getMessage()); - $this->assertEquals(0, $e->getCode()); - $this->assertNull($e->getPrevious()); - } - - /** @test */ - public function fromIpAddressProperlyCreatesExceptionWithPrev(): void - { - $prev = new Exception('Previous error'); - $e = WrongIpException::fromIpAddress('1.2.3.4', $prev); - - $this->assertEquals('Provided IP "1.2.3.4" is invalid', $e->getMessage()); - $this->assertEquals(0, $e->getCode()); - $this->assertSame($prev, $e->getPrevious()); - } -} diff --git a/module/IpGeolocation/test/GeoLite2/DbUpdaterTest.php b/module/IpGeolocation/test/GeoLite2/DbUpdaterTest.php deleted file mode 100644 index 63777440..00000000 --- a/module/IpGeolocation/test/GeoLite2/DbUpdaterTest.php +++ /dev/null @@ -1,132 +0,0 @@ -<?php -declare(strict_types=1); - -namespace ShlinkioTest\Shlink\IpGeolocation\GeoLite2; - -use GuzzleHttp\ClientInterface; -use GuzzleHttp\Exception\ClientException; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; -use Shlinkio\Shlink\IpGeolocation\Exception\RuntimeException; -use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdater; -use Shlinkio\Shlink\IpGeolocation\GeoLite2\GeoLite2Options; -use Symfony\Component\Filesystem\Exception as FilesystemException; -use Symfony\Component\Filesystem\Filesystem; -use Zend\Diactoros\Response; - -class DbUpdaterTest extends TestCase -{ - /** @var DbUpdater */ - private $dbUpdater; - /** @var ObjectProphecy */ - private $httpClient; - /** @var ObjectProphecy */ - private $filesystem; - /** @var GeoLite2Options */ - private $options; - - public function setUp(): void - { - $this->httpClient = $this->prophesize(ClientInterface::class); - $this->filesystem = $this->prophesize(Filesystem::class); - $this->options = new GeoLite2Options([ - 'temp_dir' => __DIR__ . '/../../test-resources', - 'db_location' => 'db_location', - 'download_from' => '', - ]); - - $this->dbUpdater = new DbUpdater($this->httpClient->reveal(), $this->filesystem->reveal(), $this->options); - } - - /** @test */ - public function anExceptionIsThrownIfFreshDbCannotBeDownloaded(): void - { - $request = $this->httpClient->request(Argument::cetera())->willThrow(ClientException::class); - - $this->expectException(RuntimeException::class); - $this->expectExceptionCode(0); - $this->expectExceptionMessage( - 'An error occurred while trying to download a fresh copy of the GeoLite2 database' - ); - $request->shouldBeCalledOnce(); - - $this->dbUpdater->downloadFreshCopy(); - } - - /** @test */ - public function anExceptionIsThrownIfFreshDbCannotBeExtracted(): void - { - $this->options->tempDir = '__invalid__'; - - $request = $this->httpClient->request(Argument::cetera())->willReturn(new Response()); - - $this->expectException(RuntimeException::class); - $this->expectExceptionCode(0); - $this->expectExceptionMessage( - 'An error occurred while trying to extract the GeoLite2 database from __invalid__/GeoLite2-City.tar.gz' - ); - $request->shouldBeCalledOnce(); - - $this->dbUpdater->downloadFreshCopy(); - } - - /** - * @test - * @dataProvider provideFilesystemExceptions - */ - public function anExceptionIsThrownIfFreshDbCannotBeCopiedToDestination(string $e): void - { - $request = $this->httpClient->request(Argument::cetera())->willReturn(new Response()); - $copy = $this->filesystem->copy(Argument::cetera())->willThrow($e); - - $this->expectException(RuntimeException::class); - $this->expectExceptionCode(0); - $this->expectExceptionMessage('An error occurred while trying to copy GeoLite2 db file to destination'); - $request->shouldBeCalledOnce(); - $copy->shouldBeCalledOnce(); - - $this->dbUpdater->downloadFreshCopy(); - } - - public function provideFilesystemExceptions(): iterable - { - yield 'file not found' => [FilesystemException\FileNotFoundException::class]; - yield 'IO error' => [FilesystemException\IOException::class]; - } - - /** @test */ - public function noExceptionsAreThrownIfEverythingWorksFine(): void - { - $request = $this->httpClient->request(Argument::cetera())->willReturn(new Response()); - $copy = $this->filesystem->copy(Argument::cetera())->will(function () { - }); - $remove = $this->filesystem->remove(Argument::cetera())->will(function () { - }); - - $this->dbUpdater->downloadFreshCopy(); - - $request->shouldHaveBeenCalledOnce(); - $copy->shouldHaveBeenCalledOnce(); - $remove->shouldHaveBeenCalledOnce(); - } - - /** - * @test - * @dataProvider provideExists - */ - public function databaseFileExistsChecksIfTheFilesExistsInTheFilesystem(bool $expected): void - { - $exists = $this->filesystem->exists('db_location')->willReturn($expected); - - $result = $this->dbUpdater->databaseFileExists(); - - $this->assertEquals($expected, $result); - $exists->shouldHaveBeenCalledOnce(); - } - - public function provideExists(): iterable - { - return [[true], [false]]; - } -} diff --git a/module/IpGeolocation/test/Resolver/ChainIpLocationResolverTest.php b/module/IpGeolocation/test/Resolver/ChainIpLocationResolverTest.php deleted file mode 100644 index a95b60c0..00000000 --- a/module/IpGeolocation/test/Resolver/ChainIpLocationResolverTest.php +++ /dev/null @@ -1,77 +0,0 @@ -<?php -declare(strict_types=1); - -namespace ShlinkioTest\Shlink\IpGeolocation\Resolver; - -use PHPUnit\Framework\TestCase; -use Prophecy\Prophecy\ObjectProphecy; -use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException; -use Shlinkio\Shlink\IpGeolocation\Model\Location; -use Shlinkio\Shlink\IpGeolocation\Resolver\ChainIpLocationResolver; -use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface; - -class ChainIpLocationResolverTest extends TestCase -{ - /** @var ChainIpLocationResolver */ - private $resolver; - /** @var ObjectProphecy */ - private $firstInnerResolver; - /** @var ObjectProphecy */ - private $secondInnerResolver; - - public function setUp(): void - { - $this->firstInnerResolver = $this->prophesize(IpLocationResolverInterface::class); - $this->secondInnerResolver = $this->prophesize(IpLocationResolverInterface::class); - - $this->resolver = new ChainIpLocationResolver( - $this->firstInnerResolver->reveal(), - $this->secondInnerResolver->reveal() - ); - } - - /** @test */ - public function throwsExceptionWhenNoInnerResolverCanHandleTheResolution() - { - $ipAddress = '1.2.3.4'; - - $firstResolve = $this->firstInnerResolver->resolveIpLocation($ipAddress)->willThrow(WrongIpException::class); - $secondResolve = $this->secondInnerResolver->resolveIpLocation($ipAddress)->willThrow(WrongIpException::class); - - $this->expectException(WrongIpException::class); - $firstResolve->shouldBeCalledOnce(); - $secondResolve->shouldBeCalledOnce(); - - $this->resolver->resolveIpLocation($ipAddress); - } - - /** @test */ - public function returnsResultOfFirstInnerResolver(): void - { - $ipAddress = '1.2.3.4'; - - $firstResolve = $this->firstInnerResolver->resolveIpLocation($ipAddress)->willReturn(Location::emptyInstance()); - $secondResolve = $this->secondInnerResolver->resolveIpLocation($ipAddress)->willThrow(WrongIpException::class); - - $this->resolver->resolveIpLocation($ipAddress); - - $firstResolve->shouldHaveBeenCalledOnce(); - $secondResolve->shouldNotHaveBeenCalled(); - } - - /** @test */ - public function returnsResultOfSecondInnerResolver(): void - { - $ipAddress = '1.2.3.4'; - - $firstResolve = $this->firstInnerResolver->resolveIpLocation($ipAddress)->willThrow(WrongIpException::class); - $secondResolve = $this->secondInnerResolver->resolveIpLocation($ipAddress)->willReturn( - Location::emptyInstance() - ); - - $this->resolver->resolveIpLocation($ipAddress); - - $firstResolve->shouldHaveBeenCalledOnce(); - $secondResolve->shouldHaveBeenCalledOnce(); - } -} diff --git a/module/IpGeolocation/test/Resolver/EmptyIpLocationResolverTest.php b/module/IpGeolocation/test/Resolver/EmptyIpLocationResolverTest.php deleted file mode 100644 index 6dda0388..00000000 --- a/module/IpGeolocation/test/Resolver/EmptyIpLocationResolverTest.php +++ /dev/null @@ -1,41 +0,0 @@ -<?php -declare(strict_types=1); - -namespace ShlinkioTest\Shlink\IpGeolocation\Resolver; - -use PHPUnit\Framework\TestCase; -use Shlinkio\Shlink\Common\Util\StringUtilsTrait; -use Shlinkio\Shlink\IpGeolocation\Model\Location; -use Shlinkio\Shlink\IpGeolocation\Resolver\EmptyIpLocationResolver; - -use function Functional\map; -use function range; - -class EmptyIpLocationResolverTest extends TestCase -{ - use StringUtilsTrait; - - /** @var EmptyIpLocationResolver */ - private $resolver; - - public function setUp(): void - { - $this->resolver = new EmptyIpLocationResolver(); - } - - /** - * @test - * @dataProvider provideEmptyResponses - */ - public function alwaysReturnsAnEmptyLocation(string $ipAddress): void - { - $this->assertEquals(Location::emptyInstance(), $this->resolver->resolveIpLocation($ipAddress)); - } - - public function provideEmptyResponses(): array - { - return map(range(0, 5), function () { - return [$this->generateRandomString(15)]; - }); - } -} diff --git a/module/IpGeolocation/test/Resolver/GeoLite2LocationResolverTest.php b/module/IpGeolocation/test/Resolver/GeoLite2LocationResolverTest.php deleted file mode 100644 index 806f2759..00000000 --- a/module/IpGeolocation/test/Resolver/GeoLite2LocationResolverTest.php +++ /dev/null @@ -1,66 +0,0 @@ -<?php -declare(strict_types=1); - -namespace ShlinkioTest\Shlink\IpGeolocation\Resolver; - -use GeoIp2\Database\Reader; -use GeoIp2\Exception\AddressNotFoundException; -use GeoIp2\Model\City; -use MaxMind\Db\Reader\InvalidDatabaseException; -use PHPUnit\Framework\TestCase; -use Prophecy\Prophecy\ObjectProphecy; -use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException; -use Shlinkio\Shlink\IpGeolocation\Model\Location; -use Shlinkio\Shlink\IpGeolocation\Resolver\GeoLite2LocationResolver; - -class GeoLite2LocationResolverTest extends TestCase -{ - /** @var GeoLite2LocationResolver */ - private $resolver; - /** @var ObjectProphecy */ - private $reader; - - public function setUp(): void - { - $this->reader = $this->prophesize(Reader::class); - $this->resolver = new GeoLite2LocationResolver($this->reader->reveal()); - } - - /** - * @test - * @dataProvider provideReaderExceptions - */ - public function exceptionIsThrownIfReaderThrowsException(string $e, string $message): void - { - $ipAddress = '1.2.3.4'; - - $cityMethod = $this->reader->city($ipAddress)->willThrow($e); - - $this->expectException(WrongIpException::class); - $this->expectExceptionMessage($message); - $this->expectExceptionCode(0); - $cityMethod->shouldBeCalledOnce(); - - $this->resolver->resolveIpLocation($ipAddress); - } - - public function provideReaderExceptions(): iterable - { - yield 'invalid IP address' => [AddressNotFoundException::class, 'Provided IP "1.2.3.4" is invalid']; - yield 'invalid geolite DB' => [InvalidDatabaseException::class, 'Provided GeoLite2 db file is invalid']; - } - - /** @test */ - public function resolvedCityIsProperlyMapped(): void - { - $ipAddress = '1.2.3.4'; - $city = new City([]); - - $cityMethod = $this->reader->city($ipAddress)->willReturn($city); - - $result = $this->resolver->resolveIpLocation($ipAddress); - - $this->assertEquals(Location::emptyInstance(), $result); - $cityMethod->shouldHaveBeenCalledOnce(); - } -} diff --git a/module/IpGeolocation/test/Resolver/IpApiLocationResolverTest.php b/module/IpGeolocation/test/Resolver/IpApiLocationResolverTest.php deleted file mode 100644 index a87edf98..00000000 --- a/module/IpGeolocation/test/Resolver/IpApiLocationResolverTest.php +++ /dev/null @@ -1,56 +0,0 @@ -<?php -declare(strict_types=1); - -namespace ShlinkioTest\Shlink\IpGeolocation\Resolver; - -use GuzzleHttp\Client; -use GuzzleHttp\Exception\TransferException; -use GuzzleHttp\Psr7\Response; -use PHPUnit\Framework\TestCase; -use Prophecy\Prophecy\ObjectProphecy; -use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException; -use Shlinkio\Shlink\IpGeolocation\Model\Location; -use Shlinkio\Shlink\IpGeolocation\Resolver\IpApiLocationResolver; - -use function json_encode; - -class IpApiLocationResolverTest extends TestCase -{ - /** @var IpApiLocationResolver */ - private $ipResolver; - /** @var ObjectProphecy */ - private $client; - - public function setUp(): void - { - $this->client = $this->prophesize(Client::class); - $this->ipResolver = new IpApiLocationResolver($this->client->reveal()); - } - - /** @test */ - public function correctIpReturnsDecodedInfo(): void - { - $actual = [ - 'countryCode' => 'bar', - 'lat' => 5, - 'lon' => 10, - ]; - $expected = new Location('bar', '', '', '', 5, 10, ''); - $response = new Response(); - $response->getBody()->write(json_encode($actual)); - $response->getBody()->rewind(); - - $this->client->get('http://ip-api.com/json/1.2.3.4')->willReturn($response) - ->shouldBeCalledOnce(); - $this->assertEquals($expected, $this->ipResolver->resolveIpLocation('1.2.3.4')); - } - - /** @test */ - public function guzzleExceptionThrowsShlinkException(): void - { - $this->client->get('http://ip-api.com/json/1.2.3.4')->willThrow(new TransferException()) - ->shouldBeCalledOnce(); - $this->expectException(WrongIpException::class); - $this->ipResolver->resolveIpLocation('1.2.3.4'); - } -} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 516a48fc..bb78e85b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -18,9 +18,6 @@ <testsuite name="EventDispatcher"> <directory>./module/EventDispatcher/test</directory> </testsuite> - <testsuite name="IpGeolocation"> - <directory>./module/IpGeolocation/test</directory> - </testsuite> <testsuite name="PreviewGenerator"> <directory>./module/PreviewGenerator/test</directory> </testsuite>