diff --git a/.styleci.yml b/.styleci.yml index 98eac9f..802dc83 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -5,9 +5,7 @@ preset: laravel enabled: - strict - unalign_double_arrow - - phpdoc_order - - phpdoc_separation - + disabled: - short_array_syntax diff --git a/composer.json b/composer.json index d7ea546..42e6002 100644 --- a/composer.json +++ b/composer.json @@ -19,8 +19,8 @@ } ], "require": { - "laravel-enso/core": "^8.0", - "laravel-enso/helpers": "^2.0", + "laravel-enso/core": "^10.0", + "laravel-enso/helpers": "^3.0", "laravel-enso/image-transformer": "^2.0", "laravel-enso/migrator": "^2.0", "laravel-enso/track-who": "^2.0" @@ -37,8 +37,7 @@ "providers": [ "LaravelEnso\\Files\\AppServiceProvider", "LaravelEnso\\Files\\AuthServiceProvider", - "LaravelEnso\\Files\\EnumServiceProvider", - "LaravelEnso\\Files\\UploadServiceProvider" + "LaravelEnso\\Files\\EnumServiceProvider" ] } } diff --git a/config/files.php b/config/files.php index 4b82189..63eff64 100644 --- a/config/files.php +++ b/config/files.php @@ -15,8 +15,16 @@ return [ 'linkExpiration' => (int) env('TEMPORARY_LINK_EXPIRATION', 60 * 60 * 24), 'storageLimit' => 500000, - 'paginate' => (int) env('FILES_PAGINATION', 24), + 'paginate' => (int) env('FILES_PAGINATION', 50), 'testingFolder' => 'testing', + 'renameFolders' => [ + 'dataImport' => 'import', + 'dataExport' => 'export', + 'webshopCarouselSlide' => 'carouselSlide', + ], + 'nonStandardFolders' => [ + 'files', 'imports', 'carousel', 'howToVideos', 'webshopCarouselSlide', + ], 'upgrade' => [ 'avatar' => Avatar::class, 'dataExport' => Export::class, diff --git a/database/factories/TypeFactory.php b/database/factories/TypeFactory.php index e50a58f..d4e7c55 100644 --- a/database/factories/TypeFactory.php +++ b/database/factories/TypeFactory.php @@ -19,6 +19,7 @@ public function definition() 'icon' => 'folder', 'endpoint' => null, 'description' => null, + 'is_public' => false, 'is_browsable' => false, 'is_system' => false, ]; @@ -34,7 +35,7 @@ public function model(string $model): self return $this->state(fn () => [ 'name' => $name, - 'folder' => $name->lower(), + 'folder' => $name->camel(), 'model' => $model, 'description' => "Enso {$name}", ]); diff --git a/database/migrations/2022_01_23_100000_create_file_types_table.php b/database/migrations/2017_01_01_112100_create_file_types_table.php similarity index 90% rename from database/migrations/2022_01_23_100000_create_file_types_table.php rename to database/migrations/2017_01_01_112100_create_file_types_table.php index 814f647..788ae24 100644 --- a/database/migrations/2022_01_23_100000_create_file_types_table.php +++ b/database/migrations/2017_01_01_112100_create_file_types_table.php @@ -4,7 +4,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class CreateFileTypesTable extends Migration +return new class extends Migration { public function up() { @@ -19,6 +19,7 @@ public function up() $table->text('description')->nullable(); + $table->boolean('is_public'); $table->boolean('is_browsable'); $table->boolean('is_system'); @@ -30,4 +31,4 @@ public function down() { Schema::dropIfExists('file_types'); } -} +}; diff --git a/database/migrations/2018_08_25_100000_create_files_table.php b/database/migrations/2017_01_01_112200_create_files_table.php similarity index 78% rename from database/migrations/2018_08_25_100000_create_files_table.php rename to database/migrations/2017_01_01_112200_create_files_table.php index d411e71..39b1926 100644 --- a/database/migrations/2018_08_25_100000_create_files_table.php +++ b/database/migrations/2017_01_01_112200_create_files_table.php @@ -4,14 +4,14 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class CreateFilesTable extends Migration +return new class extends Migration { public function up() { Schema::create('files', function (Blueprint $table) { $table->id(); - $table->unsignedBigInteger('type_id')->nullable(); + $table->bigInteger('type_id')->unsigned(); $table->foreign('type_id')->references('id')->on('file_types'); $table->nullableMorphs('attachable'); @@ -21,10 +21,15 @@ public function up() $table->integer('size'); $table->string('mime_type')->nullable(); + $table->boolean('is_public'); + $table->integer('created_by')->unsigned()->nullable(); $table->foreign('created_by')->references('id')->on('users'); $table->timestamps(); + + $table->index('created_at'); + $table->index(['type_id', 'created_at']); }); } @@ -32,4 +37,4 @@ public function down() { Schema::dropIfExists('files'); } -} +}; diff --git a/database/migrations/2018_08_25_101000_create_structure_for_files.php b/database/migrations/2017_01_01_112300_create_structure_for_files.php similarity index 75% rename from database/migrations/2018_08_25_101000_create_structure_for_files.php rename to database/migrations/2017_01_01_112300_create_structure_for_files.php index 634c5c8..6b26045 100644 --- a/database/migrations/2018_08_25_101000_create_structure_for_files.php +++ b/database/migrations/2017_01_01_112300_create_structure_for_files.php @@ -2,7 +2,7 @@ use LaravelEnso\Migrator\Database\Migration; -class CreateStructureForFiles extends Migration +return new class extends Migration { protected array $permissions = [ ['name' => 'core.files.index', 'description' => 'List files', 'is_default' => true], @@ -10,16 +10,16 @@ class CreateStructureForFiles extends Migration ['name' => 'core.files.show', 'description' => 'Open file in browser', 'is_default' => true], ['name' => 'core.files.download', 'description' => 'Download file', 'is_default' => true], ['name' => 'core.files.destroy', 'description' => 'Delete file', 'is_default' => true], - ['name' => 'core.files.favorite', 'description' => 'Toggle file as favorite', 'is_default' => true], ['name' => 'core.files.browse', 'description' => 'Browse file type', 'is_default' => true], ['name' => 'core.files.recent', 'description' => 'Browse recent files', 'is_default' => true], ['name' => 'core.files.favorites', 'description' => 'Browse favorites files', 'is_default' => true], - ['name' => 'core.files.sharedWithYou', 'description' => 'Browse files shared with user', 'is_default' => true], - ['name' => 'core.files.sharedByYou', 'description' => 'Browse files shared by user', 'is_default' => true], + ['name' => 'core.files.update', 'description' => 'Update file name', 'is_default' => true], + ['name' => 'core.files.makePublic', 'description' => 'Make file public', 'is_default' => true], + ['name' => 'core.files.makePrivate', 'description' => 'Make file private', 'is_default' => true], ['name' => 'core.files.favorite', 'description' => 'Toggle file as favorite', 'is_default' => true], ]; protected array $menu = [ 'name' => 'Files', 'icon' => 'folder-open', 'route' => 'core.files.index', 'order_index' => 255, 'has_children' => false, ]; -} +}; diff --git a/database/migrations/2018_08_25_102000_create_uploads_table.php b/database/migrations/2017_01_01_112400_create_uploads_table.php similarity index 61% rename from database/migrations/2018_08_25_102000_create_uploads_table.php rename to database/migrations/2017_01_01_112400_create_uploads_table.php index 5f89618..0063b33 100644 --- a/database/migrations/2018_08_25_102000_create_uploads_table.php +++ b/database/migrations/2017_01_01_112400_create_uploads_table.php @@ -4,13 +4,17 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class CreateUploadsTable extends Migration +return new class extends Migration { public function up() { Schema::create('uploads', function (Blueprint $table) { $table->increments('id'); + $table->bigInteger('file_id')->unsigned()->nullable()->unique(); + $table->foreign('file_id')->references('id')->on('files') + ->onUpdate('restrict')->onDelete('restrict'); + $table->timestamps(); }); } @@ -19,4 +23,4 @@ public function down() { Schema::dropIfExists('uploads'); } -} +}; diff --git a/database/migrations/2018_08_25_103000_create_structure_for_uploads.php b/database/migrations/2017_01_01_112500_create_structure_for_uploads.php similarity index 85% rename from database/migrations/2018_08_25_103000_create_structure_for_uploads.php rename to database/migrations/2017_01_01_112500_create_structure_for_uploads.php index d8e5ec2..08e9ec0 100644 --- a/database/migrations/2018_08_25_103000_create_structure_for_uploads.php +++ b/database/migrations/2017_01_01_112500_create_structure_for_uploads.php @@ -2,10 +2,10 @@ use LaravelEnso\Migrator\Database\Migration; -class CreateStructureForUploads extends Migration +return new class extends Migration { protected array $permissions = [ ['name' => 'core.uploads.store', 'description' => 'Upload file', 'is_default' => true], ['name' => 'core.uploads.destroy', 'description' => 'Delete upload', 'is_default' => true], ]; -} +}; diff --git a/database/migrations/2022_01_17_100000_create_favorite_files_table.php b/database/migrations/2017_01_01_112600_create_favorite_files_table.php similarity index 68% rename from database/migrations/2022_01_17_100000_create_favorite_files_table.php rename to database/migrations/2017_01_01_112600_create_favorite_files_table.php index 86b2beb..6395d41 100644 --- a/database/migrations/2022_01_17_100000_create_favorite_files_table.php +++ b/database/migrations/2017_01_01_112600_create_favorite_files_table.php @@ -4,22 +4,24 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class CreateFavoriteFilesTable extends Migration +return new class extends Migration { public function up() { Schema::create('favorite_files', function (Blueprint $table) { $table->id(); - $table->unsignedInteger('user_id')->index(); + $table->integer('user_id')->unsigned()->index(); $table->foreign('user_id')->references('id')->on('users'); - $table->unsignedBigInteger('file_id')->index(); - $table->foreign('file_id')->references('id')->on('files'); + $table->bigInteger('file_id')->unsigned()->index(); + $table->foreign('file_id')->references('id')->on('files') + ->onUpdate('restrict')->onDelete('restrict'); $table->timestamps(); $table->unique(['user_id', 'file_id']); + $table->index(['created_at']); }); } @@ -27,4 +29,4 @@ public function down() { Schema::dropIfExists('favorite_files'); } -} +}; diff --git a/database/migrations/2022_01_23_101000_create_structure_for_file_types.php b/database/migrations/2017_01_01_129000_create_structure_for_file_types.php similarity index 96% rename from database/migrations/2022_01_23_101000_create_structure_for_file_types.php rename to database/migrations/2017_01_01_129000_create_structure_for_file_types.php index 2f94d74..0625170 100644 --- a/database/migrations/2022_01_23_101000_create_structure_for_file_types.php +++ b/database/migrations/2017_01_01_129000_create_structure_for_file_types.php @@ -2,7 +2,7 @@ use LaravelEnso\Migrator\Database\Migration; -class CreateStructureForFileTypes extends Migration +return new class extends Migration { protected array $permissions = [ ['name' => 'administration.fileTypes.tableData', 'description' => 'Get table data for file types', 'is_default' => false], @@ -21,4 +21,4 @@ class CreateStructureForFileTypes extends Migration ]; protected ?string $parentMenu = 'Administration'; -} +}; diff --git a/database/seeders/TypeSeeder.php b/database/seeders/TypeSeeder.php index 5d7609b..afb4fac 100644 --- a/database/seeders/TypeSeeder.php +++ b/database/seeders/TypeSeeder.php @@ -7,7 +7,7 @@ use LaravelEnso\DataExport\Models\Export; use LaravelEnso\DataImport\Models\Import; use LaravelEnso\DataImport\Models\RejectedImport; -use LaravelEnso\Documents\Http\Resources\Document; +use LaravelEnso\Documents\Models\Document; use LaravelEnso\Files\Models\Type; use LaravelEnso\Files\Models\Upload; use LaravelEnso\HowTo\Models\Poster; @@ -20,40 +20,37 @@ class TypeSeeder extends Seeder { public function run() { - $this - // ->avatars() - // ->recent() - // ->favorites() - // ->sharedWithYou() - // ->sharedByYou() - // ->uploads() - // ->exports() - // ->imports() - // ->rejectedImports() - // ->documents() - // ->productPictures() - // ->brands() - // ->carouselSlides() - // ->howToPosters() + $this->avatars() + ->recents() + ->favorites() + ->uploads() + ->exports() + ->imports() + ->rejectedImports() + ->documents() + ->productPictures() + ->brands() + ->carouselSlides() + ->howToPosters() ->howToVideos(); } private function avatars(): self { - Type::factory()->model(Avatar::class); + Type::factory()->model(Avatar::class)->create(); return $this; } - private function recent(): self + private function recents(): self { Type::factory()->create([ - 'name' => 'Recent', + 'name' => 'Recents', 'folder' => null, 'model' => null, 'icon' => 'folder-plus', 'endpoint' => 'recent', - 'description' => 'User Favorites', + 'description' => 'Recent files', 'is_browsable' => true, 'is_system' => true, ]); @@ -77,43 +74,11 @@ private function favorites(): self return $this; } - private function sharedWithYou(): self - { - Type::factory()->create([ - 'name' => 'Shared with You', - 'folder' => null, - 'model' => null, - 'icon' => 'share', - 'endpoint' => 'sharedWithYou', - 'description' => 'Shared with User', - 'is_browsable' => false, - 'is_system' => true, - ]); - - return $this; - } - - private function sharedByYou(): self - { - Type::factory()->create([ - 'name' => 'Shared by You', - 'folder' => null, - 'model' => null, - 'icon' => 'share-alt', - 'endpoint' => 'sharedByYou', - 'description' => 'Shared by User', - 'is_browsable' => false, - 'is_system' => true, - ]); - - return $this; - } - private function uploads(): self { Type::factory()->model(Upload::class)->create([ 'name' => 'Uploads', - 'icon' => 'folder-upload', + 'icon' => 'file-upload', 'is_browsable' => true, 'is_system' => false, ]); @@ -174,7 +139,6 @@ private function productPictures(): self Type::factory()->model(Picture::class)->create([ 'icon' => 'image', 'is_browsable' => true, - 'folder' => 'productPictures', ]); } @@ -184,7 +148,10 @@ private function productPictures(): self private function brands(): self { if (class_exists(Brand::class)) { - Type::factory()->model(Brand::class)->create(); + Type::factory()->model(Brand::class)->create([ + 'is_browsable' => true, + 'icon' => 'copyright', + ]); } return $this; diff --git a/routes/app/files.php b/routes/app/files.php index fbcbd36..bb70e0a 100644 --- a/routes/app/files.php +++ b/routes/app/files.php @@ -6,17 +6,16 @@ use LaravelEnso\Files\Http\Controllers\File\Download; use LaravelEnso\Files\Http\Controllers\File\Favorite; use LaravelEnso\Files\Http\Controllers\File\Favorites; -use LaravelEnso\Files\Http\Controllers\File\Index; use LaravelEnso\Files\Http\Controllers\File\Link; +use LaravelEnso\Files\Http\Controllers\File\MakePrivate; +use LaravelEnso\Files\Http\Controllers\File\MakePublic; use LaravelEnso\Files\Http\Controllers\File\Recent; -use LaravelEnso\Files\Http\Controllers\File\SharedByYou; -use LaravelEnso\Files\Http\Controllers\File\SharedWithYou; use LaravelEnso\Files\Http\Controllers\File\Show; +use LaravelEnso\Files\Http\Controllers\File\Update; Route::prefix('files') ->as('files.') ->group(function () { - Route::get('', Index::class)->name('index'); Route::get('link/{file}', Link::class)->name('link'); Route::get('download/{file}', Download::class)->name('download'); Route::delete('{file}', Destroy::class)->name('destroy'); @@ -24,7 +23,8 @@ Route::get('browse/{type}', Browse::class)->name('browse'); Route::get('recent', Recent::class)->name('recent'); Route::get('favorites', Favorites::class)->name('favorites'); - Route::get('sharedByYou', SharedByYou::class)->name('sharedByYou'); - Route::get('sharedWithYou', SharedWithYou::class)->name('sharedWithYou'); + Route::patch('{file}', Update::class)->name('update'); + Route::patch('makePublic/{file}', MakePublic::class)->name('makePublic'); + Route::patch('makePrivate/{file}', MakePrivate::class)->name('makePrivate'); Route::patch('favorite/{file}', Favorite::class)->name('favorite'); }); diff --git a/src/AppServiceProvider.php b/src/AppServiceProvider.php index 0e943e6..13242e1 100644 --- a/src/AppServiceProvider.php +++ b/src/AppServiceProvider.php @@ -3,9 +3,6 @@ namespace LaravelEnso\Files; use Illuminate\Support\ServiceProvider; -use LaravelEnso\DynamicMethods\Services\Methods; -use LaravelEnso\Files\Dynamics\Relations\FavoriteFiles; -use LaravelEnso\Users\Models\User; class AppServiceProvider extends ServiceProvider { @@ -13,8 +10,6 @@ public function boot() { $this->load() ->publish(); - - Methods::bind(User::class, [FavoriteFiles::class]); } private function load() diff --git a/src/Contracts/Attachable.php b/src/Contracts/Attachable.php index 8adb8b8..2a2187f 100644 --- a/src/Contracts/Attachable.php +++ b/src/Contracts/Attachable.php @@ -2,9 +2,6 @@ namespace LaravelEnso\Files\Contracts; -use Illuminate\Database\Eloquent\Relations\Relation; - interface Attachable { - public function file(): Relation; } diff --git a/src/Contracts/CascadesFileDeletion.php b/src/Contracts/CascadesFileDeletion.php new file mode 100644 index 0000000..5d098c5 --- /dev/null +++ b/src/Contracts/CascadesFileDeletion.php @@ -0,0 +1,10 @@ + $this->hasManyThrough( + return fn (User $user) => $user->hasManyThrough( File::class, Favorite::class, 'user_id', diff --git a/src/Forms/Builders/Type.php b/src/Forms/Builders/Type.php new file mode 100644 index 0000000..e513fcf --- /dev/null +++ b/src/Forms/Builders/Type.php @@ -0,0 +1,33 @@ +form = (new Form($this->templatePath())); + } + + public function create() + { + return $this->form->create(); + } + + public function edit(Model $type) + { + return $this->form->edit($type); + } + + protected function templatePath(): string + { + return self::TemplatePath; + } +} diff --git a/src/Forms/Builders/TypeForm.php b/src/Forms/Builders/TypeForm.php deleted file mode 100644 index 255eaa4..0000000 --- a/src/Forms/Builders/TypeForm.php +++ /dev/null @@ -1,28 +0,0 @@ -form = (new Form(static::FormPath)); - } - - public function create() - { - return $this->form->create(); - } - - public function edit(Type $type) - { - return $this->form->edit($type); - } -} diff --git a/src/Forms/Templates/type.json b/src/Forms/Templates/type.json index 554a142..e743ad9 100644 --- a/src/Forms/Templates/type.json +++ b/src/Forms/Templates/type.json @@ -39,9 +39,23 @@ "type": "input", "content": "text" } + } + ] + }, + { + "columns": 3, + "fields": [ + { + "label": "Public", + "name": "is_public", + "value": false, + "meta": { + "type": "input", + "content": "checkbox" + } }, { - "label": "Is Browsable", + "label": "Browsable", "name": "is_browsable", "value": false, "meta": { @@ -50,7 +64,7 @@ } }, { - "label": "Is System", + "label": "System", "name": "is_system", "value": false, "meta": { diff --git a/src/Http/Controllers/File/Browse.php b/src/Http/Controllers/File/Browse.php index 7567bb1..36b7ecf 100644 --- a/src/Http/Controllers/File/Browse.php +++ b/src/Http/Controllers/File/Browse.php @@ -14,7 +14,11 @@ class Browse extends Controller public function __invoke(Request $request, Type $type) { - $files = $type->files()->for($request->user())->get(); + $files = $type->files() + ->for($request->user()) + ->between(json_decode($request->get('interval'), true)) + ->filter($request->get('query')) + ->get(); return File::collection($files); } diff --git a/src/Http/Controllers/File/Destroy.php b/src/Http/Controllers/File/Destroy.php index f785d11..e55f33b 100644 --- a/src/Http/Controllers/File/Destroy.php +++ b/src/Http/Controllers/File/Destroy.php @@ -13,7 +13,7 @@ class Destroy extends Controller public function __invoke(File $file) { - $this->authorize('destroy', $file); + $this->authorize('manage', $file); DB::transaction(fn () => $file->delete(true)); } diff --git a/src/Http/Controllers/File/Favorites.php b/src/Http/Controllers/File/Favorites.php index 2243709..1741e01 100644 --- a/src/Http/Controllers/File/Favorites.php +++ b/src/Http/Controllers/File/Favorites.php @@ -13,7 +13,14 @@ class Favorites extends Controller public function __invoke(Request $request) { - $files = $request->user()->favoriteFiles()->withData()->get(); + $files = $request->user() + ->favoriteFiles() + ->withData() + ->between(json_decode($request->get('interval'), true)) + ->filter($request->get('query')) + ->paginated() + ->latest('id') + ->get(); return File::collection($files); } diff --git a/src/Http/Controllers/File/Index.php b/src/Http/Controllers/File/Index.php deleted file mode 100644 index 6edb00e..0000000 --- a/src/Http/Controllers/File/Index.php +++ /dev/null @@ -1,17 +0,0 @@ -browsable()->get(); - - return ['folders' => Resource::collection($folders)]; - } -} diff --git a/src/Http/Controllers/File/MakePrivate.php b/src/Http/Controllers/File/MakePrivate.php new file mode 100644 index 0000000..bb6bba0 --- /dev/null +++ b/src/Http/Controllers/File/MakePrivate.php @@ -0,0 +1,22 @@ +authorize('manage', $file); + + $file->update(['is_public' => false]); + + return new Resource($file); + } +} diff --git a/src/Http/Controllers/File/MakePublic.php b/src/Http/Controllers/File/MakePublic.php new file mode 100644 index 0000000..a9f9f7c --- /dev/null +++ b/src/Http/Controllers/File/MakePublic.php @@ -0,0 +1,22 @@ +authorize('manage', $file); + + $file->update(['is_public' => true]); + + return new Resource($file); + } +} diff --git a/src/Http/Controllers/File/Recent.php b/src/Http/Controllers/File/Recent.php index 78d0ff5..15f7628 100644 --- a/src/Http/Controllers/File/Recent.php +++ b/src/Http/Controllers/File/Recent.php @@ -15,8 +15,8 @@ class Recent extends Controller public function __invoke(Request $request) { $files = File::for($request->user()) - ->latest('id') - ->limit(50) + ->between(json_decode($request->get('interval'), true)) + ->filter($request->get('query')) ->get(); return Resource::collection($files); diff --git a/src/Http/Controllers/File/SharedByYou.php b/src/Http/Controllers/File/SharedByYou.php deleted file mode 100644 index 68fa0db..0000000 --- a/src/Http/Controllers/File/SharedByYou.php +++ /dev/null @@ -1,15 +0,0 @@ -authorize('manage', $file); + + $name = "{$request->get('name')}.{$file->extension()}"; + + $file->update(['original_name' => $name]); + } +} diff --git a/src/Http/Controllers/Type/Create.php b/src/Http/Controllers/Type/Create.php index e5d518a..a58ce78 100644 --- a/src/Http/Controllers/Type/Create.php +++ b/src/Http/Controllers/Type/Create.php @@ -3,11 +3,11 @@ namespace LaravelEnso\Files\Http\Controllers\Type; use Illuminate\Routing\Controller; -use LaravelEnso\Files\Forms\Builders\TypeForm; +use LaravelEnso\Files\Forms\Builders\Type; class Create extends Controller { - public function __invoke(TypeForm $form) + public function __invoke(Type $form) { return ['form' => $form->create()]; } diff --git a/src/Http/Controllers/Type/Destroy.php b/src/Http/Controllers/Type/Destroy.php index d30f908..77d7b35 100644 --- a/src/Http/Controllers/Type/Destroy.php +++ b/src/Http/Controllers/Type/Destroy.php @@ -13,5 +13,10 @@ class Destroy extends Controller public function __invoke(Type $type) { $type->delete(); + + return [ + 'message' => __('The file type was successfully deleted'), + 'redirect' => 'administration.fileTypes.index', + ]; } } diff --git a/src/Http/Controllers/Type/Edit.php b/src/Http/Controllers/Type/Edit.php index 55fb248..7451a25 100644 --- a/src/Http/Controllers/Type/Edit.php +++ b/src/Http/Controllers/Type/Edit.php @@ -3,12 +3,12 @@ namespace LaravelEnso\Files\Http\Controllers\Type; use Illuminate\Routing\Controller; -use LaravelEnso\Files\Forms\Builders\TypeForm; -use LaravelEnso\Files\Models\Type; +use LaravelEnso\Files\Forms\Builders\Type; +use LaravelEnso\Files\Models\Type as Model; class Edit extends Controller { - public function __invoke(Type $type, TypeForm $form) + public function __invoke(Model $type, Type $form) { return ['form' => $form->edit($type)]; } diff --git a/src/Http/Controllers/Type/ExportExcel.php b/src/Http/Controllers/Type/ExportExcel.php index c0a69fb..c70943f 100644 --- a/src/Http/Controllers/Type/ExportExcel.php +++ b/src/Http/Controllers/Type/ExportExcel.php @@ -3,12 +3,12 @@ namespace LaravelEnso\Files\Http\Controllers\Type; use Illuminate\Routing\Controller; -use LaravelEnso\Files\Tables\Builders\TypeTable; +use LaravelEnso\Files\Tables\Builders\Type; use LaravelEnso\Tables\Traits\Excel; class ExportExcel extends Controller { use Excel; - protected $tableClass = TypeTable::class; + protected $tableClass = Type::class; } diff --git a/src/Http/Controllers/Type/InitTable.php b/src/Http/Controllers/Type/InitTable.php index 4a00ad4..691c919 100644 --- a/src/Http/Controllers/Type/InitTable.php +++ b/src/Http/Controllers/Type/InitTable.php @@ -3,12 +3,12 @@ namespace LaravelEnso\Files\Http\Controllers\Type; use Illuminate\Routing\Controller; -use LaravelEnso\Files\Tables\Builders\TypeTable; +use LaravelEnso\Files\Tables\Builders\Type; use LaravelEnso\Tables\Traits\Init; class InitTable extends Controller { use Init; - protected string $tableClass = TypeTable::class; + protected string $tableClass = Type::class; } diff --git a/src/Http/Controllers/Type/TableData.php b/src/Http/Controllers/Type/TableData.php index 95057b7..c5e7db9 100644 --- a/src/Http/Controllers/Type/TableData.php +++ b/src/Http/Controllers/Type/TableData.php @@ -3,12 +3,12 @@ namespace LaravelEnso\Files\Http\Controllers\Type; use Illuminate\Routing\Controller; -use LaravelEnso\Files\Tables\Builders\TypeTable; +use LaravelEnso\Files\Tables\Builders\Type; use LaravelEnso\Tables\Traits\Data; class TableData extends Controller { use Data; - protected string $tableClass = TypeTable::class; + protected string $tableClass = Type::class; } diff --git a/src/Http/Controllers/Type/Update.php b/src/Http/Controllers/Type/Update.php index aefc6bb..af74d7f 100644 --- a/src/Http/Controllers/Type/Update.php +++ b/src/Http/Controllers/Type/Update.php @@ -10,7 +10,13 @@ class Update extends Controller { public function __invoke(ValidateType $request, Type $type) { - $type->update($request->validated()); + $type->fill($request->validated()); + + if ($type->isDirty('folder')) { + $type->move(); + } + + $type->save(); return ['message' => __('The file type was successfully updated')]; } diff --git a/src/Http/Controllers/Upload/Store.php b/src/Http/Controllers/Upload/Store.php index a422fcb..609918c 100644 --- a/src/Http/Controllers/Upload/Store.php +++ b/src/Http/Controllers/Upload/Store.php @@ -4,12 +4,16 @@ use Illuminate\Http\Request; use Illuminate\Routing\Controller; +use LaravelEnso\Files\Http\Resources\File; use LaravelEnso\Files\Models\Upload; class Store extends Controller { public function __invoke(Request $request, Upload $upload) { - return $upload->store($request->allFiles()); + $files = $upload->store($request->allFiles()); + $files->each->loadData(); + + return File::collection($files); } } diff --git a/src/Http/Requests/ValidateName.php b/src/Http/Requests/ValidateName.php new file mode 100644 index 0000000..d117a93 --- /dev/null +++ b/src/Http/Requests/ValidateName.php @@ -0,0 +1,18 @@ + 'required|string|max:255']; + } +} diff --git a/src/Http/Requests/ValidateType.php b/src/Http/Requests/ValidateType.php index 65c93c1..e390631 100644 --- a/src/Http/Requests/ValidateType.php +++ b/src/Http/Requests/ValidateType.php @@ -5,7 +5,6 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; -use Illuminate\Validation\Rules\Unique; use ReflectionClass; class ValidateType extends FormRequest @@ -23,6 +22,7 @@ public function rules() 'icon' => 'nullable|required_if:is_browsable,true|string', 'folder' => 'required_with:model|string', 'description' => 'nullable|string', + 'is_public' => 'required|boolean', 'is_browsable' => 'required|boolean', 'is_system' => 'required|boolean', ]; diff --git a/src/Http/Resources/File.php b/src/Http/Resources/File.php index 9f2b980..4c21464 100644 --- a/src/Http/Resources/File.php +++ b/src/Http/Resources/File.php @@ -3,7 +3,6 @@ namespace LaravelEnso\Files\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; -use Illuminate\Support\Str; use LaravelEnso\Helpers\Services\DiskSize; use LaravelEnso\Users\Http\Resources\User; @@ -15,24 +14,17 @@ public function toArray($request) return [ 'id' => $this->id, - 'name' => $this->original_name, + 'name' => $this->name(), + 'extension' => $this->extension(), 'size' => DiskSize::forHumans($this->size), 'mimeType' => $this->mime_type, 'type' => new Type($this->whenLoaded('type')), 'owner' => new User($this->whenLoaded('createdBy')), - 'isFavorite' => (bool) $this->whenLoaded('favorite'), - 'isDestroyable' => $request->user()->can('destroy', $this->resource), + 'isFavorite' => $this->relationLoaded('favorite') ? $this->favorite : false, + 'isManageable' => $request->user()->can('manage', $this->resource), 'isAccessible' => $accessible, - 'isViewable' => $accessible && $this->isImage(), + 'isPublic' => $this->is_public, 'createdAt' => $this->created_at->toDatetimeString(), ]; } - - private function isImage(): bool - { - $mimeType = Str::of($this->mime_type); - - return $mimeType->startsWith('image') - || $mimeType->is('application/pdf'); - } } diff --git a/src/Http/Resources/Type.php b/src/Http/Resources/Type.php index cb545af..137738f 100644 --- a/src/Http/Resources/Type.php +++ b/src/Http/Resources/Type.php @@ -13,7 +13,8 @@ public function toArray($request) return [ 'id' => $this->id, 'name' => Str::title($this->name), - 'icon' => $this->icon, + 'icon' => $this->icon(), + 'folder' => $this->folder, 'endpoint' => $this->endpoint, 'isBrowsable' => $this->is_browsable, 'isSystem' => $this->is_system, diff --git a/src/Http/Resources/Url.php b/src/Http/Resources/Url.php new file mode 100644 index 0000000..f174d88 --- /dev/null +++ b/src/Http/Resources/Url.php @@ -0,0 +1,20 @@ + $this->id, + 'path' => $this->path(), + 'url' => "{$appUrl}/{$this->path()}", + ]; + } +} diff --git a/src/Models/File.php b/src/Models/File.php index 07dd807..e7ff6df 100644 --- a/src/Models/File.php +++ b/src/Models/File.php @@ -14,11 +14,13 @@ use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; use LaravelEnso\Files\Contracts\Attachable; +use LaravelEnso\Files\Contracts\CascadesFileDeletion; use LaravelEnso\Files\Services\ImageProcessor; use LaravelEnso\Files\Services\Upload; use LaravelEnso\ImageTransformer\Services\ImageTransformer; use LaravelEnso\TrackWho\Traits\CreatedBy; use LaravelEnso\Users\Models\User; +use ReflectionClass; use Symfony\Component\HttpFoundation\File\File as BaseFile; use Symfony\Component\HttpFoundation\StreamedResponse; @@ -26,12 +28,7 @@ class File extends Model { use CreatedBy; - protected $guarded = ['id']; - - public function attachable() - { - return $this->morphTo(); - } + protected $guarded = []; public function type() { @@ -70,21 +67,33 @@ public function scopeWithData(Builder $query): Builder return $query->with($attrs); } + public function scopePublic(Builder $query): Builder + { + return $query->whereIsPublic(true); + } + public function scopeFor(Builder $query, User $user): Builder { - $super = $user->isAdmin() || $user->isSupervisor(); + return $query->browsable() + ->withData() + ->when(! $user->isSuperior(), fn ($query) => $query + ->whereCreatedBy($user->id)->orWhere->public()) + ->latest('id') + ->paginated(); + } - return $query->browsable()->withData() - ->when(! $super, fn ($query) => $query->whereCreatedBy($user->id)); + public function scopePaginated(Builder $query): Builder + { + return $query->limit(Config::get('enso.files.paginate')); } public function scopeBetween(Builder $query, array $interval): Builder { return $query ->when($interval['min'], fn ($query) => $query - ->where('created_at', '>=', Carbon::parse($interval['min']))) + ->where('files.created_at', '>=', Carbon::parse($interval['min']))) ->when($interval['max'], fn ($query) => $query - ->where('created_at', '<=', Carbon::parse($interval['max']))); + ->where('files.created_at', '<=', Carbon::parse($interval['max']))); } public function scopeFilter(Builder $query, ?string $search): Builder @@ -93,25 +102,37 @@ public function scopeFilter(Builder $query, ?string $search): Builder ->where('original_name', 'LIKE', '%'.$search.'%')); } - public function name(): string + public function loadData(): self + { + $attrs = ['type', 'createdBy.person', 'createdBy.avatar', 'favorite']; + + return $this->load($attrs); + } + + public function asciiName(): string { return Str::ascii($this->original_name); } - public function folder(): string + public function name(): string { - return $this->type->folder; + return Str::beforeLast($this->asciiName(), '.'); + } + + public function extension(): string + { + return Str::afterLast($this->asciiName(), '.'); } public function path(): string { - return "{$this->folder()}/{$this->saved_name}"; + return "{$this->type->folder()}/{$this->saved_name}"; } - public static function attach(Attachable $attachable, string $savedName, string $filename, ?int $createdBy): self + public static function attach(Attachable $attachable, string $savedName, string $filename, ?int $userId = null): self { $type = Type::for($attachable::class); - $file = new IlluminateFile(Storage::path("{$type->folder}/{$savedName}")); + $file = new IlluminateFile(Storage::path($type->path($savedName))); return self::create([ 'type_id' => $type->id, @@ -119,7 +140,8 @@ public static function attach(Attachable $attachable, string $savedName, string 'saved_name' => $savedName, 'size' => $file->getSize(), 'mime_type' => $file->getMimeType(), - 'created_by' => $createdBy, + 'is_public' => $type->isPublic(), + 'created_by' => $userId, ]); } @@ -128,10 +150,14 @@ public static function upload(Attachable $attachable, UploadedFile $file): self return (new Upload($attachable, $file))->handle(); } - public function delete(bool $parent = false) + public function delete(bool $cascadable = false) { - if ($parent) { - $this->type->model::whereFileId($this->id)->first()->delete(); + $cascadesDeletion = $cascadable + && (new ReflectionClass($this->type->model)) + ->implementsInterface(CascadesFileDeletion::class); + + if ($cascadesDeletion) { + $this->type->model::cascadeFileDeletion($this); } $this->favorites->each->delete(); @@ -143,7 +169,7 @@ public function delete(bool $parent = false) public function download(): StreamedResponse { - return Storage::download($this->path(), $this->name()); + return Storage::download($this->path(), $this->asciiName()); } public function inline(): StreamedResponse @@ -168,4 +194,11 @@ public function isImage(BaseFile $file): bool ['file' => "image|mimetypes:{$mimeTypes}"] )->passes(); } + + protected function casts(): array + { + return [ + 'is_public' => 'boolean', + ]; + } } diff --git a/src/Models/Type.php b/src/Models/Type.php index 4554504..8811607 100644 --- a/src/Models/Type.php +++ b/src/Models/Type.php @@ -5,6 +5,12 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Config; +use Illuminate\Support\Facades\File as FileFacade; +use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Str; +use LaravelEnso\Files\Contracts\Attachable; use LaravelEnso\Rememberable\Traits\Rememberable; use LaravelEnso\Tables\Traits\TableCache; @@ -16,8 +22,6 @@ class Type extends Model protected $guarded = []; - protected $casts = ['is_browsable' => 'boolean', 'is_system' => 'boolean']; - protected array $rememberableKeys = ['id', 'model']; public function files() @@ -35,9 +39,65 @@ public function scopeOrdered(Builder $query): Builder return $query->orderByDesc('is_system')->orderBy('id'); } + public function icon(): string|array + { + return Str::contains($this->icon, ' ') + ? explode(' ', $this->icon) + : $this->icon; + } + + public function model(): Attachable + { + return new $this->model; + } + + public function isPublic(): bool + { + return $this->is_public; + } + public static function for(string $model): self { return self::cacheGetBy('model', $model) ?? self::factory()->model($model)->create(); } + + public function folder(): string + { + $folder = App::runningUnitTests() + ? Config::get('enso.files.testingFolder') + : $this->folder; + + if (! Storage::has($folder)) { + Storage::makeDirectory($folder); + } + + return $folder; + } + + public function path(string $filename): string + { + return "{$this->folder()}/{$filename}"; + } + + public function move(): void + { + $from = Storage::path($this->getOriginal('folder')); + $to = Storage::path($this->folder); + + if (FileFacade::isDirectory($to)) { + FileFacade::copyDirectory($from, $to); + FileFacade::deleteDirectory($from); + } else { + FileFacade::moveDirectory($from, $to); + } + } + + protected function casts(): array + { + return [ + 'is_browsable' => 'boolean', 'is_system' => 'boolean', + 'is_public' => 'boolean', + ]; + } } diff --git a/src/Models/Upload.php b/src/Models/Upload.php index 45ba75b..200dd75 100644 --- a/src/Models/Upload.php +++ b/src/Models/Upload.php @@ -4,32 +4,39 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Http\UploadedFile; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use LaravelEnso\Files\Contracts\Attachable; -use LaravelEnso\Files\Http\Resources\File as Resource; +use LaravelEnso\Files\Contracts\CascadesFileDeletion; -class Upload extends Model implements Attachable +class Upload extends Model implements Attachable, CascadesFileDeletion { + protected $guarded = []; + public function file(): Relation { return $this->belongsTo(File::class); } - public static function store(array $files) + public static function cascadeFileDeletion(File $file): void + { + self::whereFileId($file->id)->first()->delete(); + } + + public static function store(array $files): Collection { return DB::transaction(fn () => Collection::wrap($files) ->map(fn ($file) => self::upload($file))) ->values(); } - private static function upload($file): Resource + protected static function upload(UploadedFile $file): File { $upload = self::create(); $file = File::upload($upload, $file); $upload->file()->associate($file)->save(); - $file->load('createdBy.person', 'createdBy.avatar', 'type'); - return new Resource($file); + return $upload->file; } } diff --git a/src/Policies/File.php b/src/Policies/File.php index c9a5ceb..33bda6f 100644 --- a/src/Policies/File.php +++ b/src/Policies/File.php @@ -3,7 +3,6 @@ namespace LaravelEnso\Files\Policies; use Illuminate\Auth\Access\HandlesAuthorization; -use LaravelEnso\Files\Contracts\PublicFile; use LaravelEnso\Files\Models\File as Model; use LaravelEnso\Users\Models\User; @@ -13,18 +12,19 @@ class File public function before(User $user) { - if ($user->isAdmin() || $user->isSupervisor()) { + if ($user->isSuperior()) { return true; } } public function access(User $user, Model $file) { - return $this->ownsFile($user, $file) - || new $file->type->model() instanceof PublicFile; + return $file->is_public + || $this->ownsFile($user, $file) + || $file->type->isPublic(); } - public function destroy(User $user, Model $file) + public function manage(User $user, Model $file) { return $this->ownsFile($user, $file); } diff --git a/src/Policies/Upload.php b/src/Policies/Upload.php index 6d6c495..dbff4ca 100644 --- a/src/Policies/Upload.php +++ b/src/Policies/Upload.php @@ -12,7 +12,7 @@ class Upload public function before(User $user) { - if ($user->isAdmin() || $user->isSupervisor()) { + if ($user->isSuperior()) { return true; } } diff --git a/src/Services/FileValidator.php b/src/Services/FileValidator.php deleted file mode 100644 index 0ef903f..0000000 --- a/src/Services/FileValidator.php +++ /dev/null @@ -1,65 +0,0 @@ -validateFile() - ->validateExtension() - ->validateMimeType(); - } - - private function validateFile(): self - { - if (! $this->file->isValid()) { - throw File::uploadError($this->file->getClientOriginalName()); - } - - return $this; - } - - private function validateExtension(): self - { - $valid = new Collection($this->extensions); - - $extension = $this->file instanceof UploadedFile - ? $this->file->getClientOriginalExtension() - : $this->file->getExtension(); - - if ($valid->isNotEmpty() && ! $valid->contains($extension)) { - $extensions = implode(', ', $this->extensions); - throw FileException::invalidExtension($extension, $extensions); - } - - return $this; - } - - private function validateMimeType() - { - $valid = new Collection($this->mimeTypes); - - $mimeType = $this->file->getMimeType(); - - if ($valid->isNotEmpty() && ! $valid->contains($mimeType)) { - $mimeTypes = implode(', ', $this->mimeTypes); - throw FileException::invalidMimeType($mimeType, $mimeTypes); - } - - return $this; - } -} diff --git a/src/Services/Upload.php b/src/Services/Upload.php index 0595f12..17b6532 100644 --- a/src/Services/Upload.php +++ b/src/Services/Upload.php @@ -3,6 +3,7 @@ namespace LaravelEnso\Files\Services; use Illuminate\Http\UploadedFile; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Storage; @@ -13,13 +14,17 @@ use LaravelEnso\Files\Contracts\ResizesImages; use LaravelEnso\Files\Models\File; use LaravelEnso\Files\Models\Type; +use LaravelEnso\ImageTransformer\Services\ImageTransformer; class Upload { + private Type $type; + public function __construct( private Attachable $attachable, private UploadedFile $file ) { + $this->type = Type::for($this->attachable::class); } public function handle(): File @@ -48,6 +53,10 @@ private function validate(): self private function process(): self { + if (! $this->isSupportedImage()) { + return $this; + } + if ($this->attachable instanceof ResizesImages) { $processor = (new Process($this->file)) ->width($this->attachable->imageWidth()) @@ -71,11 +80,12 @@ private function upload(): File $folder = $this->folder(); $model = File::create([ - 'type_id' => Type::for($this->attachable::class)->id, + 'type_id' => $this->type->id, 'original_name' => $this->file->getClientOriginalName(), 'saved_name' => $this->file->hashName(), 'size' => $this->file->getSize(), 'mime_type' => $this->file->getMimeType(), + 'is_public' => $this->type->isPublic(), ]); $this->file->store($folder); @@ -95,4 +105,10 @@ private function folder() return $folder; } + + private function isSupportedImage(): bool + { + return Collection::wrap(ImageTransformer::SupportedMimeTypes) + ->contains($this->file->getMimeType()); + } } diff --git a/src/Services/Validate.php b/src/Services/Validate.php index 42b29c9..8bb450b 100644 --- a/src/Services/Validate.php +++ b/src/Services/Validate.php @@ -13,7 +13,7 @@ class Validate private ?array $mimeTypes; public function __construct( - protected File | UploadedFile $file + protected File|UploadedFile $file ) { $this->extensions = null; $this->mimeTypes = null; @@ -52,7 +52,9 @@ private function file(): self private function extension(): self { $valid = new Collection($this->extensions); - $extension = $this->file->extension(); + $extension = $this->file instanceof UploadedFile + ? $this->file->getClientOriginalExtension() + : $this->file->extension(); $shouldThrow = $valid->isNotEmpty() && $valid->doesntContain($extension); if ($shouldThrow) { @@ -62,7 +64,7 @@ private function extension(): self return $this; } - private function mimeType() + private function mimeType(): self { $valid = new Collection($this->mimeTypes); $mimeType = $this->file->getMimeType(); diff --git a/src/State/Types.php b/src/State/Types.php new file mode 100644 index 0000000..3adb3a6 --- /dev/null +++ b/src/State/Types.php @@ -0,0 +1,20 @@ +get()); + } +} diff --git a/src/Tables/Builders/Type.php b/src/Tables/Builders/Type.php new file mode 100644 index 0000000..0982e22 --- /dev/null +++ b/src/Tables/Builders/Type.php @@ -0,0 +1,25 @@ +string('attachable_type')->nullable()->change(); - $table->unsignedBigInteger('attachable_id')->nullable()->change(); - }); - } -} diff --git a/src/Upgrades/DropMorphModels.php b/src/Upgrades/DropMorphModels.php deleted file mode 100644 index 7a15a0f..0000000 --- a/src/Upgrades/DropMorphModels.php +++ /dev/null @@ -1,108 +0,0 @@ -needUpgrade()->isEmpty(); - } - - public function migrateTable(): void - { - $this->needUpgrade() - ->each(fn ($model, $morphKey) => $this->process($model, $morphKey)); - } - - private function process(string $model, string $morphKey) - { - $this->addForeignKey($model) - ->migrateData($model, $morphKey); - } - - private function addForeignKey(string $model): self - { - $table = $this->table($model); - $after = $this->afterColumn($table); - - Schema::table($table, function (Blueprint $table) use ($after) { - $table->unsignedBigInteger('file_id')->nullable()->after($after); - $table->foreign('file_id')->references('id')->on('files') - ->onUpdate('restrict')->onDelete('cascade'); - }); - - return $this; - } - - private function migrateData(string $model, string $morphKey) - { - $files = File::whereAttachableType($morphKey); - $type = Type::for($model); - $files->each(fn ($file) => $this->migrateFile($type, $file, $model)); - } - - private function migrateFile(Type $type, File $file, string $model) - { - $folder = Str::of($file->saved_name)->beforeLast('/'); - $savedName = Str::of($file->saved_name)->replace("{$folder}/", ''); - - $file->update([ - 'type_id' => $type->id, - 'saved_name' => $savedName, - ]); - - $record = $model::find($file->attachable_id); - - if ($record) { - $record->update(['file_id' => $file->id]); - } else { - Log::notice("File with id of {$file->id} is missing its morphed model"); - } - } - - private function afterColumn(string $table): string - { - return Collection::wrap(Schema::getColumnListing($table)) - ->filter(fn ($column) => Str::endsWith($column, '_id')) - ->last() ?? 'id'; - } - - private function needUpgrade(): Collection - { - $upgraded = fn ($model) => Table::hasColumn($this->table($model), 'file_id'); - - return $this->models()->reject($upgraded); - } - - private function table(string $model): string - { - return (new $model())->getTable(); - } - - private function models(): Collection - { - $upgrade = Config::get('enso.files.upgrade'); - - return Collection::wrap($upgrade) - ->filter(fn ($model) => class_exists($model)); - } -} diff --git a/src/Upgrades/FileIds.php b/src/Upgrades/FileIds.php deleted file mode 100644 index b999f4b..0000000 --- a/src/Upgrades/FileIds.php +++ /dev/null @@ -1,21 +0,0 @@ - $table->id()->change()); - } -} diff --git a/src/Upgrades/FileType.php b/src/Upgrades/FileType.php deleted file mode 100644 index 02d1bb5..0000000 --- a/src/Upgrades/FileType.php +++ /dev/null @@ -1,29 +0,0 @@ -unsignedBigInteger('type_id')->nullable()->after('id'); - $table->foreign('type_id')->references('id')->on('file_types'); - }); - } -} diff --git a/src/Upgrades/MoveFiles.php b/src/Upgrades/MoveFiles.php deleted file mode 100644 index bfbcb87..0000000 --- a/src/Upgrades/MoveFiles.php +++ /dev/null @@ -1,66 +0,0 @@ -notMoved()->doesntExist(); - } - - public function migrateData(): void - { - $this->notMoved() - ->pluck('attachable_type') - ->each(fn ($attachable) => $this->handle($attachable)); - } - - private function handle(string $attachable): void - { - $folder = Str::plural($attachable); - - if (! Storage::has($folder)) { - Storage::makeDirectory($folder); - } - - File::whereAttachableType($attachable) - ->each(fn ($file) => $this->move($file, $folder)); - - $this->replace($attachable, $folder); - } - - private function move(File $file, string $folder): void - { - if (Storage::exists($file->saved_name)) { - $location = Str::replace(self::ToMove, $folder, $file->saved_name); - Storage::move($file->saved_name, $location); - } - } - - private function replace(string $attachable, string $folder) - { - Collection::wrap(self::ToMove) - ->each(fn ($from) => $this->notMoved() - ->whereAttachableType($attachable) - ->where('saved_name', 'LIKE', "{$from}/%") - ->update(['saved_name' => DB::raw("REPLACE(saved_name, '{$from}', '{$folder}')")])); - } - - private function notMoved(): Builder - { - return Collection::wrap(self::ToMove) - ->reduce(fn ($files, $folder) => $files - ->orWhere('saved_name', 'LIKE', "{$folder}/%"), File::query()); - } -} diff --git a/src/Upgrades/Permissions.php b/src/Upgrades/Permissions.php deleted file mode 100644 index c361d89..0000000 --- a/src/Upgrades/Permissions.php +++ /dev/null @@ -1,20 +0,0 @@ - 'core.files.favorite', 'description' => 'Toggle file as favorite', 'is_default' => true], - ['name' => 'core.files.browse', 'description' => 'Browse file type', 'is_default' => true], - ['name' => 'core.files.recent', 'description' => 'Browse recent files', 'is_default' => true], - ['name' => 'core.files.favorites', 'description' => 'Browse favorites files', 'is_default' => true], - ['name' => 'core.files.sharedWithYou', 'description' => 'Browse files shared with user', 'is_default' => true], - ['name' => 'core.files.sharedByYou', 'description' => 'Browse files shared by user', 'is_default' => true], - ]; -} diff --git a/src/Upgrades/RenameFilePath.php b/src/Upgrades/RenameFilePath.php deleted file mode 100644 index 346569a..0000000 --- a/src/Upgrades/RenameFilePath.php +++ /dev/null @@ -1,28 +0,0 @@ - $table - ->renameColumn('path', 'saved_name')); - } -} diff --git a/src/Upgrades/TypeSeeder.php b/src/Upgrades/TypeSeeder.php deleted file mode 100644 index a28578d..0000000 --- a/src/Upgrades/TypeSeeder.php +++ /dev/null @@ -1,52 +0,0 @@ -exists(); - } - - public function migrateData(): void - { - $seeder = Seeder::class; - - Artisan::command("db:seed --class={$seeder}", ['--force' => true]); - } - - public function migratePostDataMigration(): void - { - if (! Storage::has('uploads')) { - Storage::makeDirectory('uploads'); - } - - $this->replace('uploads'); - - if (class_exists(Document::class)) { - if (! Storage::has('documents')) { - Storage::makeDirectory('documents'); - } - - $this->replace('documents'); - } - } - - private function replace(string $attachable) - { - File::whereAttachableType($attachable) - ->where('path', 'LIKE', 'files/%') - ->update(['path' => DB::raw("REPLACE(path, 'files', $attachable)")]); - } -} diff --git a/src/UploadServiceProvider.php b/src/UploadServiceProvider.php deleted file mode 100644 index 64fe7b4..0000000 --- a/src/UploadServiceProvider.php +++ /dev/null @@ -1,11 +0,0 @@ -model->file->upload($this->file); + $file = File::upload($this->model, $this->file); + $this->model->file()->associate($file)->save(); $this->assertNotNull($this->model->file); @@ -66,37 +71,39 @@ public function can_attach_file() /** @test */ public function cant_upload_file_with_invalid_extension() { - $file = UploadedFile::fake()->image('picture.jpg'); + $file = UploadedFile::fake()->image('image.jpg'); - $this->expectException(File::class); + $this->expectException(Exception::class); $this->expectExceptionMessage( - File::invalidExtension($file->getClientOriginalExtension(), 'png') + Exception::invalidExtension($file->getClientOriginalExtension(), 'png') ->getMessage() ); - $this->model->file->upload($file); + File::upload($this->model, $file); } /** @test */ public function cant_upload_file_with_invalid_mime_type() { - $file = UploadedFile::fake()->create('doc.png', 0, 'application/msword'); + $file = UploadedFile::fake()->create('doc.doc', 0, 'application/msword'); - $this->expectException(File::class); + $this->expectException(Exception::class); $this->expectExceptionMessage( - File::invalidMimeType($file->getMimeType(), 'image/png') + Exception::invalidMimeType($file->getMimeType(), 'image/png') ->getMessage() ); - $this->model->file->upload($file); + $this->model->extension = 'doc'; + File::upload($this->model, $file); } /** @test */ public function can_display_file_inline() { - $this->model->file->upload($this->file); + $file = File::upload($this->model, $this->file); + $this->model->file()->associate($file)->save(); $response = $this->model->file->inline($this->file->hashname()); @@ -106,17 +113,23 @@ public function can_display_file_inline() /** @test */ public function can_download_file() { - $this->model->file->upload($this->file); + $file = File::upload($this->model, $this->file); + $this->model->file()->associate($file)->save(); $response = $this->model->file->download(); $this->assertEquals(200, $response->getStatusCode()); } - private function createTestTable() + private function createTestTable(): self { Schema::create('attachable_models', function ($table) { $table->increments('id'); + + $table->bigInteger('file_id')->unsigned()->nullable(); + $table->foreign('file_id')->references('id')->on('files') + ->onUpdate('restrict')->onDelete('restrict'); + $table->timestamps(); }); @@ -129,8 +142,22 @@ private function cleanUp() } } -class AttachableModel extends Model implements Attachable +class AttachableModel extends Model implements Attachable, Extensions, MimeTypes { - protected $mimeTypes = ['image/png']; - protected $extensions = ['png']; + public string $extension = 'png'; + + public function file(): Relation + { + return $this->belongsTo(File::class); + } + + public function extensions(): array + { + return [$this->extension]; + } + + public function mimeTypes(): array + { + return ['image/png']; + } }