diff --git a/app/Domains/Attachable/Services/AttachmentService.php b/app/Domains/Attachable/Services/AttachmentService.php index b3df0a4..6c8b0c6 100644 --- a/app/Domains/Attachable/Services/AttachmentService.php +++ b/app/Domains/Attachable/Services/AttachmentService.php @@ -108,13 +108,23 @@ class AttachmentService public function delete(Attachment $attachment): void { - $deleted = Storage::disk('s3')->delete($attachment->path); + $croppedAttachment = $attachment->croppedAttachment; + $paths = array_values(array_filter([ + $attachment->path, + $croppedAttachment?->path, + ])); + $deleted = Storage::disk('s3')->delete( + count($paths) === 1 ? $paths[0] : $paths + ); if (! $deleted) { - throw new AttachmentStorageException('No se pudo eliminar el archivo del disco s3.'); + throw new AttachmentStorageException('No se pudieron eliminar los archivos del disco s3.'); } - $attachment->delete(); + DB::transaction(function () use ($attachment, $croppedAttachment): void { + $attachment->delete(); + $croppedAttachment?->delete(); + }); } public function copy(Attachment $source, string $path): Attachment diff --git a/app/Domains/Attachable/documentacion/README.md b/app/Domains/Attachable/documentacion/README.md index cc6d629..db110c3 100644 --- a/app/Domains/Attachable/documentacion/README.md +++ b/app/Domains/Attachable/documentacion/README.md @@ -27,5 +27,6 @@ No expone rutas HTTP propias. Lo consumen otros dominios, especialmente `Catalog - El directorio no puede quedar vacío después de normalizarlo. - Los porcentajes de inicio del crop deben estar en el rango `[0, 100)`; el recorte se extiende desde ese punto hasta los bordes derecho e inferior. - El attachment original guarda los porcentajes y la relación `croppedAttachment` con la versión procesada. +- Al eliminar el original mediante el servicio también se elimina su versión recortada. - La eliminación se considera fallida si S3 no confirma el borrado. - Las URL generadas son temporales; el vencimiento predeterminado es de 10 minutos. diff --git a/composer.json b/composer.json index b85e86f..6b11461 100644 --- a/composer.json +++ b/composer.json @@ -6,6 +6,7 @@ "keywords": ["laravel", "framework"], "license": "MIT", "require": { + "ext-gd": "*", "php": "^8.3", "barryvdh/laravel-dompdf": "^3.1", "endroid/qr-code": "^6.1", diff --git a/composer.lock b/composer.lock index 49bee22..f9e8e33 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "aa7e98d017dd610946c62579b20c501f", + "content-hash": "ce185c60c617846be30ae694f0cf6e9c", "packages": [ { "name": "aws/aws-crt-php", @@ -9838,6 +9838,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { + "ext-gd": "*", "php": "^8.3" }, "platform-dev": {}, diff --git a/database/migrations/2026_08_18_000000_add_crop_data_to_attachments_table.php b/database/migrations/2026_08_18_000000_add_crop_data_to_attachments_table.php index e65105e..c21f80c 100644 --- a/database/migrations/2026_08_18_000000_add_crop_data_to_attachments_table.php +++ b/database/migrations/2026_08_18_000000_add_crop_data_to_attachments_table.php @@ -13,6 +13,7 @@ return new class extends Migration $table->decimal('crop_vertical_start_percent', 7, 4)->nullable()->after('crop_horizontal_start_percent'); $table->foreignId('cropped_attachment_id') ->nullable() + ->unique() ->after('crop_vertical_start_percent') ->constrained('attachments') ->nullOnDelete(); diff --git a/tests/Feature/Attachable/AttachmentTest.php b/tests/Feature/Attachable/AttachmentTest.php index 3bf69f8..ff24c82 100644 --- a/tests/Feature/Attachable/AttachmentTest.php +++ b/tests/Feature/Attachable/AttachmentTest.php @@ -140,6 +140,25 @@ class AttachmentTest extends TestCase } } + public function test_it_deletes_the_crop_together_with_its_original(): void + { + Storage::fake('s3'); + + $original = app(AttachmentService::class)->storeCroppedImage( + UploadedFile::fake()->image('product.png'), + 'attachments/acme', + 10, + 10, + ); + $cropped = $original->croppedAttachment; + + app(AttachmentService::class)->delete($original); + + $this->assertDatabaseCount('attachments', 0); + Storage::disk('s3')->assertMissing($original->path); + Storage::disk('s3')->assertMissing($cropped->path); + } + public function test_it_deletes_from_s3_before_removing_the_database_record(): void { Storage::fake('s3');