feat(website-type): add footer logo support in WebsiteType model, controller, and resource; update seeder and tests

This commit is contained in:
2026-07-31 14:16:58 -03:00
parent f603a82b5e
commit dd76d5d951
11 changed files with 111 additions and 28 deletions

View File

@@ -17,7 +17,7 @@ class BootstrapAdminAppController extends Controller
return BootstrapAdminAppResource::make( return BootstrapAdminAppResource::make(
WebsiteType::query() WebsiteType::query()
->with('siteLogo') ->with(['siteLogo', 'footerLogo'])
->where('dominio', $domain) ->where('dominio', $domain)
->firstOrFail() ->firstOrFail()
); );

View File

@@ -25,6 +25,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
'border_color', 'border_color',
'login_header_footer_color', 'login_header_footer_color',
'site_logo', 'site_logo',
'footer_logo',
])] ])]
class WebsiteType extends Model class WebsiteType extends Model
{ {
@@ -40,6 +41,14 @@ class WebsiteType extends Model
return $this->belongsTo(Attachment::class, 'site_logo'); return $this->belongsTo(Attachment::class, 'site_logo');
} }
/**
* @return BelongsTo<Attachment, $this>
*/
public function footerLogo(): BelongsTo
{
return $this->belongsTo(Attachment::class, 'footer_logo');
}
/** /**
* @return HasMany<WebsiteTypeExtra, $this> * @return HasMany<WebsiteTypeExtra, $this>
*/ */

View File

@@ -33,6 +33,7 @@ class BootstrapAdminAppResource extends JsonResource
'border_color' => $this->border_color, 'border_color' => $this->border_color,
'login_header_footer_color' => $this->login_header_footer_color, 'login_header_footer_color' => $this->login_header_footer_color,
'site_logo' => $this->siteLogo?->getTemporaryUrl(1440), 'site_logo' => $this->siteLogo?->getTemporaryUrl(1440),
'footer_logo' => $this->footerLogo?->getTemporaryUrl(1440),
]; ];
} }
} }

View File

