diff --git a/public/mix-manifest.json b/public/mix-manifest.json index e3bb892e..c5567c7b 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -110,5 +110,30 @@ "/chunks/shared/authenticate.f99485cf74326346a8b1.hot-update.js": "/chunks/shared/authenticate.f99485cf74326346a8b1.hot-update.js", "/chunks/shared.56ccba53201e5195af28.hot-update.js": "/chunks/shared.56ccba53201e5195af28.hot-update.js", "/chunks/shared.4d20e6443aee96d57755.hot-update.js": "/chunks/shared.4d20e6443aee96d57755.hot-update.js", - "/chunks/shared.231c37d4d932d493e5ba.hot-update.js": "/chunks/shared.231c37d4d932d493e5ba.hot-update.js" + "/chunks/shared.231c37d4d932d493e5ba.hot-update.js": "/chunks/shared.231c37d4d932d493e5ba.hot-update.js", + "/js/main.88844afbc713230b2ac6.hot-update.js": "/js/main.88844afbc713230b2ac6.hot-update.js", + "/js/main.f20ab3eb1934b88b78bf.hot-update.js": "/js/main.f20ab3eb1934b88b78bf.hot-update.js", + "/chunks/admin~chunks/app-language~chunks/dashboard~chunks/files~chunks/invoices~chunks/my-shared-item~9b68162c.js": "/chunks/admin~chunks/app-language~chunks/dashboard~chunks/files~chunks/invoices~chunks/my-shared-item~9b68162c.js?id=556460202edd9a6086fe", + "/chunks/admin~chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunks/settin~673d1ac3.js": "/chunks/admin~chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunks/settin~673d1ac3.js?id=c01cde1da36dcd859bed", + "/chunks/admin~chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunks/settin~97130d1f.js": "/chunks/admin~chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunks/settin~97130d1f.js?id=23023a327ee0b32a7f1f", + "/chunks/admin~chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunks/shared~1bec6fe4.js": "/chunks/admin~chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunks/shared~1bec6fe4.js?id=2483e956635c6b940795", + "/chunks/app-language~chunks/dashboard~chunks/files~chunks/invoices~chunks/my-shared-items~chunks/page~7dbb6a42.js": "/chunks/app-language~chunks/dashboard~chunks/files~chunks/invoices~chunks/my-shared-items~chunks/page~7dbb6a42.js?id=edf36a5ddf8f6705f7e4", + "/chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunks/shared-with-me~chun~fd99312c.js": "/chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunks/shared-with-me~chun~fd99312c.js?id=42944aee3313456a7e1c", + "/chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunks/shared~chunks/share~c7960950.js": "/chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunks/shared~chunks/share~c7960950.js?id=373ceb601d388f0872d3", + "/chunks/files~chunks/my-shared-items~chunks/recent-uploads~chunks/settings-subscription~chunks/shared~f3cd1a63.js": "/chunks/files~chunks/my-shared-items~chunks/recent-uploads~chunks/settings-subscription~chunks/shared~f3cd1a63.js?id=93200e8f38e44f4b59f0", + "/chunks/files~chunks/my-shared-items~chunks/recent-uploads~chunks/shared-with-me~chunks/shared/files~~34b5eb22.js": "/chunks/files~chunks/my-shared-items~chunks/recent-uploads~chunks/shared-with-me~chunks/shared/files~~34b5eb22.js?id=eb119943f935f9ec4c66", + "/chunks/files~chunks/my-shared-items~chunks/recent-uploads~chunks/shared-with-me~chunks/shared/files~~bf3ddedc.js": "/chunks/files~chunks/my-shared-items~chunks/recent-uploads~chunks/shared-with-me~chunks/shared/files~~bf3ddedc.js?id=d764385b104ca8009e36", + "/chunks/files~chunks/shared-with-me~chunks/shared/files~chunks/team-folders.js": "/chunks/files~chunks/shared-with-me~chunks/shared/files~chunks/team-folders.js?id=fbf0381594bdcd60f933", + "/chunks/shared-with-me.js": "/chunks/shared-with-me.js?id=8e2cbd8ee04cc259c3ef", + "/vendors~chunks/admin~chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunk~40ccbae3.js": "/vendors~chunks/admin~chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunk~40ccbae3.js?id=37ce1e1ab9968c100712", + "/js/main.9d081c43dda89cccca63.hot-update.js": "/js/main.9d081c43dda89cccca63.hot-update.js", + "/chunks/shared-with-me.9d081c43dda89cccca63.hot-update.js": "/chunks/shared-with-me.9d081c43dda89cccca63.hot-update.js", + "/js/main.15713ba41c25f6bd5f72.hot-update.js": "/js/main.15713ba41c25f6bd5f72.hot-update.js", + "/chunks/shared-with-me.15713ba41c25f6bd5f72.hot-update.js": "/chunks/shared-with-me.15713ba41c25f6bd5f72.hot-update.js", + "/js/main.028cd308790f8e3e1d76.hot-update.js": "/js/main.028cd308790f8e3e1d76.hot-update.js", + "/chunks/shared-with-me.48ff296470c6c8d9c8e1.hot-update.js": "/chunks/shared-with-me.48ff296470c6c8d9c8e1.hot-update.js", + "/chunks/shared-with-me.45f55a938be68c52688f.hot-update.js": "/chunks/shared-with-me.45f55a938be68c52688f.hot-update.js", + "/chunks/shared-with-me.6eea1ed760d886c08ef8.hot-update.js": "/chunks/shared-with-me.6eea1ed760d886c08ef8.hot-update.js", + "/js/main.b938f61eef37f98d19fe.hot-update.js": "/js/main.b938f61eef37f98d19fe.hot-update.js", + "/js/main.a9db17282c61afba01bd.hot-update.js": "/js/main.a9db17282c61afba01bd.hot-update.js" } diff --git a/resources/js/helpers/functionHelpers.js b/resources/js/helpers/functionHelpers.js index 1d1591a3..cdf89a73 100644 --- a/resources/js/helpers/functionHelpers.js +++ b/resources/js/helpers/functionHelpers.js @@ -212,6 +212,7 @@ const FunctionHelpers = { 'Public': this.$t('Files'), 'Files': this.$t('Files'), 'TeamFolders': this.$t('Team Folders'), + 'SharedWithMe': this.$t('Shared With Me'), }[this.$route.name] } } @@ -221,6 +222,7 @@ const FunctionHelpers = { let locations = { 'Public': {name: 'Public', params: {token: this.$route.params.token, id: id}}, 'TeamFolders': {name: 'TeamFolders', params: {id: id}}, + 'SharedWithMe': {name: 'SharedWithMe', params: {id: id}}, 'MySharedItems': {name: 'Files', params: {id: id}}, 'Trash': {name: 'Trash', params: {id: id}}, 'Files': {name: 'Files', params: {id: id}}, diff --git a/resources/js/routes/routesFile.js b/resources/js/routes/routesFile.js index 24a2744c..40d2f5aa 100644 --- a/resources/js/routes/routesFile.js +++ b/resources/js/routes/routesFile.js @@ -54,7 +54,7 @@ const routesFile = [ name: 'SharedWithMe', path: '/platform/shared-with-me/:id?', component: () => - import(/* webpackChunkName: "chunks/shared-with-me" */ '../views/FileView/Trash'), + import(/* webpackChunkName: "chunks/shared-with-me" */ '../views/FileView/SharedWithMe'), meta: { requiresAuth: true }, diff --git a/resources/js/store/modules/teams.js b/resources/js/store/modules/teams.js index 6531052e..c449203a 100644 --- a/resources/js/store/modules/teams.js +++ b/resources/js/store/modules/teams.js @@ -48,6 +48,46 @@ const actions = { } }) }, + getSharedWithMeFolder: ({commit, getters}, id) => { + commit('LOADING_STATE', {loading: true, data: []}) + + if (typeof id === 'undefined') { + commit('SET_CURRENT_TEAM_FOLDER', null) + } + + axios + .get(`${getters.api}/teams/shared-with-me/${id}/${getters.sorting.URI}`) + .then(response => { + let folders = response.data.folders.data + let files = response.data.files.data + + commit('LOADING_STATE', {loading: false, data: folders.concat(files)}) + commit('SET_CURRENT_FOLDER', response.data.root) + + if (! getters.currentTeamFolder || getters.currentTeamFolder.data.id !== response.data.teamFolder.data.id) { + commit('SET_CURRENT_TEAM_FOLDER', response.data.teamFolder) + } + + events.$emit('scrollTop') + }) + .catch(error => { + + // Redirect if unauthenticated + if ([401, 403].includes(error.response.status)) { + + commit('SET_AUTHORIZED', false) + router.push({name: 'SignIn'}) + + } else { + + // Show error message + events.$emit('alert:open', { + title: i18n.t('popup_error.title'), + message: i18n.t('popup_error.message'), + }) + } + }) + }, } const mutations = { diff --git a/resources/js/views/FileView/SharedWithMe.vue b/resources/js/views/FileView/SharedWithMe.vue new file mode 100644 index 00000000..53ab177d --- /dev/null +++ b/resources/js/views/FileView/SharedWithMe.vue @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ $t('actions.search') }} + + + {{ filterLocationTitle }} + + + {{ $t('mobile.create') }} + + + {{ $t('context_menu.select') }} + + + {{ $t('preview_sorting.preview_sorting_button') }} + + + + + + + + {{ $t('Nothing shared with you') }} + + + {{ $t('All items that are shared with you will be visible here.') }} + + + {{ $t('Create your Team Folder') }} + + + + + + {{ $t('empty_page.title') }} + + + {{ $t('empty_page.description') }} + + + {{ $t('empty_page.call_to_action') }} + + + + + + + + diff --git a/src/App/Console/Commands/SetupDevEnvironment.php b/src/App/Console/Commands/SetupDevEnvironment.php index 6f00bfba..15eb3eec 100644 --- a/src/App/Console/Commands/SetupDevEnvironment.php +++ b/src/App/Console/Commands/SetupDevEnvironment.php @@ -1,4 +1,5 @@ setUpFaker(); } @@ -71,6 +73,7 @@ class SetupDevEnvironment extends Command $this->info('Creating default demo content...'); $this->create_admin_default_content(); $this->create_team_folders_content(); + $this->create_share_with_me_team_folders_content(); $this->create_share_records(); $this->info('Clearing application cache...'); @@ -125,26 +128,32 @@ class SetupDevEnvironment extends Command collect([ [ 'avatar' => 'avatar-02.png', + 'email' => 'alice@vuefilemanager.com', ], [ 'avatar' => 'avatar-03.png', + 'email' => $this->faker->email, ], [ 'avatar' => 'avatar-04.png', + 'email' => $this->faker->email, ], [ 'avatar' => 'avatar-05.png', + 'email' => $this->faker->email, ], [ 'avatar' => 'avatar-06.png', + 'email' => $this->faker->email, ], [ 'avatar' => 'avatar-07.png', + 'email' => $this->faker->email, ], ])->each(function ($user) { $newbie = User::forceCreate([ 'role' => 'user', - 'email' => $this->faker->email, + 'email' => $user['email'], 'password' => bcrypt('vuefilemanager'), 'email_verified_at' => now(), ]); @@ -756,7 +765,7 @@ class SetupDevEnvironment extends Command collect([$members[0]->id, $members[1]->id]) ->each( - fn ($id) => DB::table('team_folder_members') + fn($id) => DB::table('team_folder_members') ->insert([ 'parent_id' => $companyProjectFolder->id, 'user_id' => $id, @@ -766,7 +775,7 @@ class SetupDevEnvironment extends Command collect([$members[2]->id, $members[3]->id]) ->each( - fn ($id) => DB::table('team_folder_members') + fn($id) => DB::table('team_folder_members') ->insert([ 'parent_id' => $financeDocumentsFolder->id, 'user_id' => $id, @@ -777,7 +786,7 @@ class SetupDevEnvironment extends Command // Create invitations collect([$members[4], $members[5]]) ->each( - fn ($user) => TeamFolderInvitation::factory() + fn($user) => TeamFolderInvitation::factory() ->create([ 'email' => $user->email, 'parent_id' => $companyProjectFolder->id, @@ -787,6 +796,96 @@ class SetupDevEnvironment extends Command ); } + public function create_share_with_me_team_folders_content(): void + { + $member = User::whereEmail('howdy@hi5ve.digital') + ->first(); + + $owner = User::whereEmail('alice@vuefilemanager.com') + ->first(); + + $folder = Folder::factory() + ->create([ + 'user_id' => $owner->id, + 'team_folder' => true, + 'name' => "Alice's Project Files", + ]); + + $memes = Folder::factory() + ->create([ + 'user_id' => $owner->id, + 'parent_id' => $folder->id, + 'name' => '9 Gag', + ]); + + $hug = Folder::factory() + ->create([ + 'user_id' => $owner->id, + 'parent_id' => $folder->id, + 'name' => 'Digital Hug', + ]); + + DB::table('team_folder_members') + ->insert([ + 'parent_id' => $folder->id, + 'user_id' => $member->id, + 'permission' => 'can-edit', + ]); + + // Get meme gallery + collect([ + 'Sofishticated.jpg', + 'whaaaaat.jpg', + 'You Are My Sunshine.jpg', + ]) + ->each(function ($file) use ($owner, $memes) { + $basename = Str::random(12) . '-' . $file; + + // Copy file into app storage + Storage::putFileAs("files/$owner->id", storage_path("demo/images/memes/$file"), $basename, 'private'); + Storage::putFileAs("files/$owner->id", storage_path("demo/images/memes/thumbnail-$file"), "thumbnail-$basename", 'private'); + + // Create file record + File::create([ + 'parent_id' => $memes->id, + 'user_id' => $owner->id, + 'name' => $file, + 'basename' => $basename, + 'type' => 'image', + 'author' => 'user', + 'mimetype' => 'jpg', + 'filesize' => rand(1000000, 4000000), + 'thumbnail' => "thumbnail-$basename", + 'created_at' => now()->subMinutes(rand(1, 5)), + ]); + }); + collect([ + 'Eggcited bro.jpg', + 'Get a Rest.jpg', + ]) + ->each(function ($file) use ($owner, $hug) { + $basename = Str::random(12) . '-' . $file; + + // Copy file into app storage + Storage::putFileAs("files/$owner->id", storage_path("demo/images/memes/$file"), $basename, 'private'); + Storage::putFileAs("files/$owner->id", storage_path("demo/images/memes/thumbnail-$file"), "thumbnail-$basename", 'private'); + + // Create file record + File::create([ + 'parent_id' => $hug->id, + 'user_id' => $owner->id, + 'name' => $file, + 'basename' => $basename, + 'type' => 'image', + 'author' => 'user', + 'mimetype' => 'jpg', + 'filesize' => rand(1000000, 4000000), + 'thumbnail' => "thumbnail-$basename", + 'created_at' => now()->subMinutes(rand(1, 5)), + ]); + }); + } + private function create_share_records(): void { $user = User::whereEmail('howdy@hi5ve.digital') diff --git a/src/Domain/Files/Controllers/FileAccess/GetFileController.php b/src/Domain/Files/Controllers/FileAccess/GetFileController.php index bec2533f..af22eb5f 100644 --- a/src/Domain/Files/Controllers/FileAccess/GetFileController.php +++ b/src/Domain/Files/Controllers/FileAccess/GetFileController.php @@ -1,9 +1,8 @@ where('user_id', Auth::id()) ->where('basename', $filename) ->firstOrFail(); + if (! Gate::any(['can-edit', 'can-visit'], [$file, null])) { + abort(403, 'Access Denied'); + } + // Store user download size ($this->recordDownload)( file_size: (int) $file->getRawOriginal('filesize'), - user_id: Auth::id(), + user_id: $file->user_id, ); - return ($this->downloadFile)($file, Auth::id()); + return ($this->downloadFile)($file, $file->user_id); } } diff --git a/src/Domain/Files/Controllers/FileAccess/GetThumbnailController.php b/src/Domain/Files/Controllers/FileAccess/GetThumbnailController.php index 2e0bf69e..c8269954 100644 --- a/src/Domain/Files/Controllers/FileAccess/GetThumbnailController.php +++ b/src/Domain/Files/Controllers/FileAccess/GetThumbnailController.php @@ -1,9 +1,9 @@ whereUserId(Auth::id()) - ->whereThumbnail($filename) + ->where('thumbnail', $filename) ->firstOrFail(); - return ($this->downloadThumbnail)($file, Auth::id()); + if (! Gate::any(['can-edit', 'can-visit'], [$file, null])) { + abort(403, 'Access Denied'); + } + + return ($this->downloadThumbnail)($file, $file->user_id); } } diff --git a/src/Domain/Teams/Controllers/BrowseSharedWithMeController.php b/src/Domain/Teams/Controllers/BrowseSharedWithMeController.php index f182b242..eaad9a90 100644 --- a/src/Domain/Teams/Controllers/BrowseSharedWithMeController.php +++ b/src/Domain/Teams/Controllers/BrowseSharedWithMeController.php @@ -1,6 +1,10 @@ where('id', $id) + if ($id) { + $folders = Folder::with(['parent:id,name']) + ->where('parent_id', $id) ->sortable() ->get(); - $files = File::with($relations) + $files = File::with(['parent:id,name']) ->where('parent_id', $id) ->sortable() ->get(); } - if (! $rootId) { - $folderIds = DB::table('team_folder_members') + if (!$id) { + $sharedFolderIds = DB::table('team_folder_members') ->where('user_id', Auth::id()) ->pluck('parent_id'); - $folders = Folder::with($relations) - ->whereIn('id', $folderIds) + $folders = Folder::whereIn('id', $sharedFolderIds) ->sortable() ->get(); - - $files = []; } - // Collect folders and files to single array return [ - 'content' => collect([$folders, $files])->collapse(), - 'folder' => $requestedFolder, + 'root' => $id ? new FolderResource(Folder::findOrFail($id)) : null, + 'folders' => new FolderCollection($folders), + 'files' => isset($files) ? new FilesCollection($files) : new FilesCollection([]), + 'teamFolder' => $id ? new FolderResource(Folder::findOrFail($id)->getLatestParent()) : null, ]; } } diff --git a/tests/Domain/Files/ContentAccessTest.php b/tests/Domain/Files/ContentAccessTest.php index 2277bf94..cc99f1d7 100644 --- a/tests/Domain/Files/ContentAccessTest.php +++ b/tests/Domain/Files/ContentAccessTest.php @@ -117,10 +117,9 @@ class ContentAccessTest extends TestCase 'name' => 'fake-thumbnail.jpg', ]); - Sanctum::actingAs($users[1]); - - $this->get("thumbnail/$thumbnail->name") - ->assertNotFound(); + $this->actingAs($users[1]) + ->get("thumbnail/$thumbnail->name") + ->assertForbidden(); } /** @@ -144,10 +143,9 @@ class ContentAccessTest extends TestCase 'name' => 'fake-file.pdf', ]); - Sanctum::actingAs($users[1]); - - $this->get("file/$file->name") - ->assertStatus(404); + $this->actingAs($users[1]) + ->get("file/$file->name") + ->assertForbidden(); } /**
+ {{ $t('All items that are shared with you will be visible here.') }} +
+ {{ $t('empty_page.description') }} +