diff --git a/.env.example b/.env.example index eac828c..4ab1c9c 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,9 @@ APP_URL=http://localhost # WKHTML_PDF_BINARY='"C:\wkhtmltopdf\bin\wkhtmltopdf.exe"' 3 👈🏻 En Windows, especifica la ruta de wkhtmltopdf.exe +API_KEY_APILAYER= +API_KEY_ABSTRACTAPI= + LOG_CHANNEL=stack LOG_LEVEL=debug diff --git a/README.md b/README.md index 1164a0a..5ddc6ed 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,18 @@ Testeado en **Laravel 8** y **PHP 8**, pero puede servir en futuras versiones (y ## Importante, antes de usar: +Si quieres probar manualmente el patrón Adapter (que usa APIs externas para verificar si un email es válido) debes crear una cuenta en ambos proveedores. + +- [API Layer (Mailbox Layer)](https://mailboxlayer.com/) +- [Abstract API](https://www.abstractapi.com/) + +A continuación, debes generar tokens y copiarlos en el `.env`: + +```dotenv +API_KEY_APILAYER=#{Tu token acá} +API_KEY_ABSTRACTAPI=#{Tu token acá} +``` + Si usas Windows: 1. Instala [wkhtmltopdf](https://wkhtmltopdf.org/downloads.html), de preferencia en la ruta ```C:\wkhtmltopdf``` 2. Abre el archivo `.env` y busca la llave `WKHTML_PDF_BINARY`. Pon la ruta completa del ejecutable de wkhtmltopdf (si instalaste en ```C:\wkhtmltopdf```, sólo debes descomentar esa línea, de lo contrario, ya sabes qué hacer: Escribirla completa) @@ -21,7 +33,7 @@ Si usas Windows: - [ ] Builder - [ ] State - [x] [Pipeline](https://github.com/sebacarrasco93/patrones-laravel/commit/51d2dfcb1f71e9cd76c9413a61ec84f9e6127235) -- [ ] Adapter +- [x] Adapter - [ ] Strategy - [ ] Chain of Responsability - [x] [Command](https://github.com/sebacarrasco93/patrones-laravel/commit/22eedf535e40d23145250d62770f679f96b38ae0) diff --git a/app/Contracts/VerifiableAdapter.php b/app/Contracts/VerifiableAdapter.php new file mode 100644 index 0000000..813516c --- /dev/null +++ b/app/Contracts/VerifiableAdapter.php @@ -0,0 +1,10 @@ +validate([ + 'name' => 'required|string|max:255', + 'email' => 'required|string|email|max:255|unique:users', + 'password' => ['required'], + ]); + + if (! $emailVerifier->verify($validated['email'])) { + return redirect()->back()->withErrors(['El email no es válido']); + } + + $user = User::create($validated); + + event(new Registered($user)); + + Auth::login($user); + + return redirect()->back()->withSuccess('Sesión iniciada correctamente'); + } +} diff --git a/app/Http/Controllers/PatronesController.php b/app/Http/Controllers/PatronesController.php index 50a5cdd..d29c46b 100644 --- a/app/Http/Controllers/PatronesController.php +++ b/app/Http/Controllers/PatronesController.php @@ -23,6 +23,13 @@ public function pipeline() return view('patrones.pipeline', compact('users')); } + public function adapter() + { + $users = \App\Models\User::get(); + + return view('patrones.adapter', compact('users')); + } + public function command() { $users = \App\Models\User::get(); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index ee8ca5b..a81fd66 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,9 @@ namespace App\Providers; +use App\Contracts\VerifiableAdapter; +use App\Services\Adapters\AbstractApiAdapter; +use App\Services\Adapters\MailboxLayerAdapter; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -13,7 +16,17 @@ class AppServiceProvider extends ServiceProvider */ public function register() { - // + /* Usamos Mailbox (ApiLayer) */ + $this->app->bind( + VerifiableAdapter::class, + config('adapter.driver',MailboxLayerAdapter::class) + ); + + /* Y si falla o pasa algo, cambiamos acá, y seguimos usando AbstractApi */ + // $this->app->bind( + // VerifiableAdapter::class, + // config('adapter.driver',AbstractApiAdapter::class) + // ); } /** diff --git a/app/Services/Adapters/AbstractApiAdapter.php b/app/Services/Adapters/AbstractApiAdapter.php new file mode 100644 index 0000000..dcc1a1e --- /dev/null +++ b/app/Services/Adapters/AbstractApiAdapter.php @@ -0,0 +1,36 @@ +client = Http::baseUrl('https://emailvalidation.abstractapi.com'); + } + + public function verify(string $email) : bool + { + $this->response = $this->client->get('v1', [ + 'api_key' => config('external.api_key_abstractapi'), + 'email' => $email, + ]); + + return $this->checkResponse(); + } + + public function checkResponse() : bool + { + if (isset($this->response['error'])) { + throw new \Exception('Se produjo un error, revisa si tu API_KEY_ABSTRACTAPI es válida'); + } + + return (bool) $this->response['is_smtp_valid']['value']; + } +} diff --git a/app/Services/Adapters/MailboxLayerAdapter.php b/app/Services/Adapters/MailboxLayerAdapter.php new file mode 100644 index 0000000..19f8860 --- /dev/null +++ b/app/Services/Adapters/MailboxLayerAdapter.php @@ -0,0 +1,38 @@ +client = Http::baseUrl('http://apilayer.net'); + } + + public function verify(string $email) : bool + { + $this->response = $this->client->get('/api/check', [ + 'access_key' => config('external.api_key_apilayer'), + 'email' => $email, + 'smtp' => 1, + 'format' => 1, + ]); + + return $this->checkResponse(); + } + + public function checkResponse() : bool + { + if (isset($this->response['error'])) { + throw new \Exception('Se produjo un error, revisa si tu API_KEY_APILAYER es válida'); + } + + return (bool) $this->response['mx_found']; + } +} diff --git a/config/external.php b/config/external.php new file mode 100644 index 0000000..b411a0c --- /dev/null +++ b/config/external.php @@ -0,0 +1,17 @@ + env('API_KEY_APILAYER'), + 'api_key_abstractapi' => env('API_KEY_ABSTRACTAPI'), +]; diff --git a/resources/views/index.blade.php b/resources/views/index.blade.php index c4b0fa2..d2b1a9e 100644 --- a/resources/views/index.blade.php +++ b/resources/views/index.blade.php @@ -4,5 +4,6 @@ Factory Factory Method Pipeline + Adapter Command @endsection diff --git a/resources/views/patrones/adapter.blade.php b/resources/views/patrones/adapter.blade.php new file mode 100644 index 0000000..50aaad3 --- /dev/null +++ b/resources/views/patrones/adapter.blade.php @@ -0,0 +1,32 @@ +@extends('layout') +@section('titulo', 'Patrón Adapter') + +@section('contenido') + @foreach ($users as $user) + + @endforeach + +
+ @csrf +
+

