From 1d7c484510e606d70333f86c6ecf46f5d2d0ee2f Mon Sep 17 00:00:00 2001 From: ncoronel Date: Wed, 12 Aug 2026 08:40:45 -0300 Subject: [PATCH] feat(scanner): implement scanner bootstrap functionality with controller, request, resource, and service; add routes and migration for scanner domain --- .../ScannerBootstrapController.php | 20 +++++ .../Requests/ScannerBootstrapRequest.php | 5 ++ .../Resources/ScannerBootstrapResource.php | 5 ++ .../Services/ScannerBootstrapService.php | 19 +++++ app/Domains/Bootstrap/routes/api.php | 1 + app/Domains/Bootstrap/routes/scanner.php | 9 +++ app/Domains/Tenant/Models/WebsiteType.php | 1 + ...d_scanner_domain_to_website_type_table.php | 23 ++++++ database/seeders/WebsiteTypeSeeder.php | 2 + .../Feature/Seeders/WebsiteTypeSeederTest.php | 2 + .../Tenant/BootstrapScannerControllerTest.php | 76 +++++++++++++++++++ .../Feature/Tenant/WebsiteTypeServiceTest.php | 2 + 12 files changed, 165 insertions(+) create mode 100644 app/Domains/Bootstrap/Controllers/ScannerBootstrapController.php create mode 100644 app/Domains/Bootstrap/Requests/ScannerBootstrapRequest.php create mode 100644 app/Domains/Bootstrap/Resources/ScannerBootstrapResource.php create mode 100644 app/Domains/Bootstrap/Services/ScannerBootstrapService.php create mode 100644 app/Domains/Bootstrap/routes/scanner.php create mode 100644 database/migrations/2026_08_12_000000_add_scanner_domain_to_website_type_table.php create mode 100644 tests/Feature/Tenant/BootstrapScannerControllerTest.php diff --git a/app/Domains/Bootstrap/Controllers/ScannerBootstrapController.php b/app/Domains/Bootstrap/Controllers/ScannerBootstrapController.php new file mode 100644 index 0000000..41ef13a --- /dev/null +++ b/app/Domains/Bootstrap/Controllers/ScannerBootstrapController.php @@ -0,0 +1,20 @@ +bootstrapService->get((string) $request->validated('dominio')) + ); + } +} diff --git a/app/Domains/Bootstrap/Requests/ScannerBootstrapRequest.php b/app/Domains/Bootstrap/Requests/ScannerBootstrapRequest.php new file mode 100644 index 0000000..df410e6 --- /dev/null +++ b/app/Domains/Bootstrap/Requests/ScannerBootstrapRequest.php @@ -0,0 +1,5 @@ + WebsiteType::query() + ->with(['siteLogo', 'footerLogo']) + ->where('scanner_domain', $domain) + ->firstOrFail(), + ]; + } +} diff --git a/app/Domains/Bootstrap/routes/api.php b/app/Domains/Bootstrap/routes/api.php index b467c4a..ab36e67 100644 --- a/app/Domains/Bootstrap/routes/api.php +++ b/app/Domains/Bootstrap/routes/api.php @@ -7,3 +7,4 @@ Route::get('tenants/bootstrap/{dominio}', TenantBootstrapController::class) ->where('dominio', '.*'); require __DIR__.'/adminapp.php'; +require __DIR__.'/scanner.php'; diff --git a/app/Domains/Bootstrap/routes/scanner.php b/app/Domains/Bootstrap/routes/scanner.php new file mode 100644 index 0000000..1469278 --- /dev/null +++ b/app/Domains/Bootstrap/routes/scanner.php @@ -0,0 +1,9 @@ +string('scanner_domain')->nullable()->unique()->after('dominio'); + }); + } + + public function down(): void + { + Schema::table('website_type', function (Blueprint $table): void { + $table->dropUnique(['scanner_domain']); + $table->dropColumn('scanner_domain'); + }); + } +}; diff --git a/database/seeders/WebsiteTypeSeeder.php b/database/seeders/WebsiteTypeSeeder.php index 303f74d..ade8463 100644 --- a/database/seeders/WebsiteTypeSeeder.php +++ b/database/seeders/WebsiteTypeSeeder.php @@ -31,6 +31,7 @@ class WebsiteTypeSeeder extends Seeder [ 'nombre' => 'ShopIt', 'dominio' => 'localhost', + 'scanner_domain' => 'scanner.localhost', ...self::PRESENTATION, 'site_logo' => $this->onTicketLogo(), 'footer_logo' => $this->onTicketFooterLogo(), @@ -63,6 +64,7 @@ class WebsiteTypeSeeder extends Seeder [ 'nombre' => 'OnTicket', 'dominio' => 'onticket.localhost', + 'scanner_domain' => 'scanner.onticket.localhost', ...self::PRESENTATION, 'site_logo' => $this->onTicketLogo(), 'footer_logo' => $this->onTicketFooterLogo(), diff --git a/tests/Feature/Seeders/WebsiteTypeSeederTest.php b/tests/Feature/Seeders/WebsiteTypeSeederTest.php index 8531865..5dbf1e7 100644 --- a/tests/Feature/Seeders/WebsiteTypeSeederTest.php +++ b/tests/Feature/Seeders/WebsiteTypeSeederTest.php @@ -43,6 +43,7 @@ class WebsiteTypeSeederTest extends TestCase $this->assertSame('ShopIt', $shopIt->nombre); $this->assertSame('localhost', $shopIt->dominio); + $this->assertSame('scanner.localhost', $shopIt->scanner_domain); $this->assertSame($expectedPresentation, $shopIt->only(array_keys($expectedPresentation))); $this->assertSame('onticket_logo.png', $shopIt->siteLogo->filename); Storage::disk('s3')->assertExists($shopIt->siteLogo->path); @@ -70,6 +71,7 @@ class WebsiteTypeSeederTest extends TestCase $this->assertSame('OnTicket', $onTicket->nombre); $this->assertSame('onticket.localhost', $onTicket->dominio); + $this->assertSame('scanner.onticket.localhost', $onTicket->scanner_domain); $this->assertSame($expectedPresentation, $onTicket->only(array_keys($expectedPresentation))); $this->assertSame('onticket_logo.png', $onTicket->siteLogo->filename); Storage::disk('s3')->assertExists($onTicket->siteLogo->path); diff --git a/tests/Feature/Tenant/BootstrapScannerControllerTest.php b/tests/Feature/Tenant/BootstrapScannerControllerTest.php new file mode 100644 index 0000000..e4eb18f --- /dev/null +++ b/tests/Feature/Tenant/BootstrapScannerControllerTest.php @@ -0,0 +1,76 @@ +create(); + + WebsiteType::query()->create([ + 'codigo' => 'shopit', + 'nombre' => 'ShopIt', + 'dominio' => 'admin.shopit.test', + 'scanner_domain' => 'scanner.shopit.test', + 'primary_color' => '#112233', + 'secondary_color' => '#445566', + 'danger_color' => '#aa0000', + 'success_color' => '#00aa00', + 'warning_color' => '#ffaa00', + 'body_color' => '#666666', + 'darker_body_color' => '#333333', + 'surface_color' => '#ffffff', + 'background_color' => '#f8f8f8', + 'border_color' => '#eaeaea', + 'login_header_footer_color' => '#313131', + 'site_logo' => $siteLogo->id, + ]); + + $this->getJson('/api/v1/scanner/bootstrap/SCANNER.SHOPIT.TEST') + ->assertOk() + ->assertJsonPath('data.website_type_code', 'shopit') + ->assertJsonPath('data.primary_color', '#112233') + ->assertJsonPath('data.site_logo', $siteLogo->getTemporaryUrl(1440)) + ->assertJsonPath('data.footer_logo', null) + ->assertJsonMissingPath('data.codigo') + ->assertJsonMissingPath('data.nombre') + ->assertJsonMissingPath('data.dominio') + ->assertJsonMissingPath('data.scanner_domain'); + } + + public function test_it_does_not_match_the_admin_app_domain(): void + { + WebsiteType::query()->create([ + 'codigo' => 'shopit', + 'nombre' => 'ShopIt', + 'dominio' => 'admin.shopit.test', + 'scanner_domain' => 'scanner.shopit.test', + ]); + + $this->getJson('/api/v1/scanner/bootstrap/admin.shopit.test') + ->assertNotFound(); + } + + public function test_it_returns_not_found_for_an_unknown_scanner_domain(): void + { + $this->getJson('/api/v1/scanner/bootstrap/unknown.test') + ->assertNotFound(); + } + + public function test_it_rejects_an_invalid_domain(): void + { + $invalidDomain = str_repeat('a', 256); + + $this->getJson("/api/v1/scanner/bootstrap/{$invalidDomain}") + ->assertUnprocessable() + ->assertJsonValidationErrors(['dominio']); + } +} diff --git a/tests/Feature/Tenant/WebsiteTypeServiceTest.php b/tests/Feature/Tenant/WebsiteTypeServiceTest.php index d21e808..94767ab 100644 --- a/tests/Feature/Tenant/WebsiteTypeServiceTest.php +++ b/tests/Feature/Tenant/WebsiteTypeServiceTest.php @@ -20,6 +20,7 @@ class WebsiteTypeServiceTest extends TestCase 'codigo' => 'marketplace', 'nombre' => 'Marketplace', 'dominio' => 'marketplace.test', + 'scanner_domain' => 'scanner.marketplace.test', 'primary_color' => '#111111', 'secondary_color' => '#222222', 'danger_color' => '#ff0000', @@ -48,6 +49,7 @@ class WebsiteTypeServiceTest extends TestCase $this->assertDatabaseHas('website_type', [ 'codigo' => 'marketplace', 'dominio' => 'marketplace.test', + 'scanner_domain' => 'scanner.marketplace.test', 'warning_color' => '#ffaa00', 'body_color' => '#666666', 'darker_body_color' => '#333333',