diff --git a/.env.testing b/.env.testing index e3507529..3fef978d 100644 --- a/.env.testing +++ b/.env.testing @@ -1,6 +1,6 @@ APP_NAME=Laravel APP_ENV=local -APP_KEY=base64:uM4NZoG/Wgk0ruC0hs4FkYVEChkviOQy8ekOaVpr/pA= +APP_KEY=base64:GhP7Rdqy8uICwxrAgwO0pf6xvWIaB/DDo6z2Ro0vIFg= APP_DEBUG=true APP_URL=http://localhost APP_DEMO=false diff --git a/database/factories/FileFactory.php b/database/factories/FileFactory.php index 75c2d0a8..ee57c094 100644 --- a/database/factories/FileFactory.php +++ b/database/factories/FileFactory.php @@ -35,9 +35,7 @@ class FileFactory extends Factory 'author' => $this->faker->randomElement( ['user', 'member', 'visitor'] ), - 'created_at' => $this->faker->dateTimeBetween( - $startDate = '-36 months', $endDate = 'now', $timezone = null - ), + 'created_at' => $this->faker->dateTimeBetween('-36 months'), ]; } } diff --git a/resources/js/store/modules/fileFunctions.js b/resources/js/store/modules/fileFunctions.js index 042c00a4..f785446a 100644 --- a/resources/js/store/modules/fileFunctions.js +++ b/resources/js/store/modules/fileFunctions.js @@ -313,14 +313,6 @@ const actions = { if (getters.permission === 'master') { if (data.data.type === 'folder') commit('REMOVE_ITEM_FROM_FAVOURITES', data) } - - // Remove file - commit('REMOVE_ITEM', data.data.id) - - // Remove item from sidebar - if (getters.permission === 'master') { - if (data.data.type === 'folder') commit('REMOVE_ITEM_FROM_FAVOURITES', data) - } }) // Remove file preview @@ -329,7 +321,10 @@ const actions = { } // Get route - let route = getters.sharedDetail ? `/api/editor/remove/${router.currentRoute.params.token}` : '/api/remove' + let route = { + RequestUpload: `/api/upload-request/${router.currentRoute.params.token}/remove`, + Public: `/api/editor/remove/${router.currentRoute.params.token}`, + }[router.currentRoute.name] || '/api/remove' axios .post(route, { diff --git a/routes/upload-request.php b/routes/upload-request.php index cc22b8fe..2dda7d19 100644 --- a/routes/upload-request.php +++ b/routes/upload-request.php @@ -1,6 +1,7 @@ ['auth:sanctum']], function () { Route::post('/', CreateUploadRequestController::class); diff --git a/src/Domain/Items/Actions/DeleteFileOrFolderAction.php b/src/Domain/Items/Actions/DeleteFileOrFolderAction.php index d54c7930..ba68f4b8 100644 --- a/src/Domain/Items/Actions/DeleteFileOrFolderAction.php +++ b/src/Domain/Items/Actions/DeleteFileOrFolderAction.php @@ -65,10 +65,9 @@ class DeleteFileOrFolderAction Storage::delete("/files/$file->user_id/$file->basename"); // Delete thumbnail if exist - if ($file->thumbnail) { - Storage::delete( - "/files/$file->user_id/{$file->getRawOriginal('thumbnail')}" - ); + if ($file->type === 'image') { + getThumbnailFileList($file->basename) + ->each(fn ($thumbnail) => Storage::delete("files/$file->user_id/$thumbnail")); } // Delete file permanently @@ -104,10 +103,9 @@ class DeleteFileOrFolderAction Storage::delete("/files/$file->user_id/$file->basename"); // Delete thumbnail if exist - if ($file->thumbnail) { - Storage::delete( - "/files/$file->user_id/{$file->getRawOriginal('thumbnail')}" - ); + if ($file->type === 'image') { + getThumbnailFileList($file->basename) + ->each(fn ($thumbnail) => Storage::delete("files/$file->user_id/$thumbnail")); } // Delete file permanently diff --git a/src/Domain/Teams/Actions/TransferContentOwnershipToTeamFolderOwnerAction.php b/src/Domain/Teams/Actions/TransferContentOwnershipToTeamFolderOwnerAction.php index 270512a5..59abe6ec 100644 --- a/src/Domain/Teams/Actions/TransferContentOwnershipToTeamFolderOwnerAction.php +++ b/src/Domain/Teams/Actions/TransferContentOwnershipToTeamFolderOwnerAction.php @@ -53,7 +53,7 @@ class TransferContentOwnershipToTeamFolderOwnerAction // Move image thumbnails if ($file->type === 'image') { // Get image thumbnail list - $thumbnailList = get_thumbnail_file_list($file->basename); + $thumbnailList = getThumbnailFileList($file->basename); // move thumbnails to the new location $thumbnailList->each(function ($basename) use ($file, $folder) { diff --git a/src/Domain/Trash/Controllers/DumpTrashController.php b/src/Domain/Trash/Controllers/DumpTrashController.php index a5cddbdf..127bfaaf 100644 --- a/src/Domain/Trash/Controllers/DumpTrashController.php +++ b/src/Domain/Trash/Controllers/DumpTrashController.php @@ -13,7 +13,7 @@ class DumpTrashController extends Controller public function __invoke(): Response { abort_if( - is_demo_account(auth()->user()->email), + is_demo_account(), 204, 'Done.' ); diff --git a/src/Domain/UploadRequest/Controllers/DeleteFileOrFolderController.php b/src/Domain/UploadRequest/Controllers/DeleteFileOrFolderController.php new file mode 100644 index 00000000..ae9aec5e --- /dev/null +++ b/src/Domain/UploadRequest/Controllers/DeleteFileOrFolderController.php @@ -0,0 +1,78 @@ +status !== 'active') { + return response('Gone', 410); + } + + foreach ($request->input('items') as $file) { + // Get file or folder item + $item = get_item($file['type'], $file['id']); + + // Delete folder + if ($file['type'] === 'folder') { + $this->destroyFolder($item); + } + + // Delete file + if ($file['type'] !== 'folder') { + $this->destroyFile($item); + } + } + + return response('Done', 204); + } + + private function destroyFile(File $file): void + { + // Delete file + Storage::delete("/files/$file->user_id/$file->basename"); + + // Delete thumbnail if exist + if ($file->type === 'image') { + getThumbnailFileList($file->basename) + ->each(fn ($thumbnail) => Storage::delete("files/$file->user_id/$thumbnail")); + } + + // Delete file permanently + $file->forceDelete(); + } + + private function destroyFolder(Folder $folder): void + { + // Get children files + $files = File::whereIn('parent_id', Arr::flatten([filter_folders_ids($folder->folders), $folder->id])) + ->get(); + + // Remove all children files + foreach ($files as $file) { + // Delete file + Storage::delete("/files/$file->user_id/$file->basename"); + + // Delete thumbnail if exist + if ($file->type === 'image') { + getThumbnailFileList($file->basename) + ->each(fn ($thumbnail) => Storage::delete("files/$file->user_id/$thumbnail")); + } + + // Delete file permanently + $file->forceDelete(); + } + + $folder->forceDelete(); + } +} \ No newline at end of file diff --git a/src/Support/helpers.php b/src/Support/helpers.php index baf91d4c..2e6ee362 100644 --- a/src/Support/helpers.php +++ b/src/Support/helpers.php @@ -634,11 +634,11 @@ if (! function_exists('get_file_type')) { } } -if (! function_exists('get_thumbnail_file_list')) { +if (! function_exists('getThumbnailFileList')) { /** * Get list of image thumbnails */ - function get_thumbnail_file_list(string $basename): Collection + function getThumbnailFileList(string $basename): Collection { return collect([ config('vuefilemanager.image_sizes.later'), diff --git a/tests/Domain/Files/FileTest.php b/tests/Domain/Files/FileTest.php index ab9c450f..675cd016 100644 --- a/tests/Domain/Files/FileTest.php +++ b/tests/Domain/Files/FileTest.php @@ -1,4 +1,5 @@ 'true', ])->assertStatus(201); - $disk = Storage::disk('local'); - $file = File::first(); - $disk->assertMissing( + Storage::assertMissing( "chunks/$file->basename" ); @@ -61,7 +60,7 @@ class FileTest extends TestCase ]) ->collapse() ->each( - fn ($item) => $disk->assertExists( + fn($item) => Storage::assertExists( "files/{$user->id}/{$item['name']}-{$file->basename}" ) ); @@ -235,6 +234,63 @@ class FileTest extends TestCase 'parent_id' => $folder->id, ]); } + + /** + * @test + */ + public function it_delete_image_with_their_thumbnails() + { + $user = User::factory() + ->hasSettings() + ->create(); + + $image = File::factory() + ->create([ + 'mimetype' => 'jpeg', + 'type' => 'image', + 'basename' => 'fake-image.jpeg', + 'user_id' => $user->id, + ]); + + // Mock files + $thumbnail_sizes = collect([ + config('vuefilemanager.image_sizes.later'), + config('vuefilemanager.image_sizes.immediately') + ])->collapse(); + + $fakeFile = UploadedFile::fake() + ->create("fake-image.jpeg", 2000, 'image/jpeg'); + + Storage::putFileAs("files/$user->id", $fakeFile, $fakeFile->name); + + // Create fake image thumbnails + $thumbnail_sizes + ->each(function ($item) use ($user) { + $fakeFile = UploadedFile::fake() + ->create("{$item['name']}-fake-image.jpeg", 2000, 'image/jpeg'); + + Storage::putFileAs("files/$user->id", $fakeFile, $fakeFile->name); + }); + + $this + ->actingAs($user) + ->postJson('/api/remove', [ + 'items' => [ + [ + 'id' => $image->id, + 'type' => 'file', + 'force_delete' => true, + ], + ], + ])->assertStatus(204); + + // Assert primary file was deleted + Storage::assertMissing("files/$user->id/fake-image.jpeg"); + + // Assert thumbnail was deleted + getThumbnailFileList("fake-image.jpeg") + ->each(fn($thumbnail) => Storage::assertMissing("files/$user->id/$thumbnail")); + } /** * @test diff --git a/tests/Domain/UploadRequest/UploadRequestEditingTest.php b/tests/Domain/UploadRequest/UploadRequestEditingTest.php index 7e40d14c..781532eb 100644 --- a/tests/Domain/UploadRequest/UploadRequestEditingTest.php +++ b/tests/Domain/UploadRequest/UploadRequestEditingTest.php @@ -2,10 +2,12 @@ namespace Tests\Domain\UploadRequest; +use Storage; use Tests\TestCase; use App\Users\Models\User; use Domain\Files\Models\File; use Domain\Folders\Models\Folder; +use Illuminate\Http\UploadedFile; use Domain\UploadRequest\Models\UploadRequest; class UploadRequestEditingTest extends TestCase @@ -107,7 +109,7 @@ class UploadRequestEditingTest extends TestCase ]) ->assertStatus(201) ->assertJsonFragment([ - 'name' => 'New Folder', + 'name' => 'New Folder', ]); $this->assertDatabaseHas('folders', [ @@ -115,4 +117,176 @@ class UploadRequestEditingTest extends TestCase 'parent_id' => $uploadRequest->id, ]); } + + /** + * @test + */ + public function it_delete_image_with_their_thumbnails() + { + $user = User::factory() + ->hasSettings() + ->create(); + + $uploadRequest = UploadRequest::factory() + ->create([ + 'status' => 'active', + 'user_id' => $user->id, + ]); + + $image = File::factory() + ->create([ + 'mimetype' => 'jpeg', + 'type' => 'image', + 'basename' => 'fake-image.jpeg', + 'user_id' => $user->id, + 'parent_id' => $uploadRequest->id, + ]); + + // Mock files + $thumbnail_sizes = collect([ + config('vuefilemanager.image_sizes.later'), + config('vuefilemanager.image_sizes.immediately') + ])->collapse(); + + $fakeFile = UploadedFile::fake() + ->create("fake-image.jpeg", 2000, 'image/jpeg'); + + Storage::putFileAs("files/$user->id", $fakeFile, $fakeFile->name); + + // Create fake image thumbnails + $thumbnail_sizes + ->each(function ($item) use ($user) { + $fakeFile = UploadedFile::fake() + ->create("{$item['name']}-fake-image.jpeg", 2000, 'image/jpeg'); + + Storage::putFileAs("files/$user->id", $fakeFile, $fakeFile->name); + }); + + $this + ->postJson("/api/upload-request/$uploadRequest->id/remove", [ + 'items' => [ + [ + 'id' => $image->id, + 'type' => 'file', + 'force_delete' => true, + ], + ], + ])->assertStatus(204); + + // Assert primary file was deleted + Storage::assertMissing("files/$user->id/fake-image.jpeg"); + + // Assert thumbnail was deleted + getThumbnailFileList("fake-image.jpeg") + ->each(fn($thumbnail) => Storage::assertMissing("files/$user->id/$thumbnail")); + } + + /** + * @test + */ + public function it_delete_file() + { + $user = User::factory() + ->hasSettings() + ->create(); + + $uploadRequest = UploadRequest::factory() + ->create([ + 'status' => 'active', + 'user_id' => $user->id, + ]); + + $file = File::factory() + ->create([ + 'type' => 'file', + 'basename' => 'fake-file.pdf', + 'user_id' => $user->id, + 'parent_id' => $uploadRequest->id, + ]); + + $fakeFile = UploadedFile::fake() + ->create('fake-file.pdf', 1200, 'application/pdf'); + + Storage::putFileAs("files/$user->id", $fakeFile, $fakeFile->name); + + $this + ->postJson("/api/upload-request/$uploadRequest->id/remove", [ + 'items' => [ + [ + 'id' => $file->id, + 'type' => 'file', + 'force_delete' => true, + ], + ], + ])->assertStatus(204); + + // Assert primary file was deleted + Storage::assertMissing("files/$user->id/fake-file.pdf"); + } + + /** + * @test + */ + public function it_delete_folder_with_file_within() + { + $user = User::factory() + ->hasSettings() + ->create(); + + $uploadRequest = UploadRequest::factory() + ->create([ + 'status' => 'active', + 'user_id' => $user->id, + ]); + + $folder = Folder::factory() + ->create([ + 'user_id' => $user->id, + 'parent_id' => $uploadRequest->id, + ]); + + $folderWithin = Folder::factory() + ->create([ + 'user_id' => $user->id, + 'parent_id' => $folder->id, + ]); + + $file = File::factory() + ->create([ + 'type' => 'file', + 'basename' => 'fake-file.pdf', + 'user_id' => $user->id, + 'parent_id' => $folder->id, + ]); + + $fakeFile = UploadedFile::fake() + ->create('fake-file.pdf', 1200, 'application/pdf'); + + Storage::putFileAs("files/$user->id", $fakeFile, $fakeFile->name); + + $this + ->postJson("/api/upload-request/$uploadRequest->id/remove", [ + 'items' => [ + [ + 'id' => $folder->id, + 'type' => 'folder', + 'force_delete' => true, + ], + ], + ])->assertStatus(204); + + $this + ->assertDatabaseMissing('folders', [ + 'id' => $folder->id, + ]) + ->assertDatabaseMissing('folders', [ + 'id' => $folderWithin->id, + ]) + ->assertDatabaseMissing('files', [ + 'id' => $file->id, + ]); + + // Assert primary file was deleted + Storage::assertMissing("files/$user->id/fake-file.pdf"); + } }