From dfd7b4f95e8b62137d9d18b20e7d527f3f898622 Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Fri, 25 Mar 2022 15:20:15 -0300 Subject: [PATCH 01/19] Adding GuzzleHttp as required in composer --- composer.json | 3 +- composer.lock | 490 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 490 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index fdf66ef..529961d 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,8 @@ "league/route": "^5.1", "laminas/laminas-diactoros": "^2.8", "vlucas/phpdotenv": "^5.4", - "laminas/laminas-httphandlerrunner": "^2.1" + "laminas/laminas-httphandlerrunner": "^2.1", + "guzzlehttp/guzzle": "^7.4" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 6e84003..c29001b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "93826a3efa62a15b156fde1723e8d9b4", + "content-hash": "b0393c660755ccfb98e3ce1a1c27267b", "packages": [ { "name": "doctrine/instantiator", @@ -129,6 +129,329 @@ ], "time": "2021-11-21T21:41:47+00:00" }, + { + "name": "guzzlehttp/guzzle", + "version": "7.4.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ac1ec1cd9b5624694c3a40be801d94137afb12b4", + "reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.8.3 || ^2.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.4-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.4.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2022-03-20T14:16:28+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:56:57+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c94a94f120803a18554c1805ef2e539f8285f9a2", + "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.2.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2022-03-20T21:55:58+00:00" + }, { "name": "illuminate/container", "version": "v9.0.0", @@ -1495,6 +1818,58 @@ ], "time": "2021-11-05T16:47:00+00:00" }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, { "name": "psr/http-factory", "version": "1.0.1", @@ -1751,6 +2126,50 @@ ], "time": "2017-10-23T01:57:42+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, { "name": "sebastian/cli-parser", "version": "1.0.1", @@ -2651,6 +3070,73 @@ ], "time": "2020-09-28T06:39:44+00:00" }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", + "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-01T23:48:49+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.24.0", @@ -3075,5 +3561,5 @@ "prefer-lowest": false, "platform": [], "platform-dev": [], - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.0.0" } From 061fecea4c9897a70350d47406b7d6ae5488d513 Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Fri, 25 Mar 2022 15:30:24 -0300 Subject: [PATCH 02/19] Conector for calling API's with Basic Authentication --- src/Connector/ApiConnector.php | 113 +++++++++++++++++++++++ src/Connector/HttpConnectorInterface.php | 30 ++++++ 2 files changed, 143 insertions(+) create mode 100644 src/Connector/ApiConnector.php create mode 100644 src/Connector/HttpConnectorInterface.php diff --git a/src/Connector/ApiConnector.php b/src/Connector/ApiConnector.php new file mode 100644 index 0000000..55d8a2d --- /dev/null +++ b/src/Connector/ApiConnector.php @@ -0,0 +1,113 @@ +baseApiUrl = $baseApiUrl; + $this->client = $client; + } + + /** + * @param string $username + * @return ApiConnector + */ + public function setUsername(string $username): ApiConnector + { + $this->username = $username; + return $this; + } + + /** + * @param string $password + * @return ApiConnector + */ + public function setPassword(string $password): ApiConnector + { + $this->password = $password; + return $this; + } + + /** + * @param ClientInterface $client + * @return ApiConnector + */ + public function setClient(ClientInterface $client): ApiConnector + { + $this->client = $client; + return $this; + } + + /** + * @param string $relativeUrl + * @param array $headers + * @param array $data + * @return array + * @throws GuzzleException + */ + #[ArrayShape(['result' => "string"])] public function doGet(string $relativeUrl, array $headers, array $data): array + { + $queryString = empty($data) ? '' : '?' . http_build_query($data); + return ['result' => $this->doRequest('GET', $relativeUrl . $queryString, $headers, [])->getBody() + ->getContents()]; + } + + /** + * @param string $relativeUrl + * @param array $headers + * @param array $data + * @return array + * @throws GuzzleException + */ + #[ArrayShape(['result' => "string"])] public function doPost(string $relativeUrl, array $headers, array $data): array + { + return ['result' => $this->doRequest('POST', $relativeUrl, $headers, $data)->getBody() + ->getContents()]; + } + + /** + * @param string $method + * @param string $relativeUrl + * @param array $headers + * @param array $data + * @return ResponseInterface + * @throws GuzzleException + */ + protected function doRequest(string $method, string $relativeUrl, array $headers, array $data): ResponseInterface + { + $options['headers'] = array_merge($this->getAuthorization(), $headers); + $options['body'] = json_encode($data); + return $this->client->request($method, $this->baseApiUrl . $relativeUrl, $options); + } + + /** + * @return string[][] + */ + + #[ArrayShape(['Authorization' => "string[]"])] protected function getAuthorization(): array + { + $credentials = base64_encode($this->username . ':' . $this->password); + return [ + 'Authorization' => ['Basic '.$credentials] + ]; + } +} \ No newline at end of file diff --git a/src/Connector/HttpConnectorInterface.php b/src/Connector/HttpConnectorInterface.php new file mode 100644 index 0000000..cae899c --- /dev/null +++ b/src/Connector/HttpConnectorInterface.php @@ -0,0 +1,30 @@ + Date: Fri, 25 Mar 2022 15:49:33 -0300 Subject: [PATCH 03/19] default values for headers and data in get & post requests --- src/Connector/ApiConnector.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Connector/ApiConnector.php b/src/Connector/ApiConnector.php index 55d8a2d..65abb00 100644 --- a/src/Connector/ApiConnector.php +++ b/src/Connector/ApiConnector.php @@ -64,7 +64,7 @@ public function setClient(ClientInterface $client): ApiConnector * @return array * @throws GuzzleException */ - #[ArrayShape(['result' => "string"])] public function doGet(string $relativeUrl, array $headers, array $data): array + #[ArrayShape(['result' => "string"])] public function doGet(string $relativeUrl, array $headers = [], array $data = []): array { $queryString = empty($data) ? '' : '?' . http_build_query($data); return ['result' => $this->doRequest('GET', $relativeUrl . $queryString, $headers, [])->getBody() @@ -78,7 +78,7 @@ public function setClient(ClientInterface $client): ApiConnector * @return array * @throws GuzzleException */ - #[ArrayShape(['result' => "string"])] public function doPost(string $relativeUrl, array $headers, array $data): array + #[ArrayShape(['result' => "string"])] public function doPost(string $relativeUrl, array $headers = [], array $data = []): array { return ['result' => $this->doRequest('POST', $relativeUrl, $headers, $data)->getBody() ->getContents()]; From e9f5984ce6bcf5c98a34b2cf2ebcd5e4031f60e0 Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Fri, 25 Mar 2022 15:59:15 -0300 Subject: [PATCH 04/19] rename Journal Api Connector --- .../{ApiConnector.php => JournalApiConnector.php} | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) rename src/Connector/{ApiConnector.php => JournalApiConnector.php} (87%) diff --git a/src/Connector/ApiConnector.php b/src/Connector/JournalApiConnector.php similarity index 87% rename from src/Connector/ApiConnector.php rename to src/Connector/JournalApiConnector.php index 65abb00..8e43beb 100644 --- a/src/Connector/ApiConnector.php +++ b/src/Connector/JournalApiConnector.php @@ -9,7 +9,7 @@ use JetBrains\PhpStorm\ArrayShape; use Psr\Http\Message\ResponseInterface; -class ApiConnector implements HttpConnectorInterface +class JournalApiConnector implements HttpConnectorInterface { private string $baseApiUrl; private ClientInterface $client; @@ -29,9 +29,9 @@ public function __construct(string $baseApiUrl, ClientInterface $client) /** * @param string $username - * @return ApiConnector + * @return JournalApiConnector */ - public function setUsername(string $username): ApiConnector + public function setUsername(string $username): JournalApiConnector { $this->username = $username; return $this; @@ -39,9 +39,9 @@ public function setUsername(string $username): ApiConnector /** * @param string $password - * @return ApiConnector + * @return JournalApiConnector */ - public function setPassword(string $password): ApiConnector + public function setPassword(string $password): JournalApiConnector { $this->password = $password; return $this; @@ -49,9 +49,9 @@ public function setPassword(string $password): ApiConnector /** * @param ClientInterface $client - * @return ApiConnector + * @return JournalApiConnector */ - public function setClient(ClientInterface $client): ApiConnector + public function setClient(ClientInterface $client): JournalApiConnector { $this->client = $client; return $this; From 0eead9209a4abbc838a0795e8749c37e4e9ed077 Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Fri, 25 Mar 2022 16:18:21 -0300 Subject: [PATCH 05/19] Data source classes --- src/Service/RiverApiDataSource.php | 35 ++++++++++++++++++++ src/Service/RiverDatasourceInterface.php | 10 ++++++ src/Service/RiverFileDataSource.php | 42 ++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 src/Service/RiverApiDataSource.php create mode 100644 src/Service/RiverDatasourceInterface.php create mode 100644 src/Service/RiverFileDataSource.php diff --git a/src/Service/RiverApiDataSource.php b/src/Service/RiverApiDataSource.php new file mode 100644 index 0000000..45fad3a --- /dev/null +++ b/src/Service/RiverApiDataSource.php @@ -0,0 +1,35 @@ +apiConnector = $apiConnector; + } + + /** + * @throws GuzzleException + */ + public function getArticlesByPublication(string $publicationName, array $extraOptions = []) + { + $response = $this->apiConnector->doGet(DIRECTORY_SEPARATOR . $publicationName); + return $response['result'] ?? []; + } + + /** + * @throws GuzzleException + */ + public function getArticlesByTag(string $tagName, array $extraOptions = []) + { + $response = $this->apiConnector->doGet(DIRECTORY_SEPARATOR . $tagName); + return $response['result'] ?? []; + } +} \ No newline at end of file diff --git a/src/Service/RiverDatasourceInterface.php b/src/Service/RiverDatasourceInterface.php new file mode 100644 index 0000000..1d28fef --- /dev/null +++ b/src/Service/RiverDatasourceInterface.php @@ -0,0 +1,10 @@ +riverSourceBasePath = $riverSourceBasePath; + } + + public function getArticlesByPublication(string $publicationName, array $extraOptions = []) + { + $resourcePath = $this->riverSourceBasePath . DIRECTORY_SEPARATOR . + $publicationName . '.' . self::RESOURCES_EXTENSION; + return $this->getAndParseDataFromFile($resourcePath); + } + + public function getArticlesByTag(string $tagName, array $extraOptions = []) + { + $resourcePath = $this->riverSourceBasePath . DIRECTORY_SEPARATOR . + $tagName . '.' . self::RESOURCES_EXTENSION; + return $this->getAndParseDataFromFile($resourcePath); + } + + /** + * @param string $resourcePath + * @return array|mixed + */ + private function getAndParseDataFromFile(string $resourcePath): mixed + { + if ($result = file_get_contents($resourcePath)) { + return json_decode($result, true); + } else { + return []; + } + } +} \ No newline at end of file From b6d96e07e0725b48c682380b947e5865669e1a9c Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Fri, 25 Mar 2022 18:18:14 -0300 Subject: [PATCH 06/19] Services to get river data depending of debug mode --- src/Service/RiverApiDataSource.php | 2 +- src/Service/RiverDataSource.php | 30 +++++++++++++++++++ ...rface.php => RiverDataSourceInterface.php} | 2 +- src/Service/RiverFileDataSource.php | 2 +- 4 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 src/Service/RiverDataSource.php rename src/Service/{RiverDatasourceInterface.php => RiverDataSourceInterface.php} (87%) diff --git a/src/Service/RiverApiDataSource.php b/src/Service/RiverApiDataSource.php index 45fad3a..e9af42a 100644 --- a/src/Service/RiverApiDataSource.php +++ b/src/Service/RiverApiDataSource.php @@ -5,7 +5,7 @@ use GuzzleHttp\Exception\GuzzleException; use JournalMedia\Sample\ApiProject\Connector\JournalApiConnector; -class RiverApiDataSource implements RiverDatasourceInterface +class RiverApiDataSource implements RiverDataSourceInterface { private JournalApiConnector $apiConnector; diff --git a/src/Service/RiverDataSource.php b/src/Service/RiverDataSource.php new file mode 100644 index 0000000..cf9f3f1 --- /dev/null +++ b/src/Service/RiverDataSource.php @@ -0,0 +1,30 @@ +isDemoMode = $isDemoMode; + $this->riverApiDataSource = $riverApiDataSource; + $this->riverFileDataSource = $riverFileDataSource; + } + + public function get(): RiverDataSourceInterface + { + if ($this->isDemoMode) { + return $this->riverFileDataSource; + } else { + return $this->riverApiDataSource; + } + } +} \ No newline at end of file diff --git a/src/Service/RiverDatasourceInterface.php b/src/Service/RiverDataSourceInterface.php similarity index 87% rename from src/Service/RiverDatasourceInterface.php rename to src/Service/RiverDataSourceInterface.php index 1d28fef..fc71331 100644 --- a/src/Service/RiverDatasourceInterface.php +++ b/src/Service/RiverDataSourceInterface.php @@ -2,7 +2,7 @@ namespace JournalMedia\Sample\ApiProject\Service; -interface RiverDatasourceInterface +interface RiverDataSourceInterface { public function getArticlesByPublication(string $publicationName, array $extraOptions = []); diff --git a/src/Service/RiverFileDataSource.php b/src/Service/RiverFileDataSource.php index 1771c93..21d1351 100644 --- a/src/Service/RiverFileDataSource.php +++ b/src/Service/RiverFileDataSource.php @@ -2,7 +2,7 @@ namespace JournalMedia\Sample\ApiProject\Service; -class RiverFileDataSource implements RiverDatasourceInterface +class RiverFileDataSource implements RiverDataSourceInterface { const RESOURCES_EXTENSION = 'json'; From 97ee9832ab6d91ca5af3be318b5053cc169c370d Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Fri, 25 Mar 2022 18:19:12 -0300 Subject: [PATCH 07/19] Adding Services configuration to the service provider --- src/Http/ServiceProvider.php | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/Http/ServiceProvider.php b/src/Http/ServiceProvider.php index 73ed5c3..c89670b 100644 --- a/src/Http/ServiceProvider.php +++ b/src/Http/ServiceProvider.php @@ -3,9 +3,14 @@ namespace JournalMedia\Sample\ApiProject\Http; +use GuzzleHttp\Client; use Illuminate\Container\Container; +use JournalMedia\Sample\ApiProject\Connector\JournalApiConnector; use JournalMedia\Sample\ApiProject\Http\Controller\PublicationRiverController; use JournalMedia\Sample\ApiProject\Http\Controller\TagRiverController; +use JournalMedia\Sample\ApiProject\Service\RiverApiDataSource; +use JournalMedia\Sample\ApiProject\Service\RiverDataSource; +use JournalMedia\Sample\ApiProject\Service\RiverFileDataSource; use League\Route\Router; final class ServiceProvider @@ -20,5 +25,38 @@ public function register(Container $container): void return $router; }); + + $container->singleton(PublicationRiverController::class, function ($container) { + return new PublicationRiverController($container[RiverDataSource::class]); + }); + + $container->singleton(TagRiverController::class, function ($container) { + return new TagRiverController($container[RiverDataSource::class]); + }); + + $container->singleton(Client::class, function ($container) { + return new Client(); + }); + + $container->singleton(JournalApiConnector::class, function($container) { + return new JournalApiConnector($_ENV['THE_JOURNAL_API_BASE_URL'], $container[Client::class]); + }); + + $container->singleton(RiverApiDataSource::class, function ($container) { + return new RiverApiDataSource($container[JournalApiConnector::class]); + }); + + $container->singleton(RiverFileDataSource::class, function ($container) { + $projectFullPath = __DIR__ . DIRECTORY_SEPARATOR . '..'. DIRECTORY_SEPARATOR . '..'; + return new RiverFileDataSource($projectFullPath . DIRECTORY_SEPARATOR . $_ENV['RIVER_DEMO_FILES_RELATIVE_PATH']); + }); + + $container->singleton(RiverDataSource::class, function ($container) { + return new RiverDataSource( + $container[RiverApiDataSource::class], + $container[RiverFileDataSource::class], + (bool)$_ENV['DEMO_MODE'] + ); + }); } } From ac165d03f77747867e87a91a13047848e6ac8de6 Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Fri, 25 Mar 2022 18:20:07 -0300 Subject: [PATCH 08/19] pulling data on test mode to the controllers --- src/Http/Controller/PublicationRiverController.php | 13 +++++++++++++ src/Http/Controller/TagRiverController.php | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Http/Controller/PublicationRiverController.php b/src/Http/Controller/PublicationRiverController.php index a112e9c..065eefa 100644 --- a/src/Http/Controller/PublicationRiverController.php +++ b/src/Http/Controller/PublicationRiverController.php @@ -3,14 +3,27 @@ namespace JournalMedia\Sample\ApiProject\Http\Controller; +use JournalMedia\Sample\ApiProject\Application\Application; +use JournalMedia\Sample\ApiProject\Service\RiverDataSource; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Laminas\Diactoros\Response\HtmlResponse; final class PublicationRiverController { + private RiverDataSource $riverDataSource; + private string $publication; + + public function __construct(RiverDataSource $riverDataSource, string $publication = 'thejournal') + { + $this->riverDataSource = $riverDataSource; + $this->publication = $publication; + } + public function __invoke(ServerRequestInterface $request): ResponseInterface { + $data = $this->riverDataSource->get()->getArticlesByPublication($this->publication); + print_r($data); return new HtmlResponse( sprintf("Demo Mode: %s", $_ENV['DEMO_MODE'] === "true" ? "ON" : "OFF") ); diff --git a/src/Http/Controller/TagRiverController.php b/src/Http/Controller/TagRiverController.php index 1761de5..7f06967 100644 --- a/src/Http/Controller/TagRiverController.php +++ b/src/Http/Controller/TagRiverController.php @@ -3,16 +3,25 @@ namespace JournalMedia\Sample\ApiProject\Http\Controller; +use JournalMedia\Sample\ApiProject\Service\RiverDataSource; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Laminas\Diactoros\Response\HtmlResponse; final class TagRiverController { + private RiverDataSource $riverDataSource; + + public function __construct(RiverDataSource $riverDataSource) + { + $this->riverDataSource = $riverDataSource; + } public function __invoke( ServerRequestInterface $request, array $args ): ResponseInterface { + $data = $this->riverDataSource->get()->getArticlesByPublication($request->getAttribute('tag')); + print_r($data); return new HtmlResponse( "Display the contents of the river for the tag '{$args['tag']}'" ); From 228918572e7cf44f4b14336c6cafb928bfb45521 Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Fri, 25 Mar 2022 18:36:32 -0300 Subject: [PATCH 09/19] Fix to get the right value for environment variable DEMO_MODE --- src/Http/ServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/ServiceProvider.php b/src/Http/ServiceProvider.php index c89670b..77c3b8f 100644 --- a/src/Http/ServiceProvider.php +++ b/src/Http/ServiceProvider.php @@ -55,7 +55,7 @@ public function register(Container $container): void return new RiverDataSource( $container[RiverApiDataSource::class], $container[RiverFileDataSource::class], - (bool)$_ENV['DEMO_MODE'] + $_ENV['DEMO_MODE'] === 'true' ); }); } From c987aaeb086d1ea021fc476e861d8724a7382cd8 Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Fri, 25 Mar 2022 18:42:58 -0300 Subject: [PATCH 10/19] Updating environment template --- .env.example | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.env.example b/.env.example index a1e625e..be5c696 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,5 @@ DEMO_MODE=false +THE_JOURNAL_API_BASE_URL=https://api.thejournal.ie/v3/sample +THE_JOURNAL_API_USERNAME= +THE_JOURNAL_API_PASSWORD= +RIVER_DEMO_FILES_RELATIVE_PATH =resources/demo-responses \ No newline at end of file From d2589c2063eca4b9a811ce058f28063d9464a068 Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Fri, 25 Mar 2022 18:44:03 -0300 Subject: [PATCH 11/19] Set Basic Authentication Credentials to the Api Journal Instance --- src/Http/ServiceProvider.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Http/ServiceProvider.php b/src/Http/ServiceProvider.php index 77c3b8f..f9ddd05 100644 --- a/src/Http/ServiceProvider.php +++ b/src/Http/ServiceProvider.php @@ -39,7 +39,11 @@ public function register(Container $container): void }); $container->singleton(JournalApiConnector::class, function($container) { - return new JournalApiConnector($_ENV['THE_JOURNAL_API_BASE_URL'], $container[Client::class]); + $journalApiConnector = new JournalApiConnector($_ENV['THE_JOURNAL_API_BASE_URL'], $container[Client::class]); + $journalApiConnector + ->setUsername($_ENV['THE_JOURNAL_API_USERNAME']) + ->setPassword($_ENV['THE_JOURNAL_API_PASSWORD']); + return $journalApiConnector; }); $container->singleton(RiverApiDataSource::class, function ($container) { From b83d428b943c83910bb7fb94020c2a2843a01fcb Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Fri, 25 Mar 2022 19:21:19 -0300 Subject: [PATCH 12/19] Parsing response Journal API --- src/Connector/JournalApiConnector.php | 33 +++++++++++++++++++++------ 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/Connector/JournalApiConnector.php b/src/Connector/JournalApiConnector.php index 8e43beb..97cdf96 100644 --- a/src/Connector/JournalApiConnector.php +++ b/src/Connector/JournalApiConnector.php @@ -63,12 +63,14 @@ public function setClient(ClientInterface $client): JournalApiConnector * @param array $data * @return array * @throws GuzzleException + * @throws \Exception */ - #[ArrayShape(['result' => "string"])] public function doGet(string $relativeUrl, array $headers = [], array $data = []): array + #[ArrayShape(['result' => "mixed"])] public function doGet(string $relativeUrl, array $headers = [], array $data = []): array { $queryString = empty($data) ? '' : '?' . http_build_query($data); - return ['result' => $this->doRequest('GET', $relativeUrl . $queryString, $headers, [])->getBody() - ->getContents()]; + $response = json_decode($this->doRequest('GET', $relativeUrl . $queryString, $headers, [])->getBody() + ->getContents(), true); + return $this->parseResponse($response); } /** @@ -77,11 +79,13 @@ public function setClient(ClientInterface $client): JournalApiConnector * @param array $data * @return array * @throws GuzzleException + * @throws \Exception */ - #[ArrayShape(['result' => "string"])] public function doPost(string $relativeUrl, array $headers = [], array $data = []): array + #[ArrayShape(['result' => "mixed"])] public function doPost(string $relativeUrl, array $headers = [], array $data = []): array { - return ['result' => $this->doRequest('POST', $relativeUrl, $headers, $data)->getBody() - ->getContents()]; + $response = json_decode($this->doRequest('POST', $relativeUrl, $headers, $data)->getBody() + ->getContents()); + return $this->parseResponse($response); } /** @@ -107,7 +111,22 @@ protected function doRequest(string $method, string $relativeUrl, array $headers { $credentials = base64_encode($this->username . ':' . $this->password); return [ - 'Authorization' => ['Basic '.$credentials] + 'Authorization' => ['Basic ' . $credentials] ]; } + + /** + * @param mixed $response + * @return array + * @throws \Exception + */ + private function parseResponse(mixed $response): array + { + if ($response['status'] ?? false) { + return ['result' => $response['response']['articles']]; + } else { + //TODO: throw exception + throw new \Exception(); + } + } } \ No newline at end of file From 678037ae6aef097448fba005bf73c94a984b4f1b Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Fri, 25 Mar 2022 19:24:32 -0300 Subject: [PATCH 13/19] Fix Pull articles by tag --- src/Http/Controller/PublicationRiverController.php | 1 - src/Http/Controller/TagRiverController.php | 2 +- src/Service/RiverApiDataSource.php | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Http/Controller/PublicationRiverController.php b/src/Http/Controller/PublicationRiverController.php index 065eefa..a5735b7 100644 --- a/src/Http/Controller/PublicationRiverController.php +++ b/src/Http/Controller/PublicationRiverController.php @@ -3,7 +3,6 @@ namespace JournalMedia\Sample\ApiProject\Http\Controller; -use JournalMedia\Sample\ApiProject\Application\Application; use JournalMedia\Sample\ApiProject\Service\RiverDataSource; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; diff --git a/src/Http/Controller/TagRiverController.php b/src/Http/Controller/TagRiverController.php index 7f06967..958cfba 100644 --- a/src/Http/Controller/TagRiverController.php +++ b/src/Http/Controller/TagRiverController.php @@ -20,7 +20,7 @@ public function __invoke( ServerRequestInterface $request, array $args ): ResponseInterface { - $data = $this->riverDataSource->get()->getArticlesByPublication($request->getAttribute('tag')); + $data = $this->riverDataSource->get()->getArticlesByTag($request->getAttribute('tag')); print_r($data); return new HtmlResponse( "Display the contents of the river for the tag '{$args['tag']}'" diff --git a/src/Service/RiverApiDataSource.php b/src/Service/RiverApiDataSource.php index e9af42a..922c3be 100644 --- a/src/Service/RiverApiDataSource.php +++ b/src/Service/RiverApiDataSource.php @@ -7,7 +7,7 @@ class RiverApiDataSource implements RiverDataSourceInterface { - + const TAG_URL_PREFIX = '/tag'; private JournalApiConnector $apiConnector; public function __construct(JournalApiConnector $apiConnector) @@ -29,7 +29,7 @@ public function getArticlesByPublication(string $publicationName, array $extraOp */ public function getArticlesByTag(string $tagName, array $extraOptions = []) { - $response = $this->apiConnector->doGet(DIRECTORY_SEPARATOR . $tagName); + $response = $this->apiConnector->doGet( self::TAG_URL_PREFIX . "/$tagName"); return $response['result'] ?? []; } } \ No newline at end of file From c47798ca69a9f33290444a81daba0baf45faeac5 Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Fri, 25 Mar 2022 21:49:18 -0300 Subject: [PATCH 14/19] Render river view --- .../Controller/PublicationRiverController.php | 20 +++++---- src/Http/Controller/TagRiverController.php | 20 +++++---- src/View/RiverView.php | 45 +++++++++++++++++++ 3 files changed, 67 insertions(+), 18 deletions(-) create mode 100644 src/View/RiverView.php diff --git a/src/Http/Controller/PublicationRiverController.php b/src/Http/Controller/PublicationRiverController.php index a5735b7..8137e5b 100644 --- a/src/Http/Controller/PublicationRiverController.php +++ b/src/Http/Controller/PublicationRiverController.php @@ -4,27 +4,29 @@ namespace JournalMedia\Sample\ApiProject\Http\Controller; use JournalMedia\Sample\ApiProject\Service\RiverDataSource; +use JournalMedia\Sample\ApiProject\View\RiverView; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Laminas\Diactoros\Response\HtmlResponse; final class PublicationRiverController { private RiverDataSource $riverDataSource; - private string $publication; - public function __construct(RiverDataSource $riverDataSource, string $publication = 'thejournal') + /** + * @param RiverDataSource $riverDataSource + */ + public function __construct(RiverDataSource $riverDataSource) { $this->riverDataSource = $riverDataSource; - $this->publication = $publication; } + /** + * @param ServerRequestInterface $request + * @return ResponseInterface + */ public function __invoke(ServerRequestInterface $request): ResponseInterface { - $data = $this->riverDataSource->get()->getArticlesByPublication($this->publication); - print_r($data); - return new HtmlResponse( - sprintf("Demo Mode: %s", $_ENV['DEMO_MODE'] === "true" ? "ON" : "OFF") - ); + $data = $this->riverDataSource->get()->getArticlesByPublication('thejournal'); + return (new RiverView($data))->getResponse(); } } diff --git a/src/Http/Controller/TagRiverController.php b/src/Http/Controller/TagRiverController.php index 958cfba..ad28ad9 100644 --- a/src/Http/Controller/TagRiverController.php +++ b/src/Http/Controller/TagRiverController.php @@ -4,26 +4,28 @@ namespace JournalMedia\Sample\ApiProject\Http\Controller; use JournalMedia\Sample\ApiProject\Service\RiverDataSource; +use JournalMedia\Sample\ApiProject\View\RiverView; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Laminas\Diactoros\Response\HtmlResponse; final class TagRiverController { private RiverDataSource $riverDataSource; + /** + * @param RiverDataSource $riverDataSource + */ public function __construct(RiverDataSource $riverDataSource) { $this->riverDataSource = $riverDataSource; } - public function __invoke( - ServerRequestInterface $request, - array $args - ): ResponseInterface { + + /** + * @param ServerRequestInterface $request + * @return ResponseInterface + */ + public function __invoke(ServerRequestInterface $request): ResponseInterface { $data = $this->riverDataSource->get()->getArticlesByTag($request->getAttribute('tag')); - print_r($data); - return new HtmlResponse( - "Display the contents of the river for the tag '{$args['tag']}'" - ); + return (new RiverView($data))->getResponse(); } } diff --git a/src/View/RiverView.php b/src/View/RiverView.php new file mode 100644 index 0000000..d5db20e --- /dev/null +++ b/src/View/RiverView.php @@ -0,0 +1,45 @@ +articles = $articles; + } + + /** + * @return HtmlResponse + */ + public function getResponse(): HtmlResponse + { + $html = ''; + $html .= ''; + $html .= ''; + foreach ($this->articles as $article) { + $html .= '
'; + $html .= "

