From 2b99dec1bd36b3c2f5c76b5b1d899a97c03487bc Mon Sep 17 00:00:00 2001 From: Ben Borla Date: Sat, 18 Nov 2023 02:40:15 +0800 Subject: [PATCH] chore: code clean-up --- api/composer.json | 1 + api/composer.lock | 172 ++++++++++++++++++++++++- api/config/services.yaml | 15 +++ api/src/Controller/IndexController.php | 17 +++ api/src/Entity/Api/FruitApiEntity.php | 83 ++++++++++++ api/src/Service/FruitAggregator.php | 35 +++++ api/src/Service/FruitDecomulator.php | 30 +++++ api/src/Service/FruitsApiClient.php | 54 ++++++++ todo.md | 4 +- 9 files changed, 408 insertions(+), 3 deletions(-) create mode 100644 api/src/Controller/IndexController.php create mode 100644 api/src/Entity/Api/FruitApiEntity.php create mode 100644 api/src/Service/FruitAggregator.php create mode 100644 api/src/Service/FruitDecomulator.php create mode 100644 api/src/Service/FruitsApiClient.php diff --git a/api/composer.json b/api/composer.json index e2805ad..11dfa63 100644 --- a/api/composer.json +++ b/api/composer.json @@ -16,6 +16,7 @@ "symfony/dotenv": "6.3.*", "symfony/flex": "^2", "symfony/framework-bundle": "6.3.*", + "symfony/http-client": "6.3.*", "symfony/runtime": "6.3.*", "symfony/yaml": "6.3.*" }, diff --git a/api/composer.lock b/api/composer.lock index 74385da..019406f 100644 --- a/api/composer.lock +++ b/api/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": "727f9ec0a6ad4ea0511bd096abba77c6", + "content-hash": "8d0e266a8532decf604f885a3ac5e805", "packages": [ { "name": "doctrine/cache", @@ -2766,6 +2766,176 @@ ], "time": "2023-11-09T14:35:42+00:00" }, + { + "name": "symfony/http-client", + "version": "v6.3.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "0314e2d49939a9831929d6fc81c01c6df137fd0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/0314e2d49939a9831929d6fc81c01c6df137fd0a", + "reference": "0314e2d49939a9831929d6fc81c01c6df137fd0a", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "^3", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v6.3.8" + }, + "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": "2023-11-06T18:31:59+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "1ee70e699b41909c209a0c930f11034b93578654" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/1ee70e699b41909c209a0c930f11034b93578654", + "reference": "1ee70e699b41909c209a0c930f11034b93578654", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "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": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.4.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": "2023-07-30T20:28:31+00:00" + }, { "name": "symfony/http-foundation", "version": "v6.3.8", diff --git a/api/config/services.yaml b/api/config/services.yaml index 2d6a76f..4dce8b0 100644 --- a/api/config/services.yaml +++ b/api/config/services.yaml @@ -4,6 +4,7 @@ # Put parameters here that don't need to change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration parameters: + app.api_endpoint: '%env(API_ENDPOINT)%' services: # default configuration for services in *this* file @@ -22,3 +23,17 @@ services: # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones + + # @INFO: Register FruitsApi + App\Service\FruitsApiClient: + arguments: + $apiEndpoint: '%app.api_endpoint%' + + # @INFO: Register FruitsApiEntity + App\Entity\Api\FruitApiEntity: + + # @INFO: Register FruitAggregator + App\Service\FruitAggregator: + + # @INFO: Register FruitDecomulator + App\Service\FruitDecomulator: diff --git a/api/src/Controller/IndexController.php b/api/src/Controller/IndexController.php new file mode 100644 index 0000000..b0d920d --- /dev/null +++ b/api/src/Controller/IndexController.php @@ -0,0 +1,17 @@ +__invoke()); + } +} diff --git a/api/src/Entity/Api/FruitApiEntity.php b/api/src/Entity/Api/FruitApiEntity.php new file mode 100644 index 0000000..79cc725 --- /dev/null +++ b/api/src/Entity/Api/FruitApiEntity.php @@ -0,0 +1,83 @@ +items = $items; + + return $this; + } + + /** + * @return array + */ + public function getItems(): array + { + return $this->items; + } + + /** + * @var int $total + * + * @return self + */ + public function setTotal(int $total): self + { + $this->total = $total; + + return $this; + } + + /** + * @return int + */ + public function getTotal(): int + { + return $this->total; + } + + /** + * A helper function to return the entire properties as an array + * + * @return array + */ + public function toArray() + { + return get_object_vars($this); + } + + /** + * Returns the first item in the array + * + * @return array + */ + public function first(): array + { + return current($this->toArray()['items']); + } + + /** + * Returns the last item in the array + * + * @return array + */ + public function last(): array + { + return end($this->toArray()['items']); + } +} diff --git a/api/src/Service/FruitAggregator.php b/api/src/Service/FruitAggregator.php new file mode 100644 index 0000000..ca522e1 --- /dev/null +++ b/api/src/Service/FruitAggregator.php @@ -0,0 +1,35 @@ +api->get(); + + // @INFO: Throw an exception if the data is invalid + if (! $fruitsFromApi instanceof FruitApiEntity) { + throw new \Exception('Invalid response from the API endpoint'); + } + + // @INFO: Do nothing if nothing is returned from the API call + if (0 === $fruitsFromApi->getItems()) { + return; + } + + // @INFO: Check if the fetched data from API already exist in the database table + } +} diff --git a/api/src/Service/FruitDecomulator.php b/api/src/Service/FruitDecomulator.php new file mode 100644 index 0000000..5f34a9f --- /dev/null +++ b/api/src/Service/FruitDecomulator.php @@ -0,0 +1,30 @@ +em->getConnection(); + $platform = $connection->getDatabasePlatform(); + $table = $this->em->getClassMetadata(Fruit::class)->getTableName(); + $connection->executeStatement($platform->getTruncateTableSQL($table, true)); + } +} diff --git a/api/src/Service/FruitsApiClient.php b/api/src/Service/FruitsApiClient.php new file mode 100644 index 0000000..4d3b93c --- /dev/null +++ b/api/src/Service/FruitsApiClient.php @@ -0,0 +1,54 @@ +apiEndpoint . "/$id" : + $this->apiEndpoint . '/all'; + + $response = $this->client->request('GET', $endpoint); + + // @INFO: Return blank if HTTP status code is not equal to 200 + if ($response->getStatusCode() !== Response::HTTP_OK) { + return null; + } + + // @INFO: Since if there's an ID provided, it will store into a single + // dimensional array, we have to convert it to two-dimensional array + // to get an accurate item count. + if ($id) { + return $this->entity + ->setItems([$response->toArray()]) + ->setTotal(1); + } + + return $this->entity + ->setItems($data = $response->toArray()) + ->setTotal(count($data)); + } +} diff --git a/todo.md b/todo.md index dc5e9cb..6ed45a7 100644 --- a/todo.md +++ b/todo.md @@ -18,8 +18,8 @@ API: [x] - Setup commands - fruits:fetch - fruits:purge -[ ] - Setup service -[ ] - Setup endpoints +[x] - Setup service +[x] - Setup endpoints Database