Agregar nueva persona

+
+
+ + +
+
+ + +
+
+ + +
+ +
+@endsection diff --git a/routes/web.php b/routes/web.php index f8646b1..03d1bdc 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,6 @@ name('patrones.factory'); Route::get('factory-method', [PatronesController::class, 'factoryMethod'])->name('patrones.factoryMethod'); Route::get('pipeline', [PatronesController::class, 'pipeline'])->name('patrones.pipeline'); +Route::get('adapter', [PatronesController::class, 'adapter'])->name('patrones.adapter'); Route::get('command', [PatronesController::class, 'command'])->name('patrones.command'); Route::post('factory/{report}', FactoryController::class)->name('factory'); Route::post('factory-method/{report}', FactoryMethodController::class)->name('factoryMethod'); Route::post('pipeline/{user}', PipelineController::class)->name('pipeline'); +Route::post('adapter', AdapterController::class)->name('adapter'); Route::post('command/{user}', CommandController::class)->name('command'); diff --git a/tests/Feature/AdapterTest.php b/tests/Feature/AdapterTest.php new file mode 100644 index 0000000..0795ed9 --- /dev/null +++ b/tests/Feature/AdapterTest.php @@ -0,0 +1,98 @@ +bind(VerifiableAdapter::class, config('adapter.driver',MailboxLayerAdapter::class)); + + // 😎 Mocking de solicitud Http + Http::fake([ + 'apilayer.net/*' => Http::response([ + 'mx_found' => $emailValido, + ], 200), + ]); + } + + protected function forzarAbstractApi($emailValido = true) + { + // 💡 Forzar el uso de AbstractApi, para simular funcionamiento de AppServiceProvider.php + app()->bind(VerifiableAdapter::class, config('adapter.driver',AbstractApiAdapter::class)); + + // 😎 Mocking de solicitud Http + Http::fake([ + 'emailvalidation.abstractapi.com/*' => Http::response([ + 'is_smtp_valid' => ['value' => $emailValido] + ], 200), + ]); + } + + /** @test */ + function puede_crear_un_usuario_usando_el_proveedor_mailboxlayer_con_adapter() + { + $this->forzarMailboxLayer(true); + + $data = ['name' => 'Nombre que funciona', 'email' => 'existente@dominio.cl', 'password' => 'password']; + + $request = $this->post(route('adapter'), $data); + + $response = $this->get(route('patrones.adapter')); + + $response->assertSee('Nombre que funciona'); + } + + /** @test */ + function puede_crear_un_usuario_usando_el_proveedor_abstractapi_con_adapter() + { + $this->forzarAbstractApi(true); + + $data = ['name' => 'Nombre que funciona', 'email' => 'existente@dominio.cl', 'password' => 'password']; + + $request = $this->post(route('adapter'), $data); + + $response = $this->get(route('patrones.adapter')); + + $response->assertSee('Nombre que funciona'); + } + + /** @test */ + function no_puede_crear_un_usuario_usando_el_proveedor_mailboxlayer_con_adapter() + { + $this->forzarMailboxLayer(false); + + $data = ['name' => 'Nombre que no debería aparecer', 'email' => 'repetido@aaaaaa.tk', 'password' => 'password']; + + $request = $this->post(route('adapter'), $data); + + $response = $this->get(route('patrones.adapter')); + + $response->assertDontSee('Nombre que no debería aparecer'); + } + + /** @test */ + function no_puede_crear_un_usuario_usando_el_proveedor_abstractapi_con_adapter() + { + $this->forzarAbstractApi(false); + + $data = ['name' => 'Nombre que no debería aparecer', 'email' => 'repetido@aaaaaa.tk', 'password' => 'password']; + + $request = $this->post(route('adapter'), $data); + + $response = $this->get(route('patrones.adapter')); + + $response->assertDontSee('Nombre que no debería aparecer'); + } +}