diff --git a/composer.json b/composer.json index 95c60940..cf815888 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ "laravel/ui": "^3.2.0", "league/flysystem-aws-s3-v3": "^1.0.29", "league/flysystem-cached-adapter": "^1.1.0", + "spatie/data-transfer-object": "^3.6", "spatie/laravel-backup": "^6.16.1", "spatie/laravel-query-builder": "^3.5", "spatie/laravel-queueable-action": "^2.12", diff --git a/composer.lock b/composer.lock index 0d255415..ce641e62 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": "3c897439f8ef41534a743d4a05020502", + "content-hash": "b97469c2ea6b8b1e89c432391ec6ffc2", "packages": [ { "name": "amphp/amp", @@ -7390,6 +7390,72 @@ ], "time": "2020-09-28T06:39:44+00:00" }, + { + "name": "spatie/data-transfer-object", + "version": "3.6.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/data-transfer-object.git", + "reference": "2dd4e4d5e758b5892e6219bc444a9f38ad77c5af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/data-transfer-object/zipball/2dd4e4d5e758b5892e6219bc444a9f38ad77c5af", + "reference": "2dd4e4d5e758b5892e6219bc444a9f38ad77c5af", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "illuminate/collections": "^8.36", + "jetbrains/phpstorm-attributes": "^1.0", + "larapack/dd": "^1.1", + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "phpstan/phpstan": "Take advantage of checkUninitializedProperties with \\Spatie\\DataTransferObject\\PHPstan\\PropertiesAreAlwaysInitializedExtension" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\DataTransferObject\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brent Roose", + "email": "brent@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Data transfer objects with batteries included", + "homepage": "https://github.com/spatie/data-transfer-object", + "keywords": [ + "data-transfer-object", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/data-transfer-object/issues", + "source": "https://github.com/spatie/data-transfer-object/tree/3.6.1" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2021-08-17T03:36:52+00:00" + }, { "name": "spatie/db-dumper", "version": "2.21.1", diff --git a/database/migrations/2019_08_15_171328_create_file_manager_folders.php b/database/migrations/2019_08_15_171328_create_file_manager_folders.php index 6c87a51a..b9ae422f 100644 --- a/database/migrations/2019_08_15_171328_create_file_manager_folders.php +++ b/database/migrations/2019_08_15_171328_create_file_manager_folders.php @@ -21,6 +21,8 @@ class CreateFileManagerFolders extends Migration $table->string('color')->nullable(); $table->longText('emoji')->nullable(); + $table->boolean('team_folder')->default(0); + $table->enum('author', ['user', 'member', 'visitor'])->default('user'); $table->uuid('author_id')->nullable(); diff --git a/database/migrations/2021_08_24_080638_create_team_folders_invitations_table.php b/database/migrations/2021_08_24_080638_create_team_folders_invitations_table.php new file mode 100644 index 00000000..88d7d368 --- /dev/null +++ b/database/migrations/2021_08_24_080638_create_team_folders_invitations_table.php @@ -0,0 +1,34 @@ +uuid('id')->primary(); + $table->uuid('folder_id'); + $table->text('email'); + $table->enum('status', ['pending', 'accepted', 'rejected'])->default('pending'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('invitations'); + } +} diff --git a/routes/api.php b/routes/api.php index 3f8d2bd8..1f87c5a0 100644 --- a/routes/api.php +++ b/routes/api.php @@ -11,6 +11,7 @@ use App\Users\Controllers\ForgotPasswordController; use Domain\Folders\Controllers\FavouriteController; use Domain\Plans\Controllers\ActivePlansController; use Domain\Folders\Controllers\CreateFolderController; +use Domain\Browsing\Controllers\BrowseFolderController; use Domain\Sharing\Controllers\ShareViaEmailController; use Domain\Items\Controllers\MoveFileOrFolderController; use Domain\Items\Controllers\DeleteFileOrFolderController; @@ -21,7 +22,6 @@ use Domain\Browsing\Controllers\BrowseLatestFilesController; use Domain\Browsing\Controllers\BrowseSharedItemsController; use Domain\Browsing\Controllers\BrowseTrashContentController; use Domain\Homepage\Controllers\SendContactMessageController; -use Domain\Browsing\Controllers\BrowseFolderController; use Domain\Folders\Controllers\NavigationFolderTreeController; use Domain\Browsing\Controllers\SearchFilesAndFoldersController; diff --git a/routes/share.php b/routes/share.php index 3704acac..eb02f9ae 100644 --- a/routes/share.php +++ b/routes/share.php @@ -6,11 +6,11 @@ use Domain\Files\Controllers\VisitorShowFileController; use Domain\Files\Controllers\VisitorUploadFileController; use Domain\Folders\Controllers\VisitorCreateFolderController; use Domain\Sharing\Controllers\WebCrawlerOpenGraphController; +use Domain\Browsing\Controllers\VisitorBrowseFolderController; use Domain\Items\Controllers\VisitorMoveFileOrFolderController; use Domain\Items\Controllers\VisitorDeleteFileOrFolderController; use Domain\Items\Controllers\VisitorRenameFileOrFolderController; use Domain\Sharing\Controllers\VisitorUnlockLockedShareController; -use Domain\Browsing\Controllers\VisitorBrowseFolderController; use Domain\Folders\Controllers\VisitorNavigationFolderTreeController; use Domain\Browsing\Controllers\VisitorSearchFilesAndFoldersController; diff --git a/routes/teams.php b/routes/teams.php new file mode 100644 index 00000000..2c64c477 --- /dev/null +++ b/routes/teams.php @@ -0,0 +1,5 @@ +mapFileRoutes(); + $this->mapTeamsRoutes(); + $this->mapWebRoutes(); } @@ -106,6 +108,13 @@ class RouteServiceProvider extends ServiceProvider ->group(base_path('routes/user.php')); } + protected function mapTeamsRoutes() + { + Route::prefix('api/teams') + ->middleware(['api', 'auth:sanctum']) + ->group(base_path('routes/teams.php')); + } + protected function mapSetupWizardApiRoutes() { Route::middleware(['setup-wizard']) diff --git a/src/App/Users/Models/User.php b/src/App/Users/Models/User.php index ad5fc0a6..ad0b1b80 100644 --- a/src/App/Users/Models/User.php +++ b/src/App/Users/Models/User.php @@ -24,7 +24,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Database\Eloquent\Relations\BelongsToMany; /** - * @property mixed id + * @property string id * @property Setting settings * @property string email * @property mixed favouriteFolders @@ -34,6 +34,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; * @method static forceCreate(array $array) * @method static where(string $string, string $string1, string $toDateString) * @method static create(array $array) + * @method static find(mixed $email) */ class User extends Authenticatable implements MustVerifyEmail { diff --git a/src/Domain/Browsing/Controllers/BrowseFolderController.php b/src/Domain/Browsing/Controllers/BrowseFolderController.php index eb86cdd2..36277ef6 100644 --- a/src/Domain/Browsing/Controllers/BrowseFolderController.php +++ b/src/Domain/Browsing/Controllers/BrowseFolderController.php @@ -4,7 +4,6 @@ namespace Domain\Browsing\Controllers; use Illuminate\Http\Request; use Domain\Files\Models\File; use Domain\Folders\Models\Folder; -use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; class BrowseFolderController diff --git a/src/Domain/Browsing/Controllers/BrowseTrashContentController.php b/src/Domain/Browsing/Controllers/BrowseTrashContentController.php index 4716708e..4d8d6073 100644 --- a/src/Domain/Browsing/Controllers/BrowseTrashContentController.php +++ b/src/Domain/Browsing/Controllers/BrowseTrashContentController.php @@ -14,7 +14,6 @@ class BrowseTrashContentController $requestedFolder = $root_id ? Folder::withTrashed()->findOrFail($root_id) : null; if ($root_id) { - // Get folders and files $folders = Folder::onlyTrashed() ->with('parent') diff --git a/src/Domain/Browsing/Controllers/VisitorBrowseFolderController.php b/src/Domain/Browsing/Controllers/VisitorBrowseFolderController.php index f25727d4..51d305ac 100644 --- a/src/Domain/Browsing/Controllers/VisitorBrowseFolderController.php +++ b/src/Domain/Browsing/Controllers/VisitorBrowseFolderController.php @@ -4,7 +4,6 @@ namespace Domain\Browsing\Controllers; use Domain\Files\Models\File; use Domain\Sharing\Models\Share; use Domain\Folders\Models\Folder; -use Illuminate\Support\Collection; use Domain\Sharing\Actions\ProtectShareRecordAction; use Domain\Sharing\Actions\VerifyAccessToItemAction; @@ -23,7 +22,6 @@ class VisitorBrowseFolderController string $id, Share $shared, ): array { - // Check ability to access protected share record ($this->protectShareRecord)($shared); diff --git a/src/Domain/Folders/Models/Folder.php b/src/Domain/Folders/Models/Folder.php index fa16ffe8..a39a0b18 100644 --- a/src/Domain/Folders/Models/Folder.php +++ b/src/Domain/Folders/Models/Folder.php @@ -20,6 +20,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; * @method static find(mixed $id) * @method static where(string $string, string $user_id) * @method static findOrFail(string $root_id) + * @method static create(array $array) * @property string id * @property string user_id * @property string parent_id @@ -31,6 +32,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; * @property string created_at * @property string updated_at * @property string deleted_at + * @property bool team_folder */ class Folder extends Model { diff --git a/src/Domain/Sharing/Actions/SendViaEmailAction.php b/src/Domain/Sharing/Actions/SendViaEmailAction.php index c147dc53..e9f4969e 100644 --- a/src/Domain/Sharing/Actions/SendViaEmailAction.php +++ b/src/Domain/Sharing/Actions/SendViaEmailAction.php @@ -3,7 +3,7 @@ namespace Domain\Sharing\Actions; use Spatie\QueueableAction\QueueableAction; use Illuminate\Support\Facades\Notification; -use Domain\Sharing\Notifications\SharedSendViaEmail; +use Domain\Teams\Notifications\SharedSendViaEmail; class SendViaEmailAction { diff --git a/src/Domain/Teams/Controllers/TeamFoldersController.php b/src/Domain/Teams/Controllers/TeamFoldersController.php new file mode 100644 index 00000000..3052aa71 --- /dev/null +++ b/src/Domain/Teams/Controllers/TeamFoldersController.php @@ -0,0 +1,41 @@ + $request->user()->id, + 'name' => $data->name, + 'team_folder' => 1, + ]); + + collect($data->members) + ->each(function ($email) use ($teamFolder) { + + // Create invitation + $invitation = TeamFoldersInvitation::create([ + 'folder_id' => $teamFolder->id, + 'email' => $email, + ]); + + // Invite user + Notification::route('mail', $email) + ->notify(new InvitationIntoTeamFolder($teamFolder, $invitation)); + }); + + return response($teamFolder, 201); + } +} diff --git a/src/Domain/Teams/DTO/CreateTeamFolderData.php b/src/Domain/Teams/DTO/CreateTeamFolderData.php new file mode 100644 index 00000000..01ce0722 --- /dev/null +++ b/src/Domain/Teams/DTO/CreateTeamFolderData.php @@ -0,0 +1,18 @@ + $request->input('name'), + 'members' => $request->input('members'), + ]); + } +} diff --git a/src/Domain/Teams/Models/TeamFoldersInvitation.php b/src/Domain/Teams/Models/TeamFoldersInvitation.php new file mode 100644 index 00000000..5a8f58be --- /dev/null +++ b/src/Domain/Teams/Models/TeamFoldersInvitation.php @@ -0,0 +1,39 @@ + 'string', + ]; + + protected $guarded = ['id']; + + public $incrementing = false; + + protected $keyType = 'string'; + + protected static function boot() + { + parent::boot(); + + static::creating(function ($model) { + $model->id = Str::uuid(); + }); + } +} diff --git a/src/Domain/Teams/Notifications/InvitationIntoTeamFolder.php b/src/Domain/Teams/Notifications/InvitationIntoTeamFolder.php new file mode 100644 index 00000000..eec4b7df --- /dev/null +++ b/src/Domain/Teams/Notifications/InvitationIntoTeamFolder.php @@ -0,0 +1,54 @@ +invitation->email); + + if ($user) { + return (new MailMessage) + ->subject("You are invited to collaboration with team folder in $appTitle") + ->greeting('Hello!') + ->line('You are invited to collaboration with team folder') + ->action('Join into Team Folder', url('/team-folder-invitation', ['id' => $this->invitation->id])) + ->salutation("Regards, $appTitle"); + } + + return (new MailMessage) + ->subject("You are invited to collaboration with team folder in $appTitle") + ->greeting('Hello!') + ->line('You are invited to collaboration with team folder. But at first, you have to create an account to proceed into team folder.') + ->action('Join & Create an Account', url('/team-folder-invitation', ['id' => $this->invitation->id])) + ->salutation("Regards, $appTitle"); + } +} diff --git a/tests/Domain/Sharing/UserShareTest.php b/tests/Domain/Sharing/UserShareTest.php index 34a0682c..e046b478 100644 --- a/tests/Domain/Sharing/UserShareTest.php +++ b/tests/Domain/Sharing/UserShareTest.php @@ -7,7 +7,7 @@ use Laravel\Sanctum\Sanctum; use Domain\Files\Models\File; use Domain\Folders\Models\Folder; use Illuminate\Support\Facades\Notification; -use Domain\Sharing\Notifications\SharedSendViaEmail; +use Domain\Teams\Notifications\SharedSendViaEmail; class UserShareTest extends TestCase { diff --git a/tests/Domain/Teams/TeamsTest.php b/tests/Domain/Teams/TeamsTest.php index f7991e4e..978ac1f7 100644 --- a/tests/Domain/Teams/TeamsTest.php +++ b/tests/Domain/Teams/TeamsTest.php @@ -1,19 +1,54 @@ create([ + 'email' => 'john@internal.com', + ]); + $user = User::factory() + ->create(); + + $this + ->actingAs($user) + ->post('/api/teams/team-folders', [ + 'name' => 'Company Project', + 'members' => [ + 'john@internal.com', + 'jane@external.com', + ], + ]) + ->assertCreated() + ->assertJsonFragment([ + 'name' => 'Company Project', + ]); + + $this + ->assertDatabaseHas('folders', [ + 'name' => 'Company Project', + 'team_folder' => 1, + ]) + ->assertDatabaseHas('team_folders_invitations', [ + 'email' => 'john@internal.com', + ]) + ->assertDatabaseHas('team_folders_invitations', [ + 'email' => 'jane@external.com', + ]); + + Notification::assertTimesSent(2, InvitationIntoTeamFolder::class); } /** @@ -21,7 +56,6 @@ class TeamsTest extends TestCase */ public function it_convert_team_folder() { - } /** @@ -29,7 +63,6 @@ class TeamsTest extends TestCase */ public function it_add_member_into_team_folder() { - } /** @@ -37,7 +70,6 @@ class TeamsTest extends TestCase */ public function member_accept_team_folder_invite() { - } /** @@ -45,7 +77,6 @@ class TeamsTest extends TestCase */ public function member_reject_team_folder_invite() { - } /** @@ -53,7 +84,6 @@ class TeamsTest extends TestCase */ public function it_remove_member_from_team_folder() { - } /** @@ -61,7 +91,6 @@ class TeamsTest extends TestCase */ public function it_dissolve_team_folder() { - } /** @@ -69,7 +98,6 @@ class TeamsTest extends TestCase */ public function it_move_items_into_team_folder() { - } /** @@ -77,7 +105,6 @@ class TeamsTest extends TestCase */ public function it_get_all_team_folders() { - } /** @@ -85,6 +112,5 @@ class TeamsTest extends TestCase */ public function it_get_team_folders_shared_with_another_user() { - } -} \ No newline at end of file +}