From dd76d5d9513e556f189c4b9cc76dd2d21c43c042 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Fri, 31 Jul 2026 14:16:58 -0300 Subject: [PATCH] feat(website-type): add footer logo support in WebsiteType model, controller, and resource; update seeder and tests --- .../AdminApp/BootstrapAdminAppController.php | 2 +- app/Domains/Tenant/Models/WebsiteType.php | 9 +++ .../AdminApp/BootstrapAdminAppResource.php | 1 + .../Tenant/Services/WebsiteTypeService.php | 53 ++++++++++-------- ..._add_footer_logo_to_website_type_table.php | 31 ++++++++++ database/seeders/WebsiteTypeSeeder.php | 21 ++++++- .../website_types/onticket_footer_logo.png | Bin 0 -> 1249 bytes .../{shopit_logo.png => onticket_logo.png} | Bin .../Feature/Seeders/WebsiteTypeSeederTest.php | 7 ++- .../BootstrapAdminAppControllerTest.php | 7 ++- .../Feature/Tenant/WebsiteTypeServiceTest.php | 8 ++- 11 files changed, 111 insertions(+), 28 deletions(-) create mode 100644 database/migrations/2026_07_31_000600_add_footer_logo_to_website_type_table.php create mode 100644 public/images/website_types/onticket_footer_logo.png rename public/images/website_types/{shopit_logo.png => onticket_logo.png} (100%) diff --git a/app/Domains/Tenant/Controllers/AdminApp/BootstrapAdminAppController.php b/app/Domains/Tenant/Controllers/AdminApp/BootstrapAdminAppController.php index 9f67771..c7df1c2 100644 --- a/app/Domains/Tenant/Controllers/AdminApp/BootstrapAdminAppController.php +++ b/app/Domains/Tenant/Controllers/AdminApp/BootstrapAdminAppController.php @@ -17,7 +17,7 @@ class BootstrapAdminAppController extends Controller return BootstrapAdminAppResource::make( WebsiteType::query() - ->with('siteLogo') + ->with(['siteLogo', 'footerLogo']) ->where('dominio', $domain) ->firstOrFail() ); diff --git a/app/Domains/Tenant/Models/WebsiteType.php b/app/Domains/Tenant/Models/WebsiteType.php index b8376f5..79dcba8 100644 --- a/app/Domains/Tenant/Models/WebsiteType.php +++ b/app/Domains/Tenant/Models/WebsiteType.php @@ -25,6 +25,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; 'border_color', 'login_header_footer_color', 'site_logo', + 'footer_logo', ])] class WebsiteType extends Model { @@ -40,6 +41,14 @@ class WebsiteType extends Model return $this->belongsTo(Attachment::class, 'site_logo'); } + /** + * @return BelongsTo + */ + public function footerLogo(): BelongsTo + { + return $this->belongsTo(Attachment::class, 'footer_logo'); + } + /** * @return HasMany */ diff --git a/app/Domains/Tenant/Resources/AdminApp/BootstrapAdminAppResource.php b/app/Domains/Tenant/Resources/AdminApp/BootstrapAdminAppResource.php index 4cebcfe..8f021fe 100644 --- a/app/Domains/Tenant/Resources/AdminApp/BootstrapAdminAppResource.php +++ b/app/Domains/Tenant/Resources/AdminApp/BootstrapAdminAppResource.php @@ -33,6 +33,7 @@ class BootstrapAdminAppResource extends JsonResource 'border_color' => $this->border_color, 'login_header_footer_color' => $this->login_header_footer_color, 'site_logo' => $this->siteLogo?->getTemporaryUrl(1440), + 'footer_logo' => $this->footerLogo?->getTemporaryUrl(1440), ]; } } diff --git a/app/Domains/Tenant/Services/WebsiteTypeService.php b/app/Domains/Tenant/Services/WebsiteTypeService.php index 3ed20f7..bce4f8b 100644 --- a/app/Domains/Tenant/Services/WebsiteTypeService.php +++ b/app/Domains/Tenant/Services/WebsiteTypeService.php @@ -15,7 +15,7 @@ class WebsiteTypeService ) {} /** - * Create a website type and store its site logo. + * Create a website type and store its logos. * * @param array $data */ @@ -25,7 +25,7 @@ class WebsiteTypeService } /** - * Create or update a website type and replace its site logo when provided. + * Create or update a website type and replace its logos when provided. * * @param array $attributes * @param array $values @@ -44,34 +44,41 @@ class WebsiteTypeService private function save(WebsiteType $websiteType, array $data): WebsiteType { return DB::transaction(function () use ($websiteType, $data): WebsiteType { - $hasSiteLogo = array_key_exists('site_logo', $data); - $siteLogo = $data['site_logo'] ?? null; - $previousSiteLogo = $websiteType->exists - ? $websiteType->siteLogo()->first() - : null; + $previousLogos = []; - unset($data['site_logo']); - - if ($hasSiteLogo) { - $attachment = null; - - if ($siteLogo) { - $attachment = is_string($siteLogo) && Str::isUuid($siteLogo) - ? Attachment::query()->where('key', $siteLogo)->first() - : $this->attachmentService->store($siteLogo, 'website-types'); + foreach (['site_logo' => 'siteLogo', 'footer_logo' => 'footerLogo'] as $field => $relation) { + if (! array_key_exists($field, $data)) { + continue; } - $data['site_logo'] = $attachment?->id; + $logo = $data[$field]; + $previousLogos[$field] = $websiteType->exists + ? $websiteType->{$relation}()->first() + : null; + unset($data[$field]); + + $attachment = null; + + if ($logo) { + $attachment = is_string($logo) && Str::isUuid($logo) + ? Attachment::query()->where('key', $logo)->first() + : $this->attachmentService->store($logo, 'website-types'); + } + + $data[$field] = $attachment?->id; } $websiteType->fill($data)->save(); - if ( - $hasSiteLogo - && $previousSiteLogo instanceof Attachment - && $previousSiteLogo->id !== $websiteType->site_logo - ) { - $this->attachmentService->delete($previousSiteLogo); + foreach ($previousLogos as $field => $previousLogo) { + if ( + $previousLogo instanceof Attachment + && $previousLogo->id !== $websiteType->{$field} + && $previousLogo->id !== $websiteType->site_logo + && $previousLogo->id !== $websiteType->footer_logo + ) { + $this->attachmentService->delete($previousLogo); + } } return $websiteType; diff --git a/database/migrations/2026_07_31_000600_add_footer_logo_to_website_type_table.php b/database/migrations/2026_07_31_000600_add_footer_logo_to_website_type_table.php new file mode 100644 index 0000000..d32663d --- /dev/null +++ b/database/migrations/2026_07_31_000600_add_footer_logo_to_website_type_table.php @@ -0,0 +1,31 @@ +unsignedBigInteger('footer_logo')->nullable()->after('site_logo'); + + $table->foreign('footer_logo') + ->references('id') + ->on('attachments') + ->nullOnDelete(); + }); + } + + public function down(): void + { + Schema::table('website_type', function (Blueprint $table): void { + if (Schema::getConnection()->getDriverName() !== 'sqlite') { + $table->dropForeign(['footer_logo']); + } + + $table->dropColumn('footer_logo'); + }); + } +}; diff --git a/database/seeders/WebsiteTypeSeeder.php b/database/seeders/WebsiteTypeSeeder.php index 71a8c81..95b0889 100644 --- a/database/seeders/WebsiteTypeSeeder.php +++ b/database/seeders/WebsiteTypeSeeder.php @@ -33,6 +33,7 @@ class WebsiteTypeSeeder extends Seeder 'dominio' => 'localhost', ...self::PRESENTATION, 'site_logo' => $this->onTicketLogo(), + 'footer_logo' => $this->onTicketFooterLogo(), ], ); @@ -64,6 +65,7 @@ class WebsiteTypeSeeder extends Seeder 'dominio' => 'onticket.localhost', ...self::PRESENTATION, 'site_logo' => $this->onTicketLogo(), + 'footer_logo' => $this->onTicketFooterLogo(), ], ); @@ -115,7 +117,7 @@ class WebsiteTypeSeeder extends Seeder private function onTicketLogo(): UploadedFile { - $path = public_path('images/website_types/shopit_logo.png'); + $path = public_path('images/website_types/onticket_logo.png'); if (! file_exists($path)) { throw new RuntimeException("OnTicket logo not found at path: {$path}"); @@ -129,4 +131,21 @@ class WebsiteTypeSeeder extends Seeder true, ); } + + private function onTicketFooterLogo(): UploadedFile + { + $path = public_path('images/website_types/onticket_footer_logo.png'); + + if (! file_exists($path)) { + throw new RuntimeException("OnTicket footer logo not found at path: {$path}"); + } + + return new UploadedFile( + $path, + 'onticket_footer_logo.png', + 'image/png', + null, + true, + ); + } } diff --git a/public/images/website_types/onticket_footer_logo.png b/public/images/website_types/onticket_footer_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..dd869219370e166ddc74d61808d959cc27d6b6fa GIT binary patch literal 1249 zcmV<71Rnc|P)`bAy_gU)t2_r%BE}8HBn0!ZvRCFbWz|%pCz;=g~w{{jE!TN*sY@j`xv^>?&O z<_ZrZL1>QAB`;U{!583*BY^0N0JIQ6ICk>XeD6aj>0YHm6dm4JHtFze(y?PK4o>lv?Y zv5p#hhwP<;f5z+TxjkPCw44aqV2$scgXh%HyHgkRL;&WuRr)z(QX-doWZqex$?-6< z>$a~H(F(`GQ3(?puU<~-Fv3KM_QcY*@dDk*E%dRro;dW?<&y3hwl_O9p0hdt-!EOy zG0nmvkH%KJFHmy=Jlt0sGT#OtgQLIUnhv>Vt!1RPIfB>srXIWz`X+DrIzoHIi8XnN zW!+mQVpBzw#{HH9@I9g3cSR0GlUeN3*K>#MYJH<@*NJ>y(ibHJ0GlYZx2)xw99Kw= z(X)vLpRO`c=am?1`b$hbJRY#^s0R*!bx0S=ZgeDu*f^YkemdKnTDWw+f$$C=A+)RY zmuH_Yg{xHL#7pTghtna}{(f~4Nqu%5Wi9b3piUkuIj9US9JC3~FW{E<&U^?97=T;2 z419xdkONF%9EWy6Iad+b%3W)Jzjb8sO6FRg;3p5AvO9tSAP1-*yaWtAbrH`WaM_p| z^B8!NFLu|XlXP&BSKoE|jM^#- zTcEoXFL-{m(imk#2&kNyK@VWNp)z;S<-DGE@DogzIo4;IETW};zy{lOb*#}P7CsRk zmd=?ROCB3-Eweml<%|7$3rhDT>$xywZXCA9#`9T=)({d3XN{~WB>K=})9ng!0H(2Z zJr@?;6cPs?vw>rR*33S@)W;VO`yIAgK1DxD^h6!U^YfuXhCpELX|(A}Y)(jS(DiW^ zdS=lwnW*u~MY+AQ8+d7AIe*+z0n@DDfO;lN-BNVg!L}UAOsEC+*q00000 LNkvXXu0mjf`@=ij literal 0 HcmV?d00001 diff --git a/public/images/website_types/shopit_logo.png b/public/images/website_types/onticket_logo.png similarity index 100% rename from public/images/website_types/shopit_logo.png rename to public/images/website_types/onticket_logo.png diff --git a/tests/Feature/Seeders/WebsiteTypeSeederTest.php b/tests/Feature/Seeders/WebsiteTypeSeederTest.php index 47a93a6..e2747e3 100644 --- a/tests/Feature/Seeders/WebsiteTypeSeederTest.php +++ b/tests/Feature/Seeders/WebsiteTypeSeederTest.php @@ -21,7 +21,7 @@ class WebsiteTypeSeederTest extends TestCase $this->seed(WebsiteTypeSeeder::class); $this->assertSame(2, WebsiteType::query()->count()); - $this->assertSame(2, Attachment::query()->count()); + $this->assertSame(4, Attachment::query()->count()); $expectedPresentation = [ 'primary_color' => '#FF7006', @@ -46,6 +46,8 @@ class WebsiteTypeSeederTest extends TestCase $this->assertSame($expectedPresentation, $shopIt->only(array_keys($expectedPresentation))); $this->assertSame('onticket_logo.png', $shopIt->siteLogo->filename); Storage::disk('s3')->assertExists($shopIt->siteLogo->path); + $this->assertSame('onticket_footer_logo.png', $shopIt->footerLogo->filename); + Storage::disk('s3')->assertExists($shopIt->footerLogo->path); $this->assertSame(['carousel'], $shopIt->extras->pluck('codigo')->all()); $this->assertSame('Carrusel principal', $shopIt->extras->sole()->nombre); $this->assertSame([ @@ -71,7 +73,10 @@ class WebsiteTypeSeederTest extends TestCase $this->assertSame($expectedPresentation, $onTicket->only(array_keys($expectedPresentation))); $this->assertSame('onticket_logo.png', $onTicket->siteLogo->filename); Storage::disk('s3')->assertExists($onTicket->siteLogo->path); + $this->assertSame('onticket_footer_logo.png', $onTicket->footerLogo->filename); + Storage::disk('s3')->assertExists($onTicket->footerLogo->path); $this->assertNotSame($shopIt->site_logo, $onTicket->site_logo); + $this->assertNotSame($shopIt->footer_logo, $onTicket->footer_logo); $this->assertEqualsCanonicalizing( ['heroConfig', 'eventConfig'], $onTicket->extras->pluck('codigo')->all(), diff --git a/tests/Feature/Tenant/BootstrapAdminAppControllerTest.php b/tests/Feature/Tenant/BootstrapAdminAppControllerTest.php index be5b07a..b97c3e0 100644 --- a/tests/Feature/Tenant/BootstrapAdminAppControllerTest.php +++ b/tests/Feature/Tenant/BootstrapAdminAppControllerTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature\Tenant; +use App\Domains\Attachable\Models\Attachment; use App\Domains\Tenant\Models\WebsiteType; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -12,6 +13,8 @@ class BootstrapAdminAppControllerTest extends TestCase public function test_it_publicly_bootstraps_the_admin_app_by_domain(): void { + $footerLogo = Attachment::factory()->create(); + WebsiteType::query()->create([ 'codigo' => 'shopit', 'nombre' => 'ShopIt', @@ -27,6 +30,7 @@ class BootstrapAdminAppControllerTest extends TestCase 'background_color' => '#f8f8f8', 'border_color' => '#eaeaea', 'login_header_footer_color' => '#313131', + 'footer_logo' => $footerLogo->id, ]); $this->getJson('/api/v1/adminapp/bootstrap/ADMIN.SHOPIT.TEST') @@ -37,7 +41,8 @@ class BootstrapAdminAppControllerTest extends TestCase ->assertJsonPath('data.primary_color', '#112233') ->assertJsonPath('data.warning_color', '#ffaa00') ->assertJsonPath('data.login_header_footer_color', '#313131') - ->assertJsonPath('data.site_logo', null); + ->assertJsonPath('data.site_logo', null) + ->assertJsonPath('data.footer_logo', $footerLogo->getTemporaryUrl(1440)); } public function test_it_returns_not_found_for_an_unknown_domain(): void diff --git a/tests/Feature/Tenant/WebsiteTypeServiceTest.php b/tests/Feature/Tenant/WebsiteTypeServiceTest.php index ea3edd4..d21e808 100644 --- a/tests/Feature/Tenant/WebsiteTypeServiceTest.php +++ b/tests/Feature/Tenant/WebsiteTypeServiceTest.php @@ -12,7 +12,7 @@ class WebsiteTypeServiceTest extends TestCase { use RefreshDatabase; - public function test_it_creates_a_website_type_and_uploads_its_logo_to_s3(): void + public function test_it_creates_a_website_type_and_uploads_its_logos_to_s3(): void { Storage::fake('s3'); @@ -32,14 +32,19 @@ class WebsiteTypeServiceTest extends TestCase 'border_color' => '#dddddd', 'login_header_footer_color' => '#333333', 'site_logo' => UploadedFile::fake()->image('site-logo.png'), + 'footer_logo' => UploadedFile::fake()->image('footer-logo.png'), ]); $siteLogo = $websiteType->siteLogo()->firstOrFail(); + $footerLogo = $websiteType->footerLogo()->firstOrFail(); $this->assertSame('marketplace', $websiteType->codigo); $this->assertSame($siteLogo->id, $websiteType->site_logo); + $this->assertSame($footerLogo->id, $websiteType->footer_logo); $this->assertStringStartsWith('website-types/', $siteLogo->path); + $this->assertStringStartsWith('website-types/', $footerLogo->path); Storage::disk('s3')->assertExists($siteLogo->path); + Storage::disk('s3')->assertExists($footerLogo->path); $this->assertDatabaseHas('website_type', [ 'codigo' => 'marketplace', 'dominio' => 'marketplace.test', @@ -51,6 +56,7 @@ class WebsiteTypeServiceTest extends TestCase 'border_color' => '#dddddd', 'login_header_footer_color' => '#333333', 'site_logo' => $siteLogo->id, + 'footer_logo' => $footerLogo->id, ]); } }