From 7598d3f0ef53b9a72de5ce8d5a293c376243f5dc Mon Sep 17 00:00:00 2001 From: Gustavo Campana Date: Mon, 20 Mar 2023 12:47:23 -0300 Subject: [PATCH 1/5] 2. Servicio listar productos --- .../Controllers/Api/V1/ProductController.php | 18 ++++++++++++ app/Models/Product.php | 13 +++++++++ app/Repository/ProductRepository.php | 20 +++++++++++++ routes/api.php | 6 ++-- tests/Feature/ProductListTest.php | 29 +++++++++++++++++++ 5 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 app/Http/Controllers/Api/V1/ProductController.php create mode 100644 app/Models/Product.php create mode 100644 app/Repository/ProductRepository.php create mode 100644 tests/Feature/ProductListTest.php diff --git a/app/Http/Controllers/Api/V1/ProductController.php b/app/Http/Controllers/Api/V1/ProductController.php new file mode 100644 index 0000000..0d1535c --- /dev/null +++ b/app/Http/Controllers/Api/V1/ProductController.php @@ -0,0 +1,18 @@ +get('idCategory'); + $products = $productRepository->findProducts($categoryId); + return response()->json($products); + } +} \ No newline at end of file diff --git a/app/Models/Product.php b/app/Models/Product.php new file mode 100644 index 0000000..da2fc5a --- /dev/null +++ b/app/Models/Product.php @@ -0,0 +1,13 @@ +belongsToMany(Category::class); + } +} \ No newline at end of file diff --git a/app/Repository/ProductRepository.php b/app/Repository/ProductRepository.php new file mode 100644 index 0000000..070e9b2 --- /dev/null +++ b/app/Repository/ProductRepository.php @@ -0,0 +1,20 @@ +select('products.id', 'products.descripcion', 'products.precio', 'categories.nombre as categoria') + ->join('categories','products.category_id', '=', 'categories.id'); + if(!empty($categoryId)){ + $qb->where('products.category_id', '=', $categoryId); + } + return $qb->get()->toArray(); + } +} + diff --git a/routes/api.php b/routes/api.php index 92542e3..99c26ca 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,6 +1,8 @@ only('index'); \ No newline at end of file +Route::apiResource('v1/categories', App\Http\Controllers\Api\V1\CategoryController::class)->only('index'); + +Route::apiResource('v1/products', App\Http\Controllers\Api\V1\ProductController::class) + ->only('index'); diff --git a/tests/Feature/ProductListTest.php b/tests/Feature/ProductListTest.php new file mode 100644 index 0000000..1d9fa8f --- /dev/null +++ b/tests/Feature/ProductListTest.php @@ -0,0 +1,29 @@ +get("/api/v1/products?idCategory=$idCategory"); + $response->assertStatus(200); + $response->assertJsonCount(1); + } + + /** + * @test + */ + public function canRetrieveProductsWithoutCategory() + { + $response = $this->get("/api/v1/products"); + $response->assertStatus(200); + $response->assertJsonCount(3); + } +} From 9390740a23b78603c9f11a3da2da818a140507b7 Mon Sep 17 00:00:00 2001 From: Gustavo Campana Date: Mon, 20 Mar 2023 12:49:58 -0300 Subject: [PATCH 2/5] =?UTF-8?q?3.=20Consulta=20categor=C3=ADa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Repository/CategoryRepository.php | 19 +++++++++++++++++ tests/Feature/CategoryRepositoryTest.php | 27 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 app/Repository/CategoryRepository.php create mode 100644 tests/Feature/CategoryRepositoryTest.php diff --git a/app/Repository/CategoryRepository.php b/app/Repository/CategoryRepository.php new file mode 100644 index 0000000..deba471 --- /dev/null +++ b/app/Repository/CategoryRepository.php @@ -0,0 +1,19 @@ +select(DB::raw("c1.nombre || COALESCE(' -> ' || c2.nombre, '') || COALESCE(' -> ' || c3.nombre, '') AS path")) + ->join('categories as c2', 'c1.id', '=', 'c2.idcategoriapadre') + ->leftJoin('categories as c3', 'c2.id', '=', 'c3.idcategoriapadre') + ->whereNull('c1.idcategoriapadre') + ->pluck('path') + ->toArray(); + } +} \ No newline at end of file diff --git a/tests/Feature/CategoryRepositoryTest.php b/tests/Feature/CategoryRepositoryTest.php new file mode 100644 index 0000000..cca4ba1 --- /dev/null +++ b/tests/Feature/CategoryRepositoryTest.php @@ -0,0 +1,27 @@ + Adidas', + 'Indumentaria -> Nike', + 'Calzado -> Calzado Dita', + 'Calzado -> Calzado Nike', + 'Calzado -> Calzado Adidas', + 'Calzado -> Running -> Adidas', + 'Calzado -> Running -> Puma', + 'Calzado -> Crocs' + ]; + $categoryRepository = new CategoryRepository(); + $result = $categoryRepository->getCategoriesPath(); + $this->assertEquals($expected, $result); + } +} \ No newline at end of file From 79b994b194a4b60c4f320d8148631e81d6d0cb00 Mon Sep 17 00:00:00 2001 From: Gustavo Campana Date: Mon, 20 Mar 2023 12:50:40 -0300 Subject: [PATCH 3/5] 4. Cron job backup --- scripts/create-cron-entry.sh | 5 +++++ scripts/make-db-backup.sh | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100755 scripts/create-cron-entry.sh create mode 100755 scripts/make-db-backup.sh diff --git a/scripts/create-cron-entry.sh b/scripts/create-cron-entry.sh new file mode 100755 index 0000000..302ca52 --- /dev/null +++ b/scripts/create-cron-entry.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +SCRIPT_BACKUP="$PWD/make-db-backup.sh" + +(crontab -l; echo "0 0 * * * $SCRIPT_BACKUP") | sort -u | crontab -u $USER - \ No newline at end of file diff --git a/scripts/make-db-backup.sh b/scripts/make-db-backup.sh new file mode 100755 index 0000000..aa76f1a --- /dev/null +++ b/scripts/make-db-backup.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +source ../.env +DATE=$(date +%F) +FILE="backup_$DATE.sql" + +mysqldump -u $DB_USERNAME -h localhost -P $DB_PORT -p$DB_PASSWORD $DB_DATABASE > $FILE \ No newline at end of file From fb1988a75be1a925dff08bcc5195cdd81a182609 Mon Sep 17 00:00:00 2001 From: Gustavo Campana Date: Mon, 20 Mar 2023 12:51:41 -0300 Subject: [PATCH 4/5] 5. Artisan command --- app/Console/Commands/NicArWhoisCommand.php | 44 +++++++++++++++++++ app/Service/RDPA/RDPAQuery.php | 45 ++++++++++++++++++++ tests/Feature/NicArWhoisCommandTest.php | 49 ++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 app/Console/Commands/NicArWhoisCommand.php create mode 100644 app/Service/RDPA/RDPAQuery.php create mode 100644 tests/Feature/NicArWhoisCommandTest.php diff --git a/app/Console/Commands/NicArWhoisCommand.php b/app/Console/Commands/NicArWhoisCommand.php new file mode 100644 index 0000000..8d6cf8c --- /dev/null +++ b/app/Console/Commands/NicArWhoisCommand.php @@ -0,0 +1,44 @@ +argument('domain'); + $expirationDate = $RDPAQuery->makeCall($domain); + $this->line($expirationDate); + return 0; + } catch(\InvalidArgumentException $e){ + $this->line('Dominio inválido. Solo se permiten dominios .com.ar'); + return -1; + } catch(\UnexpectedValueException $e){ + $this->line('Dominio no encontrado'); + return -1; + } + } +} diff --git a/app/Service/RDPA/RDPAQuery.php b/app/Service/RDPA/RDPAQuery.php new file mode 100644 index 0000000..52daaf2 --- /dev/null +++ b/app/Service/RDPA/RDPAQuery.php @@ -0,0 +1,45 @@ +callRDPAForDomainEvents($domain); + return $this->getExpirationDate($domainEvents); + } + + /** + * @throws \UnexpectedValueException + */ + public function callRDPAForDomainEvents(string $domain): array + { + $response = Http::get("https://rdap.nic.ar/domain/$domain"); + $domainEvents = $response->json('events'); + if(!is_array($domainEvents) || count($domainEvents) == 0){ + throw new \UnexpectedValueException('Dominio no encontrado'); + } + return $domainEvents; + } + + private function getExpirationDate(array $domainEvents): string + { + $expirationDate = ''; + foreach($domainEvents as $event){ + if($event['eventAction'] == 'expiration'){ + $expirationDate = (new \DateTime($event['eventDate']))->format('h:i:s d/M/Y'); + } + } + return $expirationDate; + } +} \ No newline at end of file diff --git a/tests/Feature/NicArWhoisCommandTest.php b/tests/Feature/NicArWhoisCommandTest.php new file mode 100644 index 0000000..05b9c06 --- /dev/null +++ b/tests/Feature/NicArWhoisCommandTest.php @@ -0,0 +1,49 @@ +artisan("nick-ar:whois $domain") + ->expectsOutput('10:00:00 01/Jan/2024'); + } + + /** + * @test + */ + public function cantObtainExpirationDateForUnregisteredDomain() + { + $domain = "donwebtest.com.ar"; + $this->artisan("nick-ar:whois $domain") + ->expectsOutput('Dominio no encontrado'); + } + + /** + * @test + */ + public function cantRunCommandForNonComArDomain() + { + $domain = "test.com"; + $this->artisan("nick-ar:whois $domain") + ->expectsOutput('Dominio inválido. Solo se permiten dominios .com.ar') + ->assertExitCode(-1); + } + /** + * @test + */ + public function cantRunCommandWithoutDomainParameter() + { + $this->expectException(RuntimeException::class); + $this->artisan("nick-ar:whois"); + } +} From 39bf6c56375be51bf21a34d88acb582709028e56 Mon Sep 17 00:00:00 2001 From: Gustavo Campana Date: Mon, 20 Mar 2023 12:53:05 -0300 Subject: [PATCH 5/5] 6. Whois socket service --- .../Api/V1/WhoisQueryController.php | 20 +++++++ app/Service/Whois/DonwebResponseParser.php | 21 +++++++ app/Service/Whois/ResponseParserFactory.php | 15 +++++ app/Service/Whois/WhoisQuerier.php | 58 ++++++++++++++++++ app/Service/Whois/WhoisResponseParser.php | 8 +++ app/Service/Whois/WhoisService.php | 19 ++++++ routes/api.php | 2 + tests/Feature/WhoisQueryTest.php | 60 +++++++++++++++++++ 8 files changed, 203 insertions(+) create mode 100644 app/Http/Controllers/Api/V1/WhoisQueryController.php create mode 100644 app/Service/Whois/DonwebResponseParser.php create mode 100644 app/Service/Whois/ResponseParserFactory.php create mode 100644 app/Service/Whois/WhoisQuerier.php create mode 100644 app/Service/Whois/WhoisResponseParser.php create mode 100644 app/Service/Whois/WhoisService.php create mode 100644 tests/Feature/WhoisQueryTest.php diff --git a/app/Http/Controllers/Api/V1/WhoisQueryController.php b/app/Http/Controllers/Api/V1/WhoisQueryController.php new file mode 100644 index 0000000..a6e4644 --- /dev/null +++ b/app/Http/Controllers/Api/V1/WhoisQueryController.php @@ -0,0 +1,20 @@ +get('domain'); + $queryResult = $whoisService->makeQuery($whoisServer, $domain); + return response()->json($queryResult); + } +} diff --git a/app/Service/Whois/DonwebResponseParser.php b/app/Service/Whois/DonwebResponseParser.php new file mode 100644 index 0000000..3aa825f --- /dev/null +++ b/app/Service/Whois/DonwebResponseParser.php @@ -0,0 +1,21 @@ +getNameServers($responseLines); + } + + public function getNameServers(array $textLines): array + { + $nameServers = array_filter($textLines, function($el){ + return str_contains(strtolower($el), 'name server'); + }); + $nameServers = str_replace('Name Server: ', '', $nameServers); + return [ 'Name Servers' => array_values($nameServers) ]; + } +} \ No newline at end of file diff --git a/app/Service/Whois/ResponseParserFactory.php b/app/Service/Whois/ResponseParserFactory.php new file mode 100644 index 0000000..09296c4 --- /dev/null +++ b/app/Service/Whois/ResponseParserFactory.php @@ -0,0 +1,15 @@ +connectToServer($whoisServer); + $this->writeDomain($domainToQuery); + $response = $this->readResponse(); + $this->closeConnection(); + return $response; + } + + /** + * @param string $whoisServer + * @return void + * @throws \ErrorException + */ + private function connectToServer(string $whoisServer): void + { + $this->socket = fsockopen("tcp://$whoisServer", $this->port, $errno, $errstr, $this->timeOut); + } + + private function writeDomain(string $domainToQuery): void + { + fwrite($this->socket, $domainToQuery."\r\n"); + } + + private function readResponse(): string + { + $response = ''; + while(!feof($this->socket)) { + $response .= fgets($this->socket, 1024); + } + return $response; + } + + private function closeConnection(): void + { + fclose($this->socket); + } +} \ No newline at end of file diff --git a/app/Service/Whois/WhoisResponseParser.php b/app/Service/Whois/WhoisResponseParser.php new file mode 100644 index 0000000..92d9525 --- /dev/null +++ b/app/Service/Whois/WhoisResponseParser.php @@ -0,0 +1,8 @@ +whoisQuerier = $whoisQuerier; + } + public function makeQuery(string $whoisServer, string $domainToQuery): array + { + $response = $this->whoisQuerier->queryServer($whoisServer, $domainToQuery); + $parser = ResponseParserFactory::getResponseParser($whoisServer); + return $parser->parseResponse($response); + } +} \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 99c26ca..d9c8e24 100644 --- a/routes/api.php +++ b/routes/api.php @@ -6,3 +6,5 @@ Route::apiResource('v1/products', App\Http\Controllers\Api\V1\ProductController::class) ->only('index'); + +Route::get('v1/whois', 'App\Http\Controllers\Api\V1\WhoisQueryController@index'); diff --git a/tests/Feature/WhoisQueryTest.php b/tests/Feature/WhoisQueryTest.php new file mode 100644 index 0000000..53dd255 --- /dev/null +++ b/tests/Feature/WhoisQueryTest.php @@ -0,0 +1,60 @@ +get("/api/v1/whois?domain=$domain"); + $response->assertStatus(200); + } + + /** + * @test + */ + public function aValidDomainShouldReturnDNS() + { + $domain = 'sistemasdg.com'; + $response = $this->get("/api/v1/whois?domain=$domain"); + $response->assertStatus(200); + $response->assertJson([ + 'Name Servers' => [ + 'ns1.donweb.cl', + 'ns1.donweb.co', + 'ns1.donweb.mx', + 'ns1.donweb.uy', + 'ns1.traxhost.com', + 'ns2.donweb.bo', + 'ns2.donweb.com.br', + 'ns2.donweb.pe', + 'ns3.hostmar.com', + //'NS1.EMPRETIENDA.NET', + //'NS2.EMPRETIENDA.NET', + //'NS3.EMPRETIENDA.NET', + //'NS4.EMPRETIENDA.NET' + ] + ]); + } + + /** + * @test + */ + public function anInvalidDomainShouldReturnEmptyNameServers(): void + { + $domain = 'testfornoregistereddomain.com'; + $response = $this->get("/api/v1/whois?domain=$domain"); + $response->assertStatus(200); + $response->assertJson([ + 'Name Servers' => [] + ]); + } + +}