diff --git a/app/Domains/Tenant/Requests/StoreTenantRequest.php b/app/Domains/Tenant/Requests/StoreTenantRequest.php index ae0655e..e49552e 100644 --- a/app/Domains/Tenant/Requests/StoreTenantRequest.php +++ b/app/Domains/Tenant/Requests/StoreTenantRequest.php @@ -35,8 +35,7 @@ class StoreTenantRequest extends FormRequest */ public function rules(): array { - $logoRule = ['required', new ImageOrBase64Rule()]; - + $logoRule = ['required', new ImageOrBase64Rule]; return [ 'codigo' => ['required', 'string', 'max:255', Rule::unique('tenants', 'codigo')], @@ -61,9 +60,9 @@ class StoreTenantRequest extends FormRequest 'footer_bg_color' => ['required', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'], 'header_logo' => $logoRule, 'footer_logo' => $logoRule, - 'hero_bg_image' => ['nullable', new ImageOrBase64Rule()], + 'hero_bg_image' => ['nullable', new ImageOrBase64Rule], 'main_carousel_images' => ['sometimes', 'array'], - 'main_carousel_images.*' => ['required', 'distinct', new ImageOrBase64Rule()], + 'main_carousel_images.*' => ['required', 'distinct', new ImageOrBase64Rule], 'hero_config' => ['nullable', 'array'], 'hero_config.title_html' => ['nullable', 'string'], 'hero_config.description_html' => ['nullable', 'string'], diff --git a/app/Domains/Tenant/Requests/UpdateTenantRequest.php b/app/Domains/Tenant/Requests/UpdateTenantRequest.php index 1338754..21a5571 100644 --- a/app/Domains/Tenant/Requests/UpdateTenantRequest.php +++ b/app/Domains/Tenant/Requests/UpdateTenantRequest.php @@ -41,8 +41,7 @@ class UpdateTenantRequest extends FormRequest /** @var Tenant|null $tenant */ $tenant = $this->route('tenant'); - $logoRule = ['nullable', new ImageOrBase64Rule()]; - + $logoRule = ['nullable', new ImageOrBase64Rule]; return [ 'codigo' => [ @@ -72,9 +71,9 @@ class UpdateTenantRequest extends FormRequest 'footer_bg_color' => ['nullable', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'], 'header_logo' => $logoRule, 'footer_logo' => $logoRule, - 'hero_bg_image' => ['nullable', new ImageOrBase64Rule()], + 'hero_bg_image' => ['nullable', new ImageOrBase64Rule], 'main_carousel_images' => ['sometimes', 'array'], - 'main_carousel_images.*' => ['required', 'distinct', new ImageOrBase64Rule()], + 'main_carousel_images.*' => ['required', 'distinct', new ImageOrBase64Rule], 'hero_config' => ['nullable', 'array'], 'hero_config.title_html' => ['nullable', 'string'], 'hero_config.description_html' => ['nullable', 'string'], diff --git a/app/Domains/Tenant/Services/TenantService.php b/app/Domains/Tenant/Services/TenantService.php index 8a53699..62e0fe1 100644 --- a/app/Domains/Tenant/Services/TenantService.php +++ b/app/Domains/Tenant/Services/TenantService.php @@ -12,15 +12,12 @@ use Illuminate\Validation\ValidationException; class TenantService { - public function __construct(protected AttachmentService $attachmentService) - { - } + public function __construct(protected AttachmentService $attachmentService) {} /** * Create a new tenant and store its logos. * * @param array $data - * @return Tenant */ public function create(array $data): Tenant { @@ -40,7 +37,7 @@ class TenantService $headerAttachmentId = null; if ($headerLogo) { $attachment = Str::isUuid($headerLogo) - ? \App\Domains\Attachable\Models\Attachment::query()->where('key', $headerLogo)->first() + ? Attachment::query()->where('key', $headerLogo)->first() : $this->attachmentService->store($headerLogo, 'tenants'); if ($attachment) { @@ -51,7 +48,7 @@ class TenantService $footerAttachmentId = null; if ($footerLogo) { $attachment = Str::isUuid($footerLogo) - ? \App\Domains\Attachable\Models\Attachment::query()->where('key', $footerLogo)->first() + ? Attachment::query()->where('key', $footerLogo)->first() : $this->attachmentService->store($footerLogo, 'tenants'); if ($attachment) { @@ -64,7 +61,7 @@ class TenantService if ($heroBgImage) { $attachment = Str::isUuid($heroBgImage) - ? \App\Domains\Attachable\Models\Attachment::query()->where('key', $heroBgImage)->first() + ? Attachment::query()->where('key', $heroBgImage)->first() : $this->attachmentService->store($heroBgImage, 'tenants'); if ($attachment) { @@ -85,9 +82,7 @@ class TenantService /** * Update an existing tenant and store new logos if uploaded. * - * @param Tenant $tenant * @param array $data - * @return Tenant */ public function update(Tenant $tenant, array $data): Tenant { @@ -115,7 +110,7 @@ class TenantService if ($hasHeaderLogoKey) { if ($headerLogo) { $attachment = Str::isUuid($headerLogo) - ? \App\Domains\Attachable\Models\Attachment::query()->where('key', $headerLogo)->first() + ? Attachment::query()->where('key', $headerLogo)->first() : $this->attachmentService->store($headerLogo, 'tenants'); if ($attachment) { @@ -131,7 +126,7 @@ class TenantService if ($hasFooterLogoKey) { if ($footerLogo) { $attachment = Str::isUuid($footerLogo) - ? \App\Domains\Attachable\Models\Attachment::query()->where('key', $footerLogo)->first() + ? Attachment::query()->where('key', $footerLogo)->first() : $this->attachmentService->store($footerLogo, 'tenants'); if ($attachment) { @@ -148,7 +143,7 @@ class TenantService if ($hasHeroBgImageKey) { if ($heroBgImage) { $attachment = Str::isUuid($heroBgImage) - ? \App\Domains\Attachable\Models\Attachment::query()->where('key', $heroBgImage)->first() + ? Attachment::query()->where('key', $heroBgImage)->first() : $this->attachmentService->store($heroBgImage, 'tenants'); if ($attachment) { diff --git a/tests/Feature/Tenant/TenantMainCarouselImagesTest.php b/tests/Feature/Tenant/TenantMainCarouselImagesTest.php index 32a691a..6823cc1 100644 --- a/tests/Feature/Tenant/TenantMainCarouselImagesTest.php +++ b/tests/Feature/Tenant/TenantMainCarouselImagesTest.php @@ -6,6 +6,8 @@ use App\Domains\Attachable\Enums\AttachmentType; use App\Domains\Attachable\Models\Attachment; use App\Domains\Tenant\Models\Tenant; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Http\UploadedFile; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Tests\TestCase; @@ -64,6 +66,94 @@ class TenantMainCarouselImagesTest extends TestCase ->assertJsonPath('data.main_carousel_images', []); } + public function test_store_uploads_and_attaches_main_carousel_images_in_request_order(): void + { + Storage::fake('s3'); + + $headerLogo = $this->createAttachment('header.png'); + $footerLogo = $this->createAttachment('footer.png'); + + $response = $this + ->withHeader('Accept', 'application/json') + ->post('/api/tenants', [ + ...$this->tenantPayload(), + 'header_logo' => $headerLogo->key, + 'footer_logo' => $footerLogo->key, + 'main_carousel_images' => [ + UploadedFile::fake()->image('first-carousel.png', 1200, 300), + UploadedFile::fake()->image('second-carousel.png', 1200, 300), + ], + ]); + + $response + ->assertCreated() + ->assertJsonCount(2, 'data.main_carousel_images'); + + $tenant = Tenant::query()->where('codigo', 'new-acme')->firstOrFail(); + $images = $tenant->mainCarouselImages()->get(); + + $this->assertSame( + ['first-carousel.png', 'second-carousel.png'], + $images->pluck('filename')->all() + ); + $this->assertSame([0, 1], $images->pluck('pivot.orden')->all()); + Storage::disk('s3')->assertExists($images[0]->path); + Storage::disk('s3')->assertExists($images[1]->path); + } + + public function test_update_replaces_and_can_clear_main_carousel_images(): void + { + $tenant = $this->createTenant(); + $oldImage = $this->createAttachment('old.png'); + $firstImage = $this->createAttachment('first.png'); + $secondImage = $this->createAttachment('second.png'); + + $tenant->mainCarouselImages()->attach($oldImage->id, ['orden' => 0]); + + $this->putJson("/api/tenants/{$tenant->codigo}", [ + 'main_carousel_images' => [$secondImage->key, $firstImage->key], + ]) + ->assertOk() + ->assertJsonCount(2, 'data.main_carousel_images'); + + $this->assertSame( + [$secondImage->id, $firstImage->id], + $tenant->mainCarouselImages()->pluck('attachments.id')->all() + ); + $this->assertDatabaseMissing('tenant_main_carousel_images', [ + 'tenant_id' => $tenant->id, + 'attachment_id' => $oldImage->id, + ]); + + $this->putJson("/api/tenants/{$tenant->codigo}", [ + 'main_carousel_images' => [], + ]) + ->assertOk() + ->assertJsonPath('data.main_carousel_images', []); + + $this->assertDatabaseMissing('tenant_main_carousel_images', [ + 'tenant_id' => $tenant->id, + ]); + } + + /** + * @return array + */ + private function tenantPayload(): array + { + return [ + 'codigo' => 'new-acme', + 'nombre' => 'New Acme', + 'dominio' => 'new-acme.com', + 'primary_color' => '#111111', + 'secondary_color' => '#222222', + 'danger_color' => '#333333', + 'success_color' => '#444444', + 'header_bg_color' => '#ffffff', + 'footer_bg_color' => '#ffffff', + ]; + } + private function createTenant(): Tenant { $headerLogo = $this->createAttachment('header.png');