mirror of
https://github.com/VueFileManager/vuefilemanager.git
synced 2026-04-18 16:22:14 +00:00
handle team invitation for non registered user
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=base64:47yorkyoH3qCrKKO4eG6LpZUogoTC51qey5vYq/O3AM=
|
||||
APP_KEY=base64:XP4FSfZLrj3n2MbhbOVWp4ldCbU0Ew+bhiEpHyOpxVw=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
APP_DEMO=false
|
||||
|
||||
@@ -95,6 +95,10 @@ return [
|
||||
'name' => 'allowed_adsense',
|
||||
'value' => 0,
|
||||
],
|
||||
[
|
||||
'name' => 'allowed_recaptcha',
|
||||
'value' => 0,
|
||||
],
|
||||
],
|
||||
'extended' => [
|
||||
[
|
||||
@@ -177,6 +181,10 @@ return [
|
||||
'name' => 'allowed_adsense',
|
||||
'value' => 0,
|
||||
],
|
||||
[
|
||||
'name' => 'allowed_recaptcha',
|
||||
'value' => 0,
|
||||
],
|
||||
|
||||
// Subscription
|
||||
[
|
||||
|
||||
@@ -20,7 +20,7 @@ class CreateTeamFolderInvitationsTable extends Migration
|
||||
$table->text('email');
|
||||
$table->string('color')->nullable();
|
||||
$table->enum('permission', ['can-edit', 'can-view', 'can-view-and-download']);
|
||||
$table->enum('status', ['pending', 'accepted', 'rejected'])->default('pending');
|
||||
$table->enum('status', ['pending', 'accepted', 'waiting-for-registration', 'rejected'])->default('pending');
|
||||
$table->timestamps();
|
||||
$table->charset = 'utf8mb4';
|
||||
$table->collation = 'utf8mb4_unicode_ci';
|
||||
|
||||
@@ -6,22 +6,37 @@
|
||||
v-if="invitation"
|
||||
:title="$t('Invitation To Join Team Folder')"
|
||||
:description="
|
||||
$t('{name} invite you to join with his team into shared team folder', {
|
||||
$t('Jane invite you to join with his team into shared team folder', {
|
||||
name: invitation.data.relationships.inviter.data.attributes.name,
|
||||
})
|
||||
"
|
||||
>
|
||||
<div class="relative mx-auto mb-10 w-24 text-center">
|
||||
<VueFolderTeamIcon class="inline-block w-28" />
|
||||
<MemberAvatar :member="invitation.data.relationships.inviter" class="absolute -bottom-2.5 -right-6" :is-border="true" :size="38" />
|
||||
<MemberAvatar
|
||||
:member="invitation.data.relationships.inviter"
|
||||
class="absolute -bottom-2.5 -right-6"
|
||||
:is-border="true"
|
||||
:size="38"
|
||||
/>
|
||||
</div>
|
||||
</Headline>
|
||||
|
||||
<p
|
||||
v-if="invitation"
|
||||
class="mx-auto mb-4 max-w-md text-sm text-gray-500"
|
||||
v-html="
|
||||
$t('Register account with your email peterpapp@makingcg.com and get access to this Team Folder.', {
|
||||
email: invitation.data.attributes.email,
|
||||
})
|
||||
"
|
||||
></p>
|
||||
|
||||
<AuthButton
|
||||
@click.native="acceptInvitation"
|
||||
class="mb-12 w-full justify-center md:w-min"
|
||||
icon="chevron-right"
|
||||
:text="$t('Accept Invitation')"
|
||||
:text="acceptButton"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
/>
|
||||
@@ -37,10 +52,17 @@
|
||||
|
||||
<!--Accepted invitation screen-->
|
||||
<AuthContent v-if="invitation" name="accepted" :visible="false">
|
||||
<Headline :title="$t('You are successfully joined')" :description="$t('You can now proceed to your account and participate in team folder')" />
|
||||
<Headline
|
||||
:title="$t('You are successfully joined')"
|
||||
:description="$t('You can now proceed to your account and participate in team folder')"
|
||||
/>
|
||||
|
||||
<router-link replace v-if="!config.isAuthenticated" :to="{ name: 'SignIn' }">
|
||||
<AuthButton class="mb-12 w-full justify-center md:w-min" icon="chevron-right" :text="$t('Proceed to your account')" />
|
||||
<AuthButton
|
||||
class="mb-12 w-full justify-center md:w-min"
|
||||
icon="chevron-right"
|
||||
:text="$t('Proceed to your account')"
|
||||
/>
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
@@ -51,29 +73,47 @@
|
||||
params: { id: invitation.data.attributes.parent_id },
|
||||
}"
|
||||
>
|
||||
<AuthButton class="mb-12 w-full justify-center md:w-min" icon="chevron-right" :text="$t('Go to Team Folder')" />
|
||||
<AuthButton
|
||||
class="mb-12 w-full justify-center md:w-min"
|
||||
icon="chevron-right"
|
||||
:text="$t('Go to Team Folder')"
|
||||
/>
|
||||
</router-link>
|
||||
</AuthContent>
|
||||
|
||||
<!--Denied invitation screen-->
|
||||
<AuthContent name="denied" :visible="false">
|
||||
<Headline :title="$t('You are successfully denied invitation')" :description="$t('You can now proceed to your account')" />
|
||||
<Headline
|
||||
:title="$t('You are successfully denied invitation')"
|
||||
:description="$t('You can now proceed to your account')"
|
||||
/>
|
||||
|
||||
<router-link :to="{ name: 'SignIn' }">
|
||||
<AuthButton class="mb-12 w-full justify-center md:w-min" icon="chevron-right" :text="$t('Proceed to your account')" />
|
||||
<AuthButton
|
||||
class="mb-12 w-full justify-center md:w-min"
|
||||
icon="chevron-right"
|
||||
:text="$t('Proceed to your account')"
|
||||
/>
|
||||
</router-link>
|
||||
</AuthContent>
|
||||
|
||||
<!--Used or Expired invitation screen-->
|
||||
<AuthContent name="expired" :visible="false">
|
||||
<Headline :title="$t('Your invitation has been used')" :description="$t('We are sorry but this invitation was used previously')" />
|
||||
<Headline
|
||||
:title="$t('Your invitation has been used')"
|
||||
:description="$t('We are sorry but this invitation was used previously')"
|
||||
/>
|
||||
|
||||
<router-link replace v-if="!config.isAuthenticated" :to="{ name: 'SignIn' }">
|
||||
<AuthButton class="mb-12 w-full justify-center md:w-min" icon="chevron-right" :text="$t('Log In')" />
|
||||
</router-link>
|
||||
|
||||
<router-link replace v-if="config.isAuthenticated" :to="{ name: 'SharedWithMe' }">
|
||||
<AuthButton class="mb-12 w-full justify-center md:w-min" icon="chevron-right" :text="$t('Go to your shared folders')" />
|
||||
<AuthButton
|
||||
class="mb-12 w-full justify-center md:w-min"
|
||||
icon="chevron-right"
|
||||
:text="$t('Go to your shared folders')"
|
||||
/>
|
||||
</router-link>
|
||||
</AuthContent>
|
||||
</AuthContentWrapper>
|
||||
@@ -106,6 +146,11 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
acceptButton() {
|
||||
return this.invitation && this.invitation.data.attributes.isExistedUser
|
||||
? this.$t('Accept Invitation')
|
||||
: this.$t('Accept and Register Account')
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -121,7 +166,12 @@ export default {
|
||||
axios
|
||||
.put(`/api/teams/invitations/${this.$router.currentRoute.params.id}`)
|
||||
.then(() => {
|
||||
|
||||
if (this.invitation.data.attributes.isExistedUser) {
|
||||
this.goToAuthPage('accepted')
|
||||
} else {
|
||||
this.$router.push({name: 'SignUp'})
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.$isSomethingWrong()
|
||||
|
||||
@@ -1090,6 +1090,10 @@ class SetupDevEnvironment extends Command
|
||||
'name' => 'subscription_type',
|
||||
'value' => 'fixed',
|
||||
],
|
||||
[
|
||||
'name' => 'allowed_recaptcha',
|
||||
'value' => 0,
|
||||
],
|
||||
])->each(function ($col) {
|
||||
Setting::updateOrCreate([
|
||||
'name' => $col['name'],
|
||||
|
||||
@@ -187,6 +187,10 @@ class SetupProdEnvironment extends Command
|
||||
'name' => 'billing_vat_number',
|
||||
'value' => null,
|
||||
],
|
||||
[
|
||||
'name' => 'allowed_recaptcha',
|
||||
'value' => 0,
|
||||
],
|
||||
])->each(function ($col) {
|
||||
Setting::forceCreate([
|
||||
'name' => $col['name'],
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Users\Actions;
|
||||
|
||||
use App\Users\Models\User;
|
||||
use App\Users\DTO\CreateUserData;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Domain\Teams\Models\TeamFolderInvitation;
|
||||
use Domain\Teams\Models\TeamFolderMember;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
|
||||
class CreateNewUserAction extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected AutoSubscribeForMeteredBillingAction $autoSubscribeForMeteredBilling,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Validate and create a new user.
|
||||
@@ -39,13 +41,27 @@ class CreateNewUserAction extends Controller
|
||||
'avatar' => $data->avatar,
|
||||
]);
|
||||
|
||||
// Join to previously accepted team folder invitations
|
||||
TeamFolderInvitation::where('email', $user->email)
|
||||
->where('status', 'waiting-for-registration')
|
||||
->cursor()
|
||||
->each(function ($invitation) use ($user) {
|
||||
TeamFolderMember::create([
|
||||
'user_id' => $user->id,
|
||||
'parent_id' => $invitation->parent_id,
|
||||
'permission' => $invitation->permission,
|
||||
]);
|
||||
|
||||
$invitation->accept();
|
||||
});
|
||||
|
||||
// Subscribe user for metered billing
|
||||
if ($settings['subscription_type'] === 'metered') {
|
||||
($this->autoSubscribeForMeteredBilling)($user);
|
||||
}
|
||||
|
||||
// Mark as verified if verification is disabled
|
||||
if (! $data->password || ! intval($settings['user_verification'])) {
|
||||
if (!$data->password || !intval($settings['user_verification'])) {
|
||||
$user->markEmailAsVerified();
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class RegisterUserRequest extends FormRequest
|
||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email', new EmailProvider],
|
||||
'name' => 'required|string|max:255',
|
||||
'password' => $this->passwordRules(),
|
||||
'reCaptcha' => [new RequiredIf(get_settings('allowed_recaptcha') == 1), 'string', app(ReCaptchaRules::class)],
|
||||
'reCaptcha' => [new RequiredIf(get_settings('allowed_recaptcha') == 1), 'string', 'nullable', app(ReCaptchaRules::class)],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ class StoreEnvironmentSettingsController extends Controller
|
||||
'local' => [
|
||||
'APP_ENV' => 'local',
|
||||
'APP_DEBUG' => 'true',
|
||||
'QUEUE_CONNECTION' => 'sync',
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace Domain\Teams\Controllers;
|
||||
|
||||
use App\Users\Models\User;
|
||||
use Domain\Teams\Models\TeamFolderMember;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Http\Controllers\Controller;
|
||||
@@ -13,7 +14,7 @@ class InvitationsController extends Controller
|
||||
{
|
||||
public function show(TeamFolderInvitation $invitation)
|
||||
{
|
||||
if ($invitation->status === 'accepted') {
|
||||
if ($invitation->status !== 'pending') {
|
||||
abort(410);
|
||||
}
|
||||
|
||||
@@ -23,19 +24,24 @@ class InvitationsController extends Controller
|
||||
public function update(
|
||||
TeamFolderInvitation $invitation
|
||||
): ResponseFactory | Response {
|
||||
$user = User::where('email', $invitation->email)
|
||||
->firstOrFail();
|
||||
$user = User::where('email', $invitation->email);
|
||||
|
||||
$invitation->update([
|
||||
'status' => 'accepted',
|
||||
]);
|
||||
if ($user->exists()) {
|
||||
$invitation->accept();
|
||||
|
||||
DB::table('team_folder_members')
|
||||
->insert([
|
||||
// Store team member
|
||||
TeamFolderMember::create([
|
||||
'user_id' => $user->first()->id,
|
||||
'parent_id' => $invitation->parent_id,
|
||||
'user_id' => $user->id,
|
||||
'permission' => 'can-edit',
|
||||
'permission' => $invitation->permission,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($user->doesntExist()) {
|
||||
$invitation->update([
|
||||
'status' => 'waiting-for-registration',
|
||||
]);
|
||||
}
|
||||
|
||||
return response('Done', 204);
|
||||
}
|
||||
@@ -43,9 +49,7 @@ class InvitationsController extends Controller
|
||||
public function destroy(
|
||||
TeamFolderInvitation $invitation
|
||||
): ResponseFactory | Response {
|
||||
$invitation->update([
|
||||
'status' => 'rejected',
|
||||
]);
|
||||
$invitation->reject();
|
||||
|
||||
return response('Done', 204);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,18 @@ class TeamFolderInvitation extends Model
|
||||
|
||||
protected $keyType = 'string';
|
||||
|
||||
public function accept() {
|
||||
$this->update([
|
||||
'status' => 'accepted',
|
||||
]);
|
||||
}
|
||||
|
||||
public function reject() {
|
||||
$this->update([
|
||||
'status' => 'rejected',
|
||||
]);
|
||||
}
|
||||
|
||||
protected static function newFactory(): TeamFolderInvitationFactory
|
||||
{
|
||||
return TeamFolderInvitationFactory::new();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace Domain\Teams\Resources;
|
||||
|
||||
use App\Users\Models\User;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class TeamInvitationResource extends JsonResource
|
||||
@@ -17,6 +18,7 @@ class TeamInvitationResource extends JsonResource
|
||||
'color' => $this->color,
|
||||
'status' => $this->status,
|
||||
'permission' => $this->permission,
|
||||
'isExistedUser' => User::where('email', $this->email)->exists(),
|
||||
],
|
||||
'relationships' => [
|
||||
$this->mergeWhen($this->inviter, fn () => [
|
||||
|
||||
@@ -41,7 +41,7 @@ class HomepageTest extends TestCase
|
||||
{
|
||||
$this->get('/')
|
||||
->assertStatus(200)
|
||||
->assertSee('setup-disclaimer')
|
||||
->assertSee('installation-needed')
|
||||
->assertSee('VueFileManager');
|
||||
}
|
||||
|
||||
|
||||
@@ -190,12 +190,12 @@ class SettingsTest extends TestCase
|
||||
$this
|
||||
->actingAs($admin)
|
||||
->postJson('/api/admin/settings/email', [
|
||||
'driver' => 'smtp',
|
||||
'host' => 'smtp.email.com',
|
||||
'port' => 25,
|
||||
'username' => 'john@doe.com',
|
||||
'password' => 'secret',
|
||||
'encryption' => 'tls',
|
||||
'mailDriver' => 'smtp',
|
||||
'smtp.host' => 'smtp.email.com',
|
||||
'smtp.port' => 25,
|
||||
'smtp.username' => 'john@doe.com',
|
||||
'smtp.password' => 'secret',
|
||||
'smtp.encryption' => 'tls',
|
||||
])->assertStatus(204);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class SetupWizardTest extends TestCase
|
||||
|
||||
$this->postJson('/api/setup/purchase-code', [
|
||||
'purchaseCode' => '8624194e-3156-4cd0-944e-3440fcecdacb',
|
||||
])->assertStatus(204);
|
||||
])->assertStatus(201);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Domain\Sharing;
|
||||
|
||||
use Tests\TestCase;
|
||||
@@ -193,7 +194,7 @@ class VisitorBrowseTest extends TestCase
|
||||
}
|
||||
|
||||
// Check public shared item
|
||||
if (! $is_protected) {
|
||||
if (!$is_protected) {
|
||||
$this->getJson("/api/browse/folders/$root->id/$share->token")
|
||||
->assertStatus(200)
|
||||
->assertJsonFragment([
|
||||
@@ -260,9 +261,10 @@ class VisitorBrowseTest extends TestCase
|
||||
|
||||
$tree = [
|
||||
[
|
||||
'id' => $share->item_id,
|
||||
'name' => 'Home',
|
||||
'location' => 'public',
|
||||
'isMovable' => true,
|
||||
'isOpen' => true,
|
||||
'folders' => [
|
||||
[
|
||||
'id' => $folder_level_2->id,
|
||||
@@ -308,7 +310,7 @@ class VisitorBrowseTest extends TestCase
|
||||
}
|
||||
|
||||
// Check public shared item
|
||||
if (! $is_protected) {
|
||||
if (!$is_protected) {
|
||||
$this->getJson("/api/browse/navigation/$share->token")
|
||||
->assertStatus(200)
|
||||
->assertExactJson($tree);
|
||||
@@ -360,7 +362,7 @@ class VisitorBrowseTest extends TestCase
|
||||
}
|
||||
|
||||
// Check public shared item
|
||||
if (! $is_protected) {
|
||||
if (!$is_protected) {
|
||||
$this->getJson("/api/browse/search/$share->token?query=doc")
|
||||
->assertStatus(200)
|
||||
->assertJsonFragment([
|
||||
@@ -411,7 +413,7 @@ class VisitorBrowseTest extends TestCase
|
||||
}
|
||||
|
||||
// Check public shared item
|
||||
if (! $is_protected) {
|
||||
if (!$is_protected) {
|
||||
$this->getJson("/api/browse/search/$share->token?query=doc")
|
||||
->assertStatus(200)
|
||||
->assertJsonFragment([]);
|
||||
@@ -458,7 +460,7 @@ class VisitorBrowseTest extends TestCase
|
||||
}
|
||||
|
||||
// Check public shared item
|
||||
if (! $is_protected) {
|
||||
if (!$is_protected) {
|
||||
$this->getJson("/api/browse/file/$share->token")
|
||||
->assertStatus(200)
|
||||
->assertJsonFragment([
|
||||
|
||||
@@ -300,6 +300,7 @@ class VisitorManipulatingTest extends TestCase
|
||||
collect([true, false])
|
||||
->each(function ($is_protected) {
|
||||
$user = User::factory()
|
||||
->hasSettings()
|
||||
->create();
|
||||
|
||||
$folder = Folder::factory(Folder::class)
|
||||
|
||||
@@ -44,7 +44,7 @@ class TeamManagementTest extends TestCase
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_accept_team_folder_invite()
|
||||
public function it_accept_team_folder_invite_as_registered_user()
|
||||
{
|
||||
$member = User::factory()
|
||||
->create([
|
||||
@@ -59,7 +59,7 @@ class TeamManagementTest extends TestCase
|
||||
'parent_id' => $folder->id,
|
||||
'email' => $member->email,
|
||||
'status' => 'pending',
|
||||
'permission' => 'can-edit',
|
||||
'permission' => 'can-view',
|
||||
]);
|
||||
|
||||
$this
|
||||
@@ -75,8 +75,69 @@ class TeamManagementTest extends TestCase
|
||||
->assertDatabaseHas('team_folder_members', [
|
||||
'parent_id' => $folder->id,
|
||||
'user_id' => $member->id,
|
||||
'permission' => 'can-view',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_accept_team_folder_invite_as_guest_user()
|
||||
{
|
||||
$folder = Folder::factory()
|
||||
->create();
|
||||
|
||||
$invitation = TeamFolderInvitation::factory()
|
||||
->create([
|
||||
'parent_id' => $folder->id,
|
||||
'email' => 'howdy@hi5ve.digital',
|
||||
'status' => 'pending',
|
||||
'permission' => 'can-edit',
|
||||
]);
|
||||
|
||||
$this
|
||||
->putJson("/api/teams/invitations/{$invitation->id}")
|
||||
->assertNoContent();
|
||||
|
||||
$this
|
||||
->assertDatabaseHas('team_folder_invitations', [
|
||||
'parent_id' => $folder->id,
|
||||
'status' => 'waiting-for-registration',
|
||||
])
|
||||
->assertDatabaseMissing('team_folder_members', [
|
||||
'parent_id' => $folder->id,
|
||||
'permission' => 'can-edit',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_apply_accepted_invitation_after_user_registration()
|
||||
{
|
||||
$invitation = TeamFolderInvitation::factory()
|
||||
->create([
|
||||
'email' => 'john@doe.com',
|
||||
'status' => 'waiting-for-registration',
|
||||
]);
|
||||
|
||||
$this->postJson('api/register', [
|
||||
'email' => 'john@doe.com',
|
||||
'password' => 'SecretPassword',
|
||||
'password_confirmation' => 'SecretPassword',
|
||||
'name' => 'John Doe',
|
||||
])->assertStatus(201);
|
||||
|
||||
$this
|
||||
->assertDatabaseHas('team_folder_invitations', [
|
||||
'parent_id' => $invitation->parent_id,
|
||||
'status' => 'accepted',
|
||||
])
|
||||
->assertDatabaseHas('team_folder_members', [
|
||||
'parent_id' => $invitation->parent_id,
|
||||
'user_id' => User::first()->id,
|
||||
'permission' => $invitation->permission,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user