{$article['title']}

"; + $html .= '
'; + $html .= '
'; + $html .= ""; + $html .= '
'; + $html .= '
'; + $html .= $article['excerpt']; + $html .= '
'; + $html .= '
'; + $html .= '
'; + } + $html .= ''; + $html .= ''; + + return new HtmlResponse($html); + } +} \ No newline at end of file From e85fb4c8f3f2a2ffd1478c9b4aac8de6954fe1ab Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Fri, 25 Mar 2022 22:11:13 -0300 Subject: [PATCH 15/19] Handle Api Exceptions --- src/ApiException.php | 8 ++++++++ src/Connector/JournalApiConnector.php | 15 +++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 src/ApiException.php diff --git a/src/ApiException.php b/src/ApiException.php new file mode 100644 index 0000000..cb44828 --- /dev/null +++ b/src/ApiException.php @@ -0,0 +1,8 @@ + "mixed"])] public function doGet(string $relativeUrl, array $headers = [], array $data = []): array { @@ -79,7 +79,7 @@ public function setClient(ClientInterface $client): JournalApiConnector * @param array $data * @return array * @throws GuzzleException - * @throws \Exception + * @throws Exception */ #[ArrayShape(['result' => "mixed"])] public function doPost(string $relativeUrl, array $headers = [], array $data = []): array { @@ -118,15 +118,14 @@ protected function doRequest(string $method, string $relativeUrl, array $headers /** * @param mixed $response * @return array - * @throws \Exception + * @throws Exception */ - private function parseResponse(mixed $response): array + #[ArrayShape(['result' => "mixed"])] private function parseResponse(mixed $response): array { if ($response['status'] ?? false) { return ['result' => $response['response']['articles']]; } else { - //TODO: throw exception - throw new \Exception(); + throw new ApiException('Error getting data from API'); } } } \ No newline at end of file From eb73a11c481fbc45f33a16f710260e95cf09ac3a Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Fri, 25 Mar 2022 22:12:13 -0300 Subject: [PATCH 16/19] Handling Exceptions --- src/Connector/HttpConnectorInterface.php | 2 +- src/Http/Controller/PublicationRiverController.php | 9 +++++++-- src/Http/Controller/TagRiverController.php | 13 ++++++++++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Connector/HttpConnectorInterface.php b/src/Connector/HttpConnectorInterface.php index cae899c..916190a 100644 --- a/src/Connector/HttpConnectorInterface.php +++ b/src/Connector/HttpConnectorInterface.php @@ -10,7 +10,7 @@ interface HttpConnectorInterface * @param ClientInterface $client * @return mixed */ - public function setClient(ClientInterface $client); + public function setClient(ClientInterface $client): mixed; /** * @param string $relativeUrl diff --git a/src/Http/Controller/PublicationRiverController.php b/src/Http/Controller/PublicationRiverController.php index 8137e5b..ab2efb5 100644 --- a/src/Http/Controller/PublicationRiverController.php +++ b/src/Http/Controller/PublicationRiverController.php @@ -5,6 +5,7 @@ use JournalMedia\Sample\ApiProject\Service\RiverDataSource; use JournalMedia\Sample\ApiProject\View\RiverView; +use Laminas\Diactoros\Response\HtmlResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -26,7 +27,11 @@ public function __construct(RiverDataSource $riverDataSource) */ public function __invoke(ServerRequestInterface $request): ResponseInterface { - $data = $this->riverDataSource->get()->getArticlesByPublication('thejournal'); - return (new RiverView($data))->getResponse(); + try { + $data = $this->riverDataSource->get()->getArticlesByPublication('thejournal'); + return (new RiverView($data))->getResponse(); + } catch (Exception $e) { + return new HtmlResponse($e->getMessage()); + } } } diff --git a/src/Http/Controller/TagRiverController.php b/src/Http/Controller/TagRiverController.php index ad28ad9..7429eca 100644 --- a/src/Http/Controller/TagRiverController.php +++ b/src/Http/Controller/TagRiverController.php @@ -3,8 +3,10 @@ namespace JournalMedia\Sample\ApiProject\Http\Controller; +use Exception; use JournalMedia\Sample\ApiProject\Service\RiverDataSource; use JournalMedia\Sample\ApiProject\View\RiverView; +use Laminas\Diactoros\Response\HtmlResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -24,8 +26,13 @@ public function __construct(RiverDataSource $riverDataSource) * @param ServerRequestInterface $request * @return ResponseInterface */ - public function __invoke(ServerRequestInterface $request): ResponseInterface { - $data = $this->riverDataSource->get()->getArticlesByTag($request->getAttribute('tag')); - return (new RiverView($data))->getResponse(); + public function __invoke(ServerRequestInterface $request): ResponseInterface + { + try { + $data = $this->riverDataSource->get()->getArticlesByTag($request->getAttribute('tag')); + return (new RiverView($data))->getResponse(); + } catch (Exception $e) { + return new HtmlResponse($e->getMessage()); + } } } From 46536dd201463ebb3c6dd4436ff8db1ec9faf5d3 Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Sat, 26 Mar 2022 20:13:37 -0300 Subject: [PATCH 17/19] Fixes: return decode json response as array, do not generate authorization header when username or password are empty --- src/Connector/JournalApiConnector.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Connector/JournalApiConnector.php b/src/Connector/JournalApiConnector.php index 21c444e..67eb227 100644 --- a/src/Connector/JournalApiConnector.php +++ b/src/Connector/JournalApiConnector.php @@ -1,4 +1,5 @@ "mixed"])] public function doPost(string $relativeUrl, array $headers = [], array $data = []): array { $response = json_decode($this->doRequest('POST', $relativeUrl, $headers, $data)->getBody() - ->getContents()); + ->getContents(), true); return $this->parseResponse($response); } @@ -109,6 +110,9 @@ protected function doRequest(string $method, string $relativeUrl, array $headers #[ArrayShape(['Authorization' => "string[]"])] protected function getAuthorization(): array { + if (empty($this->username) || empty($this->password)) { + return []; + } $credentials = base64_encode($this->username . ':' . $this->password); return [ 'Authorization' => ['Basic ' . $credentials] From 2c0cd5c4e7dc054abdac053c783a524bb587703a Mon Sep 17 00:00:00 2001 From: Jose Ruiz Date: Sat, 26 Mar 2022 22:45:48 -0300 Subject: [PATCH 18/19] adding tests --- phpunit.xml | 8 ++ tests/JournalApiConnectorTest.php | 152 ++++++++++++++++++++++++++++++ tests/RiverApiDataSourceTest.php | 80 ++++++++++++++++ tests/RiverFileDataSourceTest.php | 41 ++++++++ tests/responses/google | 1 + tests/responses/thejournal | 1 + 6 files changed, 283 insertions(+) create mode 100644 phpunit.xml create mode 100644 tests/JournalApiConnectorTest.php create mode 100644 tests/RiverApiDataSourceTest.php create mode 100644 tests/RiverFileDataSourceTest.php create mode 100644 tests/responses/google create mode 100644 tests/responses/thejournal diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..bc51414 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,8 @@ + + + + + tests + + + \ No newline at end of file diff --git a/tests/JournalApiConnectorTest.php b/tests/JournalApiConnectorTest.php new file mode 100644 index 0000000..6592935 --- /dev/null +++ b/tests/JournalApiConnectorTest.php @@ -0,0 +1,152 @@ + function (GuzzleHttp\Psr7\Request $request) { + $this->assertEquals( + ['Basic ' . base64_encode(self::USERNAME . ':' . self::PASSWORD)], + $request->getHeader('Authorization') + ); + $this->assertEquals(self::BASE_URL . '/' . self::RELATIVE_URL, $request->getUri()); + return new GuzzleHttp\Psr7\Response(200, [], self::JSON_RESPONSE); + } + ]); + $journalApiConnector = new JournalApiConnector(self::BASE_URL, $client); + $journalApiConnector->setUsername(self::USERNAME)->setPassword(self::PASSWORD); + $journalApiConnector->doGet('/' . self::RELATIVE_URL); + } + + /** @test */ + public function it_creates_get_request_without_authorization_headers() + { + $client = new Client([ + 'handler' => function (GuzzleHttp\Psr7\Request $request) { + $this->assertEquals( + [], + $request->getHeader('Authorization') + ); + $this->assertEquals(self::BASE_URL . '/' . self::RELATIVE_URL, $request->getUri()); + return new GuzzleHttp\Psr7\Response(200, [], self::JSON_RESPONSE); + } + ]); + $journalApiConnector = new JournalApiConnector(self::BASE_URL, $client); + $journalApiConnector->doGet('/' . self::RELATIVE_URL); + } + + /** @test */ + public function it_creates_post_request_with_authorization_headers() + { + $client = new Client([ + 'handler' => function (GuzzleHttp\Psr7\Request $request) { + $this->assertEquals( + ['Basic ' . base64_encode(self::USERNAME . ':' . self::PASSWORD)], + $request->getHeader('Authorization') + ); + $this->assertEquals(self::BASE_URL . '/' . self::RELATIVE_URL, $request->getUri()); + return new GuzzleHttp\Psr7\Response(200, [], self::JSON_RESPONSE); + } + ]); + $journalApiConnector = new JournalApiConnector(self::BASE_URL, $client); + $journalApiConnector->setUsername(self::USERNAME)->setPassword(self::PASSWORD); + $journalApiConnector->doPost('/' . self::RELATIVE_URL); + } + + /** @test */ + public function it_creates_request_with_custom_headers() + { + $client = new Client([ + 'handler' => function (GuzzleHttp\Psr7\Request $request) { + $this->assertEquals( + ['customValue'], + $request->getHeader('customHeader') + ); + $this->assertEquals(self::BASE_URL . '/' . self::RELATIVE_URL, $request->getUri()); + return new GuzzleHttp\Psr7\Response(200, [], self::JSON_RESPONSE); + } + ]); + $journalApiConnector = new JournalApiConnector(self::BASE_URL, $client); + $journalApiConnector->doGet('/' . self::RELATIVE_URL, ['customHeader' => 'customValue']); + } + + /** @test */ + public function it_throws_api_exception_when_bad_response() + { + $this->expectException(ApiException::class); + + $client = new Client([ + 'handler' => function (GuzzleHttp\Psr7\Request $request) { + $this->assertEquals(self::BASE_URL . '/' . self::RELATIVE_URL, $request->getUri()); + return new GuzzleHttp\Psr7\Response(200, [], ''); + } + ]); + $journalApiConnector = new JournalApiConnector(self::BASE_URL, $client); + $journalApiConnector->doGet('/' . self::RELATIVE_URL, ['customHeader' => 'customValue']); + } + + /** @test */ + public function it_creates_get_request_with_query_parameters() + { + $client = new Client([ + 'handler' => function (GuzzleHttp\Psr7\Request $request) { + $this->assertEquals(self::BASE_URL . '/' . self::RELATIVE_URL . '?param1=1¶m2=2', $request->getUri()); + return new GuzzleHttp\Psr7\Response(200, [], self::JSON_RESPONSE); + } + ]); + $journalApiConnector = new JournalApiConnector(self::BASE_URL, $client); + $journalApiConnector->doGet('/' . self::RELATIVE_URL, [], ['param1' => '1', 'param2' => '2']); + } + + /** @test */ + public function it_creates_post_request_with_data() + { + $client = new Client([ + 'handler' => function (GuzzleHttp\Psr7\Request $request) { + $this->assertEquals('{"param1":"1","param2":"2"}', $request->getBody()->getContents()); + return new GuzzleHttp\Psr7\Response(200, [], self::JSON_RESPONSE); + } + ]); + $journalApiConnector = new JournalApiConnector(self::BASE_URL, $client); + $journalApiConnector->doPost('/' . self::RELATIVE_URL, [], ['param1' => '1', 'param2' => '2']); + } + + /** @test */ + public function it_creates_get_method() + { + $client = new Client([ + 'handler' => function (GuzzleHttp\Psr7\Request $request) { + $this->assertEquals('GET', $request->getMethod()); + return new GuzzleHttp\Psr7\Response(200, [], self::JSON_RESPONSE); + } + ]); + $journalApiConnector = new JournalApiConnector(self::BASE_URL, $client); + $journalApiConnector->doGet('/' . self::RELATIVE_URL); + } + + /** @test */ + public function it_creates_post_method() + { + $client = new Client([ + 'handler' => function (GuzzleHttp\Psr7\Request $request) { + $this->assertEquals('POST', $request->getMethod()); + return new GuzzleHttp\Psr7\Response(200, [], self::JSON_RESPONSE); + } + ]); + $journalApiConnector = new JournalApiConnector(self::BASE_URL, $client); + $journalApiConnector->doPost('/' . self::RELATIVE_URL); + } +} \ No newline at end of file diff --git a/tests/RiverApiDataSourceTest.php b/tests/RiverApiDataSourceTest.php new file mode 100644 index 0000000..1fc0a6f --- /dev/null +++ b/tests/RiverApiDataSourceTest.php @@ -0,0 +1,80 @@ + function (GuzzleHttp\Psr7\Request $request) { + $this->assertEquals(self::BASE_URL . '/' . self::PUBLICATION_NAME, $request->getUri()); + return new GuzzleHttp\Psr7\Response(200, [], $this->getPublicationResponse()); + } + ]); + $journalApiConnector = new JournalApiConnector(self::BASE_URL, $client); + $journalApiConnector->setUsername(self::USERNAME)->setPassword(self::PASSWORD); + $dataSource = new RiverApiDataSource($journalApiConnector); + $articles = $dataSource->getArticlesByPublication(self::PUBLICATION_NAME); + $article = reset($articles); + $this->assertArticleFields($article); + } + + /** @test + * @throws GuzzleException + */ + public function it_get_tag_articles() + { + $client = new Client([ + 'handler' => function (GuzzleHttp\Psr7\Request $request) { + $this->assertEquals(self::BASE_URL . '/tag/' . self::TAG_NAME, $request->getUri()); + return new GuzzleHttp\Psr7\Response(200, [], $this->getTagResponse()); + } + ]); + $journalApiConnector = new JournalApiConnector(self::BASE_URL, $client); + $journalApiConnector->setUsername(self::USERNAME)->setPassword(self::PASSWORD); + $dataSource = new RiverApiDataSource($journalApiConnector); + $articles = $dataSource->getArticlesByTag(self::TAG_NAME); + $article = reset($articles); + $this->assertArticleFields($article); + } + + /** + * @param mixed $article + * @return void + */ + private function assertArticleFields(mixed $article): void + { + $this->assertNotEmpty($article['title']); + $this->assertNotEmpty($article['excerpt']); + $this->assertNotEmpty($article['images']); + } + + /** + * @return string + */ + private function getPublicationResponse(): string + { + return file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'responses/thejournal'); + + } + private function getTagResponse(): string + { + return file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'responses/google'); + } +} \ No newline at end of file diff --git a/tests/RiverFileDataSourceTest.php b/tests/RiverFileDataSourceTest.php new file mode 100644 index 0000000..7c1b10d --- /dev/null +++ b/tests/RiverFileDataSourceTest.php @@ -0,0 +1,41 @@ +getArticlesByPublication(self::PUBLICATION_NAME); + $article = reset($articles); + $this->assertArticleFields($article); + } + + /** @test */ + public function it_get_tag_articles() + { + $dataSource = new RiverFileDataSource(self::RIVER_DEMO_FILES_RELATIVE_PATH); + $articles = $dataSource->getArticlesByTag(self::TAG_NAME); + $article = reset($articles); + $this->assertArticleFields($article); + } + + /** + * @param mixed $article + * @return void + */ + private function assertArticleFields(mixed $article): void + { + $this->assertNotEmpty($article['title']); + $this->assertNotEmpty($article['excerpt']); + $this->assertNotEmpty($article['images']); + } +} \ No newline at end of file diff --git a/tests/responses/google b/tests/responses/google new file mode 100644 index 0000000..ed35659 --- /dev/null +++ b/tests/responses/google @@ -0,0 +1 @@ +{"rendered":1648345082,"status":true,"response":{"articles":[{"id":5710818,"title":"High Court challenge alleges DPC failed to investigate complaint made about Google, IAB Europe","slug":"high-court-data-protection-commission-google-iab-europe","date":"2022-03-14 16:16:01","date_modified":"2022-03-14 16:16:01","date_published":"2022-03-14 16:16:02","is_updated":false,"has_audio":"0","has_video":"0","has_slideshow":"0","is_hot_on_twitter":"0","publication":"thejournal","stats":{"views":"8110","facebook":"0","comments":"0","twitter":"0","email":"1","dislikes":"0","likes":"0","fresh_until_ts":1648342910},"syndication_source":"","seo_title":null,"seo_description":null,"is_sponsored":0,"sticky":null,"header_url":null,"internal_syndication_id":null,"digest_key":"article_5710818","date_unix":1647274561,"date_modified_unix":1647274561,"status":"publish","is_exclusive":"0","excerpt":"Dr Johnny Ryan claims the Data Protection Commission has delayed investigating his 2018 complaint regarding the two companies.","content":"