@@ -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<string, mixed> $data * @param array<string, mixed> $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<string, mixed> $attributes * @param array<string, mixed> $attributes
* @param array<string, mixed> $values * @param array<string, mixed> $values
@@ -44,34 +44,41 @@ class WebsiteTypeService
private function save(WebsiteType $websiteType, array $data): WebsiteType private function save(WebsiteType $websiteType, array $data): WebsiteType
{ {
return DB::transaction(function () use ($websiteType, $data): WebsiteType { return DB::transaction(function () use ($websiteType, $data): WebsiteType {
$hasSiteLogo = array_key_exists('site_logo', $data); $previousLogos = [];
$siteLogo = $data['site_logo'] ?? null;
$previousSiteLogo = $websiteType->exists
? $websiteType->siteLogo()->first()
: null;
unset($data['site_logo']); foreach (['site_logo' => 'siteLogo', 'footer_logo' => 'footerLogo'] as $field => $relation) {
if (! array_key_exists($field, $data)) {
if ($hasSiteLogo) { continue;
$attachment = null;
if ($siteLogo) {
$attachment = is_string($siteLogo) && Str::isUuid($siteLogo)
? Attachment::query()->where('key', $siteLogo)->first()
: $this->attachmentService->store($siteLogo, 'website-types');
} }
$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(); $websiteType->fill($data)->save();
if ( foreach ($previousLogos as $field => $previousLogo) {
$hasSiteLogo if (
&& $previousSiteLogo instanceof Attachment $previousLogo instanceof Attachment
&& $previousSiteLogo->id !== $websiteType->site_logo && $previousLogo->id !== $websiteType->{$field}
) { && $previousLogo->id !== $websiteType->site_logo
$this->attachmentService->delete($previousSiteLogo); && $previousLogo->id !== $websiteType->footer_logo
) {
$this->attachmentService->delete($previousLogo);
}
} }
return $websiteType; return $websiteType;

View File

@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('website_type', function (Blueprint $table): void {
$table->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');
});
}
};

View File

@@ -33,6 +33,7 @@ class WebsiteTypeSeeder extends Seeder
'dominio' => 'localhost', 'dominio' => 'localhost',
...self::PRESENTATION, ...self::PRESENTATION,
'site_logo' => $this->onTicketLogo(), 'site_logo' => $this->onTicketLogo(),
'footer_logo' => $this->onTicketFooterLogo(),
], ],
); );
@@ -64,6 +65,7 @@ class WebsiteTypeSeeder extends Seeder
'dominio' => 'onticket.localhost', 'dominio' => 'onticket.localhost',
...self::PRESENTATION, ...self::PRESENTATION,
'site_logo' => $this->onTicketLogo(), 'site_logo' => $this->onTicketLogo(),
'footer_logo' => $this->onTicketFooterLogo(),
], ],
); );
@@ -115,7 +117,7 @@ class WebsiteTypeSeeder extends Seeder
private function onTicketLogo(): UploadedFile 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)) { if (! file_exists($path)) {
throw new RuntimeException("OnTicket logo not found at path: {$path}"); throw new RuntimeException("OnTicket logo not found at path: {$path}");
@@ -129,4 +131,21 @@ class WebsiteTypeSeeder extends Seeder
true, 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,
);
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -21,7 +21,7 @@ class WebsiteTypeSeederTest extends TestCase
$this->seed(WebsiteTypeSeeder::class); $this->seed(WebsiteTypeSeeder::class);
$this->assertSame(2, WebsiteType::query()->count()); $this->assertSame(2, WebsiteType::query()->count());
$this->assertSame(2, Attachment::query()->count()); $this->assertSame(4, Attachment::query()->count());
$expectedPresentation = [ $expectedPresentation = [
'primary_color' => '#FF7006', 'primary_color' => '#FF7006',
@@ -46,6 +46,8 @@ class WebsiteTypeSeederTest extends TestCase
$this->assertSame($expectedPresentation, $shopIt->only(array_keys($expectedPresentation))); $this->assertSame($expectedPresentation, $shopIt->only(array_keys($expectedPresentation)));
$this->assertSame('onticket_logo.png', $shopIt->siteLogo->filename); $this->assertSame('onticket_logo.png', $shopIt->siteLogo->filename);
Storage::disk('s3')->assertExists($shopIt->siteLogo->path); 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(['carousel'], $shopIt->extras->pluck('codigo')->all());
$this->assertSame('Carrusel principal', $shopIt->extras->sole()->nombre); $this->assertSame('Carrusel principal', $shopIt->extras->sole()->nombre);
$this->assertSame([ $this->assertSame([
@@ -71,7 +73,10 @@ class WebsiteTypeSeederTest extends TestCase
$this->assertSame($expectedPresentation, $onTicket->only(array_keys($expectedPresentation))); $this->assertSame($expectedPresentation, $onTicket->only(array_keys($expectedPresentation)));
$this->assertSame('onticket_logo.png', $onTicket->siteLogo->filename); $this->assertSame('onticket_logo.png', $onTicket->siteLogo->filename);
Storage::disk('s3')->assertExists($onTicket->siteLogo->path); 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->site_logo, $onTicket->site_logo);
$this->assertNotSame($shopIt->footer_logo, $onTicket->footer_logo);
$this->assertEqualsCanonicalizing( $this->assertEqualsCanonicalizing(
['heroConfig', 'eventConfig'], ['heroConfig', 'eventConfig'],
$onTicket->extras->pluck('codigo')->all(), $onTicket->extras->pluck('codigo')->all(),

View File

@@ -2,6 +2,7 @@
namespace Tests\Feature\Tenant; namespace Tests\Feature\Tenant;
use App\Domains\Attachable\Models\Attachment;
use App\Domains\Tenant\Models\WebsiteType; use App\Domains\Tenant\Models\WebsiteType;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase; use Tests\TestCase;
@@ -12,6 +13,8 @@ class BootstrapAdminAppControllerTest extends TestCase
public function test_it_publicly_bootstraps_the_admin_app_by_domain(): void public function test_it_publicly_bootstraps_the_admin_app_by_domain(): void
{ {
$footerLogo = Attachment::factory()->create();
WebsiteType::query()->create([ WebsiteType::query()->create([
'codigo' => 'shopit', 'codigo' => 'shopit',
'nombre' => 'ShopIt', 'nombre' => 'ShopIt',
@@ -27,6 +30,7 @@ class BootstrapAdminAppControllerTest extends TestCase
'background_color' => '#f8f8f8', 'background_color' => '#f8f8f8',
'border_color' => '#eaeaea', 'border_color' => '#eaeaea',
'login_header_footer_color' => '#313131', 'login_header_footer_color' => '#313131',
'footer_logo' => $footerLogo->id,
]); ]);
$this->getJson('/api/v1/adminapp/bootstrap/ADMIN.SHOPIT.TEST') $this->getJson('/api/v1/adminapp/bootstrap/ADMIN.SHOPIT.TEST')
@@ -37,7 +41,8 @@ class BootstrapAdminAppControllerTest extends TestCase
->assertJsonPath('data.primary_color', '#112233') ->assertJsonPath('data.primary_color', '#112233')
->assertJsonPath('data.warning_color', '#ffaa00') ->assertJsonPath('data.warning_color', '#ffaa00')
->assertJsonPath('data.login_header_footer_color', '#313131') ->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 public function test_it_returns_not_found_for_an_unknown_domain(): void

View File

@@ -12,7 +12,7 @@ class WebsiteTypeServiceTest extends TestCase
{ {
use RefreshDatabase; 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'); Storage::fake('s3');
@@ -32,14 +32,19 @@ class WebsiteTypeServiceTest extends TestCase
'border_color' => '#dddddd', 'border_color' => '#dddddd',
'login_header_footer_color' => '#333333', 'login_header_footer_color' => '#333333',
'site_logo' => UploadedFile::fake()->image('site-logo.png'), 'site_logo' => UploadedFile::fake()->image('site-logo.png'),
'footer_logo' => UploadedFile::fake()->image('footer-logo.png'),
]); ]);
$siteLogo = $websiteType->siteLogo()->firstOrFail(); $siteLogo = $websiteType->siteLogo()->firstOrFail();
$footerLogo = $websiteType->footerLogo()->firstOrFail();
$this->assertSame('marketplace', $websiteType->codigo); $this->assertSame('marketplace', $websiteType->codigo);
$this->assertSame($siteLogo->id, $websiteType->site_logo); $this->assertSame($siteLogo->id, $websiteType->site_logo);
$this->assertSame($footerLogo->id, $websiteType->footer_logo);
$this->assertStringStartsWith('website-types/', $siteLogo->path); $this->assertStringStartsWith('website-types/', $siteLogo->path);
$this->assertStringStartsWith('website-types/', $footerLogo->path);
Storage::disk('s3')->assertExists($siteLogo->path); Storage::disk('s3')->assertExists($siteLogo->path);
Storage::disk('s3')->assertExists($footerLogo->path);
$this->assertDatabaseHas('website_type', [ $this->assertDatabaseHas('website_type', [
'codigo' => 'marketplace', 'codigo' => 'marketplace',
'dominio' => 'marketplace.test', 'dominio' => 'marketplace.test',
@@ -51,6 +56,7 @@ class WebsiteTypeServiceTest extends TestCase
'border_color' => '#dddddd', 'border_color' => '#dddddd',
'login_header_footer_color' => '#333333', 'login_header_footer_color' => '#333333',
'site_logo' => $siteLogo->id, 'site_logo' => $siteLogo->id,
'footer_logo' => $footerLogo->id,
]); ]);
} }
} }