From b9ad2e589fb05af8b76f76598cafb478efbff5d3 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Wed, 29 Jul 2026 14:54:57 -0300 Subject: [PATCH] refactor(tenant): remove legacy presentation configuration and related carousel images --- .../Controllers/BootstrapTenantController.php | 1 - .../Tenant/Controllers/TenantController.php | 8 +- app/Domains/Tenant/Models/Tenant.php | 28 -- .../Tenant/Requests/StoreTenantRequest.php | 13 - .../Tenant/Requests/UpdateTenantRequest.php | 13 - .../Tenant/Resources/TenantResource.php | 14 - app/Domains/Tenant/Services/TenantService.php | 90 ------- ...move_legacy_tenant_presentation_config.php | 44 ++++ database/seeders/TenantSeeder.php | 241 +++++------------- lang/en/api.php | 3 - lang/es/api.php | 3 - tests/Feature/Seeders/TenantSeederTest.php | 27 -- .../Tenant/TenantMainCarouselImagesTest.php | 191 -------------- .../TenantPresentationConfigRemovalTest.php | 20 ++ 14 files changed, 136 insertions(+), 560 deletions(-) create mode 100644 database/migrations/2026_07_29_000300_remove_legacy_tenant_presentation_config.php delete mode 100644 tests/Feature/Tenant/TenantMainCarouselImagesTest.php create mode 100644 tests/Feature/Tenant/TenantPresentationConfigRemovalTest.php diff --git a/app/Domains/Tenant/Controllers/BootstrapTenantController.php b/app/Domains/Tenant/Controllers/BootstrapTenantController.php index ed1d97e..1767580 100644 --- a/app/Domains/Tenant/Controllers/BootstrapTenantController.php +++ b/app/Domains/Tenant/Controllers/BootstrapTenantController.php @@ -19,7 +19,6 @@ class BootstrapTenantController extends Controller ->with([ 'headerLogo', 'footerLogo', - 'mainCarouselImages', 'menues', 'socialMedia', 'categories' => fn ($query) => $query->orderBy('nombre'), diff --git a/app/Domains/Tenant/Controllers/TenantController.php b/app/Domains/Tenant/Controllers/TenantController.php index 475ca56..6fea5af 100644 --- a/app/Domains/Tenant/Controllers/TenantController.php +++ b/app/Domains/Tenant/Controllers/TenantController.php @@ -19,7 +19,7 @@ class TenantController extends Controller { return TenantResource::collection( Tenant::query() - ->with(['headerLogo', 'footerLogo', 'mainCarouselImages', 'socialMedia']) + ->with(['headerLogo', 'footerLogo', 'socialMedia']) ->latest() ->paginateFromRequest() )->response(); @@ -30,14 +30,14 @@ class TenantController extends Controller $tenant = $this->tenantService->create($request->validated()); return TenantResource::make( - $tenant->loadMissing(['headerLogo', 'footerLogo', 'mainCarouselImages', 'socialMedia']) + $tenant->loadMissing(['headerLogo', 'footerLogo', 'socialMedia']) )->response()->setStatusCode(201); } public function show(Tenant $tenant): TenantResource { return TenantResource::make( - $tenant->loadMissing(['headerLogo', 'footerLogo', 'mainCarouselImages', 'socialMedia']) + $tenant->loadMissing(['headerLogo', 'footerLogo', 'socialMedia']) ); } @@ -46,7 +46,7 @@ class TenantController extends Controller $tenant = $this->tenantService->update($tenant, $request->validated()); return TenantResource::make( - $tenant->loadMissing(['headerLogo', 'footerLogo', 'mainCarouselImages', 'socialMedia']) + $tenant->loadMissing(['headerLogo', 'footerLogo', 'socialMedia']) ); } diff --git a/app/Domains/Tenant/Models/Tenant.php b/app/Domains/Tenant/Models/Tenant.php index 7d6ebb4..555c740 100644 --- a/app/Domains/Tenant/Models/Tenant.php +++ b/app/Domains/Tenant/Models/Tenant.php @@ -28,8 +28,6 @@ use Illuminate\Database\Eloquent\Relations\HasMany; 'footer_bg_color', 'header_logo_id', 'footer_logo_id', - 'hero_config', - 'event_config', 'website_type_code', 'search_product_layout', 'search_group_layout', @@ -58,8 +56,6 @@ class Tenant extends Model protected function casts(): array { return [ - 'hero_config' => 'array', - 'event_config' => 'array', 'search_product_layout' => ProductLayout::class, 'search_group_layout' => GroupLayout::class, 'search_items_per_page' => 'integer', @@ -82,14 +78,6 @@ class Tenant extends Model return $this->belongsTo(Attachment::class, 'footer_logo_id'); } - /** - * @return BelongsTo - */ - public function heroBgImage(): BelongsTo - { - return $this->belongsTo(Attachment::class, 'hero_bg_image_id'); - } - /** * @return BelongsTo */ @@ -98,22 +86,6 @@ class Tenant extends Model return $this->belongsTo(WebsiteType::class, 'website_type_code', 'codigo'); } - /** - * @return BelongsToMany - */ - public function mainCarouselImages(): BelongsToMany - { - return $this->belongsToMany( - Attachment::class, - 'tenant_main_carousel_images', - 'tenant_id', - 'attachment_id' - ) - ->withPivot('orden') - ->withTimestamps() - ->orderByPivot('orden'); - } - public function catalogItems(): HasMany { return $this->hasMany(CatalogItem::class, 'tenant_code', 'codigo'); diff --git a/app/Domains/Tenant/Requests/StoreTenantRequest.php b/app/Domains/Tenant/Requests/StoreTenantRequest.php index 115f889..f808d88 100644 --- a/app/Domains/Tenant/Requests/StoreTenantRequest.php +++ b/app/Domains/Tenant/Requests/StoreTenantRequest.php @@ -62,9 +62,6 @@ 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], - 'main_carousel_images' => ['sometimes', 'array'], - 'main_carousel_images.*' => ['required', 'distinct', new ImageOrBase64Rule], 'social_media' => ['sometimes', 'array'], 'social_media.*.code' => [ 'required', @@ -74,16 +71,6 @@ class StoreTenantRequest extends FormRequest ], 'social_media.*.url' => ['required', 'url', 'max:2048'], 'social_media.*.orden' => ['sometimes', 'integer', 'min:0', 'distinct'], - 'hero_config' => ['nullable', 'array'], - 'hero_config.title_html' => ['nullable', 'string'], - 'hero_config.description_html' => ['nullable', 'string'], - 'hero_config.button_text' => ['nullable', 'string'], - 'hero_config.button_href' => ['nullable', 'string'], - 'event_config' => ['nullable', 'array'], - 'event_config.title' => ['nullable', 'string'], - 'event_config.location' => ['nullable', 'string'], - 'event_config.dates' => ['nullable', 'array'], - 'event_config.dates.*' => ['required', 'string'], 'search_product_layout' => ['sometimes', Rule::enum(ProductLayout::class)], 'search_group_layout' => ['sometimes', Rule::enum(GroupLayout::class)], 'search_items_per_page' => ['sometimes', 'integer', 'min:4', 'max:48'], diff --git a/app/Domains/Tenant/Requests/UpdateTenantRequest.php b/app/Domains/Tenant/Requests/UpdateTenantRequest.php index bb6f2d9..c860586 100644 --- a/app/Domains/Tenant/Requests/UpdateTenantRequest.php +++ b/app/Domains/Tenant/Requests/UpdateTenantRequest.php @@ -73,9 +73,6 @@ 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], - 'main_carousel_images' => ['sometimes', 'array'], - 'main_carousel_images.*' => ['required', 'distinct', new ImageOrBase64Rule], 'social_media' => ['sometimes', 'array'], 'social_media.*.code' => [ 'required', @@ -85,16 +82,6 @@ class UpdateTenantRequest extends FormRequest ], 'social_media.*.url' => ['required', 'url', 'max:2048'], 'social_media.*.orden' => ['sometimes', 'integer', 'min:0', 'distinct'], - 'hero_config' => ['nullable', 'array'], - 'hero_config.title_html' => ['nullable', 'string'], - 'hero_config.description_html' => ['nullable', 'string'], - 'hero_config.button_text' => ['nullable', 'string'], - 'hero_config.button_href' => ['nullable', 'string'], - 'event_config' => ['nullable', 'array'], - 'event_config.title' => ['nullable', 'string'], - 'event_config.location' => ['nullable', 'string'], - 'event_config.dates' => ['nullable', 'array'], - 'event_config.dates.*' => ['required', 'string'], 'search_product_layout' => ['sometimes', Rule::enum(ProductLayout::class)], 'search_group_layout' => ['sometimes', Rule::enum(GroupLayout::class)], 'search_items_per_page' => ['sometimes', 'integer', 'min:4', 'max:48'], diff --git a/app/Domains/Tenant/Resources/TenantResource.php b/app/Domains/Tenant/Resources/TenantResource.php index 989d169..1f6e142 100644 --- a/app/Domains/Tenant/Resources/TenantResource.php +++ b/app/Domains/Tenant/Resources/TenantResource.php @@ -19,12 +19,6 @@ class TenantResource extends JsonResource */ public function toArray(Request $request): array { - $heroConfig = $this->hero_config; - if (is_array($heroConfig)) { - $heroConfig['background_image'] = $this->heroBgImage?->getTemporaryUrl(1440); - unset($heroConfig['background_image_id']); - } - return [ 'id' => $this->id, 'codigo' => $this->codigo, @@ -39,17 +33,9 @@ class TenantResource extends JsonResource // 1 day 'header_logo' => $this->headerLogo?->getTemporaryUrl(1440), 'footer_logo' => $this->footerLogo?->getTemporaryUrl(1440), - 'hero_config' => $heroConfig, - 'event_config' => $this->event_config, 'search_product_layout' => $this->search_product_layout->value, 'search_group_layout' => $this->search_group_layout->value, 'search_items_per_page' => $this->search_items_per_page, - 'main_carousel_images' => $this->whenLoaded( - 'mainCarouselImages', - fn () => $this->mainCarouselImages - ->map(fn ($attachment) => $attachment->getTemporaryUrl(1440)) - ->values() - ), 'social_media' => $this->whenLoaded( 'socialMedia', fn () => $this->socialMedia diff --git a/app/Domains/Tenant/Services/TenantService.php b/app/Domains/Tenant/Services/TenantService.php index 1ce024d..36e74ce 100644 --- a/app/Domains/Tenant/Services/TenantService.php +++ b/app/Domains/Tenant/Services/TenantService.php @@ -2,13 +2,11 @@ namespace App\Domains\Tenant\Services; -use App\Domains\Attachable\Enums\AttachmentType; use App\Domains\Attachable\Models\Attachment; use App\Domains\Attachable\Services\AttachmentService; use App\Domains\Tenant\Models\Tenant; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; -use Illuminate\Validation\ValidationException; class TenantService { @@ -24,15 +22,11 @@ class TenantService return DB::transaction(function () use ($data): Tenant { $headerLogo = $data['header_logo'] ?? null; $footerLogo = $data['footer_logo'] ?? null; - $heroBgImage = $data['hero_bg_image'] ?? null; - $mainCarouselImages = $data['main_carousel_images'] ?? []; $socialMedia = $data['social_media'] ?? []; unset( $data['header_logo'], $data['footer_logo'], - $data['hero_bg_image'], - $data['main_carousel_images'], $data['social_media'] ); @@ -61,21 +55,8 @@ class TenantService $data['header_logo_id'] = $headerAttachmentId; $data['footer_logo_id'] = $footerAttachmentId; - if ($heroBgImage) { - $attachment = Str::isUuid($heroBgImage) - ? Attachment::query()->where('key', $heroBgImage)->first() - : $this->attachmentService->store($heroBgImage, 'tenants'); - - if ($attachment) { - $heroConfig = $data['hero_config'] ?? []; - $heroConfig['background_image_id'] = $attachment->id; - $data['hero_config'] = $heroConfig; - } - } - /** @var Tenant $tenant */ $tenant = Tenant::query()->create($data); - $this->syncMainCarouselImages($tenant, $mainCarouselImages); $this->syncSocialMedia($tenant, $socialMedia); return $tenant; @@ -92,25 +73,17 @@ class TenantService return DB::transaction(function () use ($tenant, $data): Tenant { $hasHeaderLogoKey = array_key_exists('header_logo', $data); $hasFooterLogoKey = array_key_exists('footer_logo', $data); - $hasHeroBgImageKey = array_key_exists('hero_bg_image', $data); - $hasMainCarouselImagesKey = array_key_exists('main_carousel_images', $data); $hasSocialMediaKey = array_key_exists('social_media', $data); $headerLogo = $data['header_logo'] ?? null; $footerLogo = $data['footer_logo'] ?? null; - $heroBgImage = $data['hero_bg_image'] ?? null; - $mainCarouselImages = $data['main_carousel_images'] ?? []; $socialMedia = $data['social_media'] ?? []; unset( $data['header_logo'], $data['footer_logo'], - $data['hero_bg_image'], - $data['main_carousel_images'], $data['social_media'] ); - $oldHeroBgId = $tenant->hero_bg_image_id; - $tenant->fill($data); if ($hasHeaderLogoKey) { @@ -145,34 +118,8 @@ class TenantService } } - $currentHeroConfig = $tenant->hero_config ?? []; - if ($hasHeroBgImageKey) { - if ($heroBgImage) { - $attachment = Str::isUuid($heroBgImage) - ? Attachment::query()->where('key', $heroBgImage)->first() - : $this->attachmentService->store($heroBgImage, 'tenants'); - - if ($attachment) { - $currentHeroConfig['background_image_id'] = $attachment->id; - } else { - unset($currentHeroConfig['background_image_id']); - } - } else { - unset($currentHeroConfig['background_image_id']); - } - } else { - if ($oldHeroBgId) { - $currentHeroConfig['background_image_id'] = $oldHeroBgId; - } - } - $tenant->hero_config = empty($currentHeroConfig) ? null : $currentHeroConfig; - $tenant->save(); - if ($hasMainCarouselImagesKey) { - $this->syncMainCarouselImages($tenant, $mainCarouselImages); - } - if ($hasSocialMediaKey) { $this->syncSocialMedia($tenant, $socialMedia); } @@ -181,21 +128,6 @@ class TenantService }); } - /** - * @param array $images - */ - private function syncMainCarouselImages(Tenant $tenant, array $images): void - { - $attachments = []; - - foreach (array_values($images) as $order => $image) { - $attachment = $this->resolveMainCarouselImage($image, $order); - $attachments[$attachment->id] = ['orden' => $order]; - } - - $tenant->mainCarouselImages()->sync($attachments); - } - /** * @param array $socialMedia */ @@ -213,26 +145,4 @@ class TenantService $tenant->socialMedia()->sync($associations); $tenant->unsetRelation('socialMedia'); } - - private function resolveMainCarouselImage(mixed $image, int $order): Attachment - { - if (is_string($image) && Str::isUuid($image)) { - $attachment = Attachment::query() - ->where('key', $image) - ->where('type', AttachmentType::Image->value) - ->first(); - - if ($attachment === null) { - throw ValidationException::withMessages([ - "main_carousel_images.{$order}" => [ - __('api.tenant.invalid_carousel_image'), - ], - ]); - } - - return $attachment; - } - - return $this->attachmentService->store($image, 'tenants/main-carousel'); - } } diff --git a/database/migrations/2026_07_29_000300_remove_legacy_tenant_presentation_config.php b/database/migrations/2026_07_29_000300_remove_legacy_tenant_presentation_config.php new file mode 100644 index 0000000..30c3eb9 --- /dev/null +++ b/database/migrations/2026_07_29_000300_remove_legacy_tenant_presentation_config.php @@ -0,0 +1,44 @@ +dropColumn('hero_bg_image_id'); + $table->dropColumn(['hero_config', 'event_config']); + }); + } + + public function down(): void + { + Schema::table('tenants', function (Blueprint $table): void { + $table->json('hero_config')->nullable(); + $table->json('event_config')->nullable(); + $table->unsignedBigInteger('hero_bg_image_id') + ->virtualAs('hero_config->>"$.background_image_id"') + ->nullable(); + }); + + Schema::create('tenant_main_carousel_images', function (Blueprint $table): void { + $table->id(); + $table->foreignId('tenant_id') + ->constrained('tenants') + ->cascadeOnDelete(); + $table->foreignId('attachment_id') + ->constrained('attachments') + ->cascadeOnDelete(); + $table->unsignedInteger('orden')->default(0); + $table->timestamps(); + + $table->unique(['tenant_id', 'attachment_id']); + $table->index(['tenant_id', 'orden']); + }); + } +}; diff --git a/database/seeders/TenantSeeder.php b/database/seeders/TenantSeeder.php index c10f538..8db49a2 100644 --- a/database/seeders/TenantSeeder.php +++ b/database/seeders/TenantSeeder.php @@ -7,18 +7,11 @@ use App\Domains\Tenant\Models\Tenant; use App\Domains\Tenant\Services\TenantService; use Illuminate\Database\Seeder; use Illuminate\Http\UploadedFile; +use RuntimeException; +use Throwable; class TenantSeeder extends Seeder { - private const SONDER_MAIN_CAROUSEL_IMAGES = [ - '01-urban-team.png', - '02-running-shoes.png', - '03-streetwear.png', - '04-football-training.png', - '05-activewear-essentials.png', - '06-city-runners.png', - ]; - private const SOCIAL_MEDIA = [ [ 'code' => 'instagram', @@ -44,88 +37,13 @@ class TenantSeeder extends Seeder public function __construct(protected TenantService $tenantService) {} - /** - * Run the database seeds. - */ public function run(): void { - // Check if tenant 'sonder' already exists and delete it to prevent duplicates - $existing = Tenant::query()->where('codigo', 'sonder')->first(); - if ($existing) { - $attachmentService = app(AttachmentService::class); - if ($existing->headerLogo) { - try { - $attachmentService->delete($existing->headerLogo); - } catch (\Throwable $e) { - // Ignore exception on cleanup - } - } - if ($existing->footerLogo && $existing->footer_logo_id !== $existing->header_logo_id) { - try { - $attachmentService->delete($existing->footerLogo); - } catch (\Throwable $e) { - // Ignore exception on cleanup - } - } - foreach ($existing->mainCarouselImages as $mainCarouselImage) { - try { - $attachmentService->delete($mainCarouselImage); - } catch (\Throwable $e) { - // Ignore exception on cleanup - } - } - $existing->delete(); - } + $this->deleteTenant('sonder'); - // Check if domain 'localhost' is already in use by another tenant and delete it - $existingDomain = Tenant::query()->where('dominio', 'localhost')->first(); - if ($existingDomain) { - $existingDomain->delete(); - } - - $headerImagePath = public_path('images/sonder_header.png'); - $footerImagePath = public_path('images/sonder_footer.png'); - - if (! file_exists($headerImagePath)) { - throw new \RuntimeException("Image not found at path: {$headerImagePath}"); - } - - if (! file_exists($footerImagePath)) { - throw new \RuntimeException("Image not found at path: {$footerImagePath}"); - } - - $headerLogo = new UploadedFile( - $headerImagePath, - 'sonder_header.png', - 'image/png', - null, - true - ); - - $footerLogo = new UploadedFile( - $footerImagePath, - 'sonder_footer.png', - 'image/png', - null, - true - ); - - $mainCarouselImages = []; - foreach (self::SONDER_MAIN_CAROUSEL_IMAGES as $filename) { - $imagePath = public_path("images/sonder-main-carousel/{$filename}"); - - if (! file_exists($imagePath)) { - throw new \RuntimeException("Image not found at path: {$imagePath}"); - } - - $mainCarouselImages[] = new UploadedFile( - $imagePath, - $filename, - 'image/png', - null, - true - ); - } + Tenant::query() + ->where('dominio', 'localhost') + ->delete(); $this->tenantService->create([ 'codigo' => 'sonder', @@ -137,82 +55,17 @@ class TenantSeeder extends Seeder 'success_color' => '#198754', 'header_bg_color' => '#ffffff', 'footer_bg_color' => '#313131', - 'header_logo' => $headerLogo, - 'footer_logo' => $footerLogo, - 'main_carousel_images' => $mainCarouselImages, + 'header_logo' => $this->uploadedImage('images/sonder_header.png', 'sonder_header.png'), + 'footer_logo' => $this->uploadedImage('images/sonder_footer.png', 'sonder_footer.png'), 'social_media' => self::SOCIAL_MEDIA, ]); - // Check if tenant 'fiesta_futbol_infantil' already exists - $existingFiesta = Tenant::query()->where('codigo', 'fiesta_futbol_infantil')->first(); - if ($existingFiesta) { - $attachmentService = app(AttachmentService::class); - if ($existingFiesta->headerLogo) { - try { - $attachmentService->delete($existingFiesta->headerLogo); - } catch (\Throwable $e) { - } - } - if ($existingFiesta->footerLogo && $existingFiesta->footer_logo_id !== $existingFiesta->header_logo_id) { - try { - $attachmentService->delete($existingFiesta->footerLogo); - } catch (\Throwable $e) { - } - } - if ($existingFiesta->heroBgImage) { - try { - $attachmentService->delete($existingFiesta->heroBgImage); - } catch (\Throwable $e) { - } - } - $existingFiesta->delete(); - } + $this->deleteTenant('fiesta_futbol_infantil'); $fiestaDomain = 'fiesta-futbol-infantil.localhost'; - $existingFiestaDomain = Tenant::query()->where('dominio', $fiestaDomain)->first(); - if ($existingFiestaDomain) { - $existingFiestaDomain->delete(); - } - - $fiestaHeaderImagePath = public_path('images/futbol_infantil_header.png'); - $fiestaFooterImagePath = public_path('images/futbol_infantil_footer.png'); - $fiestaHeroBgImagePath = public_path('images/futbol_infantil_hero.jpg'); - - if (! file_exists($fiestaHeaderImagePath)) { - throw new \RuntimeException("Image not found at path: {$fiestaHeaderImagePath}"); - } - - if (! file_exists($fiestaFooterImagePath)) { - throw new \RuntimeException("Image not found at path: {$fiestaFooterImagePath}"); - } - - if (! file_exists($fiestaHeroBgImagePath)) { - throw new \RuntimeException("Image not found at path: {$fiestaHeroBgImagePath}"); - } - - $fiestaHeaderLogo = new UploadedFile( - $fiestaHeaderImagePath, - 'futbol_infantil_header.png', - 'image/png', - null, - true - ); - - $fiestaFooterLogo = new UploadedFile( - $fiestaFooterImagePath, - 'futbol_infantil_footer.png', - 'image/png', - null, - true - ); - - $fiestaHeroBgImage = new UploadedFile( - $fiestaHeroBgImagePath, - 'futbol_infantil_hero.jpg', - 'image/jpeg', - null, - true - ); + Tenant::query() + ->where('dominio', $fiestaDomain) + ->delete(); $this->tenantService->create([ 'codigo' => 'fiesta_futbol_infantil', @@ -224,21 +77,63 @@ class TenantSeeder extends Seeder 'success_color' => '#198754', 'header_bg_color' => '#ffffff', 'footer_bg_color' => '#015327', - 'header_logo' => $fiestaHeaderLogo, - 'footer_logo' => $fiestaFooterLogo, - 'hero_bg_image' => $fiestaHeroBgImage, - 'hero_config' => [ - 'title_html' => 'ASEGURÁ TU LUGAR', - 'description_html' => 'Comprá tu entrada oficial en segundos de forma 100% segura. Preparate para vivir la experiencia completa.', - 'button_text' => 'Quiero mi entrada', - 'button_href' => null, - ], - 'event_config' => [ - 'title' => 'FIESTA NACIONAL DEL FÚTBOL INFANTIL', - 'location' => 'Sunchales, Santa Fe', - 'dates' => ['2026-10-09', '2026-10-10', '2026-10-11', '2026-10-12'], - ], + 'header_logo' => $this->uploadedImage( + 'images/futbol_infantil_header.png', + 'futbol_infantil_header.png', + ), + 'footer_logo' => $this->uploadedImage( + 'images/futbol_infantil_footer.png', + 'futbol_infantil_footer.png', + ), 'social_media' => self::SOCIAL_MEDIA, ]); } + + private function deleteTenant(string $codigo): void + { + $tenant = Tenant::query() + ->where('codigo', $codigo) + ->first(); + + if ($tenant === null) { + return; + } + + $attachmentService = app(AttachmentService::class); + + if ($tenant->headerLogo) { + try { + $attachmentService->delete($tenant->headerLogo); + } catch (Throwable) { + // Ignore cleanup errors while recreating demo data. + } + } + + if ($tenant->footerLogo && $tenant->footer_logo_id !== $tenant->header_logo_id) { + try { + $attachmentService->delete($tenant->footerLogo); + } catch (Throwable) { + // Ignore cleanup errors while recreating demo data. + } + } + + $tenant->delete(); + } + + private function uploadedImage(string $relativePath, string $filename): UploadedFile + { + $path = public_path($relativePath); + + if (! file_exists($path)) { + throw new RuntimeException("Image not found at path: {$path}"); + } + + return new UploadedFile( + $path, + $filename, + 'image/png', + null, + true, + ); + } } diff --git a/lang/en/api.php b/lang/en/api.php index 4344376..9740577 100644 --- a/lang/en/api.php +++ b/lang/en/api.php @@ -94,9 +94,6 @@ return [ 'schema_required' => 'The schema is required for static menus.', 'not_found' => 'The specified menu does not exist.', ], - 'tenant' => [ - 'invalid_carousel_image' => 'The specified image does not exist or is not an image attachment.', - ], 'errors' => [ 'forbidden' => 'You do not have permission to perform this action.', 'not_found' => 'The requested resource was not found.', diff --git a/lang/es/api.php b/lang/es/api.php index 33d3a3e..50e2416 100644 --- a/lang/es/api.php +++ b/lang/es/api.php @@ -94,9 +94,6 @@ return [ 'schema_required' => 'El schema es obligatorio para los menús estáticos.', 'not_found' => 'El menú indicado no existe.', ], - 'tenant' => [ - 'invalid_carousel_image' => 'La imagen indicada no existe o no es un attachment de tipo imagen.', - ], 'errors' => [ 'forbidden' => 'No tienes permiso para realizar esta acción.', 'not_found' => 'El recurso solicitado no fue encontrado.', diff --git a/tests/Feature/Seeders/TenantSeederTest.php b/tests/Feature/Seeders/TenantSeederTest.php index b4dc03d..f6b110c 100644 --- a/tests/Feature/Seeders/TenantSeederTest.php +++ b/tests/Feature/Seeders/TenantSeederTest.php @@ -13,33 +13,6 @@ class TenantSeederTest extends TestCase { use RefreshDatabase; - public function test_it_loads_the_sonder_main_carousel_images_in_order(): void - { - Storage::fake('s3'); - - $this->seed([ - SocialMediaSeeder::class, - TenantSeeder::class, - ]); - - $tenant = Tenant::query()->where('codigo', 'sonder')->firstOrFail(); - $images = $tenant->mainCarouselImages()->get(); - - $this->assertSame([ - '01-urban-team.png', - '02-running-shoes.png', - '03-streetwear.png', - '04-football-training.png', - '05-activewear-essentials.png', - '06-city-runners.png', - ], $images->pluck('filename')->all()); - $this->assertSame([0, 1, 2, 3, 4, 5], $images->pluck('pivot.orden')->all()); - - foreach ($images as $image) { - Storage::disk('s3')->assertExists($image->path); - } - } - public function test_it_assigns_social_media_to_both_tenants_in_order(): void { Storage::fake('s3'); diff --git a/tests/Feature/Tenant/TenantMainCarouselImagesTest.php b/tests/Feature/Tenant/TenantMainCarouselImagesTest.php deleted file mode 100644 index 6823cc1..0000000 --- a/tests/Feature/Tenant/TenantMainCarouselImagesTest.php +++ /dev/null @@ -1,191 +0,0 @@ -createTenant(); - $firstAttachment = $this->createAttachment('first.png'); - $secondAttachment = $this->createAttachment('second.png'); - - $tenant->mainCarouselImages()->attach([ - $secondAttachment->id => ['orden' => 2], - $firstAttachment->id => ['orden' => 1], - ]); - - $attachments = $tenant->mainCarouselImages()->get(); - - $this->assertCount(2, $attachments); - $this->assertTrue($attachments[0]->is($firstAttachment)); - $this->assertTrue($attachments[1]->is($secondAttachment)); - $this->assertSame([1, 2], $attachments->pluck('pivot.orden')->all()); - } - - public function test_bootstrap_returns_all_main_carousel_image_urls_in_order(): void - { - $tenant = $this->createTenant(); - $firstAttachment = $this->createAttachment('first.png'); - $secondAttachment = $this->createAttachment('second.png'); - - $tenant->mainCarouselImages()->attach([ - $secondAttachment->id => ['orden' => 20], - $firstAttachment->id => ['orden' => 10], - ]); - - $response = $this->getJson('/api/tenants/bootstrap/acme.com'); - - $response - ->assertOk() - ->assertJsonCount(2, 'data.main_carousel_images'); - - $urls = $response->json('data.main_carousel_images'); - - $this->assertStringContainsString($firstAttachment->key, $urls[0]); - $this->assertStringContainsString($secondAttachment->key, $urls[1]); - } - - public function test_show_returns_an_empty_main_carousel_images_array_when_the_tenant_has_no_images(): void - { - $tenant = $this->createTenant(); - - $this->getJson("/api/tenants/{$tenant->codigo}") - ->assertOk() - ->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'); - $footerLogo = $this->createAttachment('footer.png'); - - return Tenant::query()->create([ - 'codigo' => 'acme', - 'nombre' => 'Acme', - 'dominio' => 'acme.com', - 'primary_color' => '#111111', - 'secondary_color' => '#222222', - 'danger_color' => '#333333', - 'success_color' => '#444444', - 'header_bg_color' => '#ffffff', - 'footer_bg_color' => '#ffffff', - 'header_logo_id' => $headerLogo->id, - 'footer_logo_id' => $footerLogo->id, - ]); - } - - private function createAttachment(string $filename): Attachment - { - $key = (string) Str::uuid(); - - return Attachment::query()->create([ - 'key' => $key, - 'path' => "tenants/main-carousel/{$key}.png", - 'filename' => $filename, - 'type' => AttachmentType::Image, - 'mime_type' => 'image/png', - 'extension' => 'png', - 'size' => 100, - ]); - } -} diff --git a/tests/Feature/Tenant/TenantPresentationConfigRemovalTest.php b/tests/Feature/Tenant/TenantPresentationConfigRemovalTest.php new file mode 100644 index 0000000..60f1a57 --- /dev/null +++ b/tests/Feature/Tenant/TenantPresentationConfigRemovalTest.php @@ -0,0 +1,20 @@ +assertFalse(Schema::hasTable('tenant_main_carousel_images')); + $this->assertFalse(Schema::hasColumn('tenants', 'hero_bg_image_id')); + $this->assertFalse(Schema::hasColumn('tenants', 'hero_config')); + $this->assertFalse(Schema::hasColumn('tenants', 'event_config')); + } +}