A HIGH COURT challenge has been brought against the Data Protection Commission\u2019s alleged failure to fully investigate a complaint made about Google and IAB Europe\u2019s procession of personal data.<\/p>\n

The action has been brought by Dr Johnny Ryan, a senior fellow of the Irish Council for Civil Liberties, regarding what he claims is the DPC\u2019s delay in investigating his 2018 complaint regarding the two companies.<\/p>\n

The DPC, in correspondence with Ryan\u2019s lawyers, has denied that it has delayed in handling the complaint.<\/p>\n

Ryan, who in his capacity with the ICCL raises issues of concern about data protection rights, has sued the DPC over its alleged failure to progress and substantially complete the investigation into his complaint.<\/p>\n

His complaint concerns the operation of a system, alleged used by Googles and IAB, called \u2018Real Time Bidding\u2019 or (RTB) which underlies the targeting of individual data subjects for online advertising based on their personal data.<\/p>\n

[embed id=\"embed_1\"]<\/p>\n

The failure to investigate the matter, he claims, is in breach of the requirements of both the 2018 Data Protection Act and the GDPR. (General Data Protection Regulation), which is the EU\u2019s privacy and data protection requirements.<\/p>\n

In his complaint, Ryan raised concerns including that the RTB systems used by the two firms involves the unauthorised, and potentially unlimited disclosure and processing of personal data.<\/p>\n

His complaint contains several instances where he claims that Google and IAB have breached GDPR.<\/p>\n

The DPC is the state\u2019s supervisory authority in respect of GDPR and in respect of data controllers who main establishment is in Ireland.<\/p>\n

He also raised issues about the company\u2019s alleged inability to demonstrate their compliance with the GDPR requirement that personal data be processed lawfully and fairly, or that the processing of personal data be kept to a minimum.<\/p>\n

The High Court heard that the DPC opened an inquiry into RTB in May 2019, under its own violation, and there had been detailed correspondence between Ryan and the DPC over the matter.<\/p>\n

However, Ryan claims that his complaint has not been properly or adequately investigated by the commission and has brought High Court judicial review proceedings against the DPC.<\/p>\n

[embed id=\"embed_2\"]<\/p>\n

In his action, Ryan, represented by James Doherty SC and Sean O\u2019Sullivan Bl, seeks a declaration that the respondent has failed to carry out an investigation into the complaint with all due diligence within a reasonable time.<\/p>\n

He also seeks an order directing the DPC to proceed with the investigation of he that part of the complaint not addressed in the in the inquiry, without delay.<\/p>\n

He further seeks that the matter be referred to the Court of Justice of the European Union.<\/p>\n

The action came before Mr Justice Charles Meenan today. The judge on an ex-parte basis, granted Ryan permission to bring his challenge.<\/p>\n

The matter was adjourned to a date in May.<\/p>\n","permalink":"https:\/\/www.thejournal.ie\/high-court-data-protection-commission-google-iab-europe-5710818-Mar2022\/","shortlink":"https:\/\/jrnl.ie\/5710818","type":"post","secondary_publications":[],"main_story_links":[{"text":"","url":""},{"text":"","url":""}],"categories":{"thejournal":{"id":"1","name":"Irish","slug":"irish"}},"publication_data":{"primary":"thejournal","all":["thejournal"],"categories":{"thejournal":[{"id":"1","name":"Irish","slug":"irish"}]}},"images":{"thumbnail":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-145x145.jpg","width":145,"height":145},"medium":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-296x201.jpg","width":296,"height":201},"large":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-630x428.jpg","width":630,"height":428},"larger":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-744x506.jpg","width":744,"height":506},"river_0":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-230x150.jpg","width":230,"height":150},"river_1":{"image":"https:\/\/c2.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-2300x1500.jpg","width":2300,"height":1500},"river_magazine_0":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-630x332.jpg","width":630,"height":332},"article_page_0":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-390x285.jpg","width":390,"height":285},"square_0":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-390x390.jpg","width":390,"height":390},"square_1":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-197x197.jpg","width":197,"height":197},"square_2":{"image":"https:\/\/c2.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-96x96.jpg","width":96,"height":96},"square_3":{"image":"https:\/\/c2.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-85x85.jpg","width":85,"height":85},"square_4":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-100x100.jpg","width":100,"height":100},"square_5":{"image":"https:\/\/c2.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-2000x2000.jpg","width":2000,"height":2000},"main_story_0":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-630x260.jpg","width":630,"height":260},"main_story_1":{"image":"https:\/\/c3.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-210x87.jpg","width":210,"height":87},"main_story_2":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-290x120.jpg","width":290,"height":120},"super_wide_0":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-950x260.jpg","width":950,"height":260},"super_wide_1":{"image":"https:\/\/c3.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-290x80.jpg","width":290,"height":80},"listings_0":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-150x100.jpg","width":150,"height":100},"listings_1":{"image":"https:\/\/c2.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-80x50.jpg","width":80,"height":50},"listings_2":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-225x150.jpg","width":225,"height":150},"listings_3":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-300x200.jpg","width":300,"height":200},"listings_4":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-304x203.jpg","width":304,"height":203},"listings_5":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-752x501.jpg","width":752,"height":501},"listings_6":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-310x207.jpg","width":310,"height":207},"offer_large_0":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-458x298.jpg","width":458,"height":298},"offer_medium_0":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-298x194.jpg","width":298,"height":194},"offer_small_0":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-218x140.jpg","width":218,"height":140},"hero_0":{"image":"https:\/\/c1.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7-1366x380.jpg","width":1366,"height":380},"original":{"image":"https:\/\/c0.thejournal.ie\/media\/2022\/03\/viewoffourcourtsindublincitycentre-7.jpg","width":false,"height":false}},"is_short_post":false,"image_data":{"orientation":"landscape","credit":"Shutterstock","caption":"","id":"5710830","raw_source":"Shutterstock","raw_source_url":false},"show_image":true,"slideshows":false,"poll":false,"by_line_author":false,"has_liveblog":0,"has_active_liveblog":0,"liveblog":[],"primary_tag":{"slug":"courts","name":"Courts"},"tags":[{"name":"Court","slug":"court","description":"","count":"2993"},{"name":"Courts","slug":"courts","description":"","count":"5350"},{"name":"Data Protection Commission","slug":"data-protection-commission","description":"","count":"42"},{"name":"Google","slug":"google","description":"Provides a wide range of internet services, primarily a search engine. Google was founded by the \"Google guys\" Larry Page and Sergey Brin in 1998 when they were both Ph.D students at Stanford. The company employs 22,000 people and profited $6.5 billion in 2009. Google generates most of its profit from advertising.","image":"https:\/\/cdn.thejournal.ie\/media\/2010\/08\/google2-145x145.jpg","count":"884"},{"name":"High Court","slug":"high-court","description":"Deals with the most serious civil and criminal cases, it also has the power to determine whether a law is constitutional or not. It acts as an appeals court to the Circuit Courts in civil matters, it also can review decisions made by certain tribunals. This court consists of a president and 36 ordinary judges.","image":"https:\/\/cdn.thejournal.ie\/media\/2010\/09\/four-courts1-145x145.jpg","count":"1268"},{"name":"IAB Europe","slug":"iab-europe","description":"","count":"2"}],"embeds":{"embed_1":{"type":"ad","size":"mpu","id":"ad_1"},"embed_2":{"type":"contributions_prompt","id":"contributions_prompt_2","iframe":"