Skip to content

Commit

Permalink
Added member and invitation endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
korridor committed Apr 10, 2024
1 parent e8621ad commit 0b482bd
Show file tree
Hide file tree
Showing 48 changed files with 1,186 additions and 106 deletions.
18 changes: 13 additions & 5 deletions app/Actions/Fortify/CreateNewUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,18 @@ public function create(array $input): User
*/
protected function createTeam(User $user): void
{
$user->ownedTeams()->save(Organization::forceCreate([
'user_id' => $user->id,
'name' => explode(' ', $user->name, 2)[0]."'s Organization",
'personal_team' => true,
]));
$organization = new Organization();
$organization->name = explode(' ', $user->name, 2)[0]."'s Organization";
$organization->personal_team = true;
$organization->owner()->associate($user);
$organization->save();

$organization->users()->attach(
$user, [
'role' => 'owner',
]
);

$user->ownedTeams()->save($organization);
}
}
35 changes: 25 additions & 10 deletions app/Actions/Jetstream/AddOrganizationMember.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@

namespace App\Actions\Jetstream;

use App\Enums\Role;
use App\Models\Organization;
use App\Models\User;
use App\Service\UserService;
use Closure;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\In;
use Korridor\LaravelModelValidationRules\Rules\ExistsEloquent;
use Laravel\Jetstream\Contracts\AddsTeamMembers;
use Laravel\Jetstream\Events\AddingTeamMember;
use Laravel\Jetstream\Events\TeamMemberAdded;
use Laravel\Jetstream\Jetstream;
use Laravel\Jetstream\Rules\Role;

class AddOrganizationMember implements AddsTeamMembers
{
Expand All @@ -37,9 +39,15 @@ public function add(User $owner, Organization $organization, string $email, ?str

AddingTeamMember::dispatch($organization, $newOrganizationMember);

$organization->users()->attach(
$newOrganizationMember, ['role' => $role]
);
DB::transaction(function () use ($organization, $newOrganizationMember, $role) {
$organization->users()->attach(
$newOrganizationMember, ['role' => $role]
);

if ($role === Role::Owner->value) {
app(UserService::class)->changeOwnership($organization, $newOrganizationMember);
}
});

TeamMemberAdded::dispatch($organization, $newOrganizationMember);
}
Expand All @@ -60,7 +68,7 @@ protected function validate(Organization $organization, string $email, ?string $
/**
* Get the validation rules for adding a team member.
*
* @return array<string, array<ValidationRule|Rule|string>>
* @return array<string, array<ValidationRule|Rule|string|In>>
*/
protected function rules(): array
{
Expand All @@ -72,9 +80,16 @@ protected function rules(): array
return $builder->where('is_placeholder', '=', false);
}))->withMessage(__('We were unable to find a registered user with this email address.')),
],
'role' => Jetstream::hasRoles()
? ['required', 'string', new Role]
: null,
'role' => [
'required',
'string',
Rule::in([
Role::Owner->value,
Role::Admin->value,
Role::Manager->value,
Role::Employee->value,
]),
],
]);
}

Expand Down
18 changes: 13 additions & 5 deletions app/Actions/Jetstream/CreateOrganization.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,19 @@ public function create(User $user, array $input): Organization

AddingTeam::dispatch($user);

/** @var Organization $organization */
$organization = $user->ownedTeams()->create([
'name' => $input['name'],
'personal_team' => false,
]);
$organization = new Organization();
$organization->name = $input['name'];
$organization->personal_team = false;
$organization->owner()->associate($user);
$organization->save();

$organization->users()->attach(
$user, [
'role' => 'owner',
]
);

$user->ownedTeams()->save($organization);

$user->switchTeam($organization);

Expand Down
30 changes: 21 additions & 9 deletions app/Actions/Jetstream/InviteOrganizationMember.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,36 @@

namespace App\Actions\Jetstream;

use App\Enums\Role;
use App\Models\Organization;
use App\Models\OrganizationInvitation;
use App\Models\User;
use App\Service\PermissionStore;
use Closure;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\In;
use Korridor\LaravelModelValidationRules\Rules\UniqueEloquent;
use Laravel\Jetstream\Contracts\InvitesTeamMembers;
use Laravel\Jetstream\Events\InvitingTeamMember;
use Laravel\Jetstream\Jetstream;
use Laravel\Jetstream\Mail\TeamInvitation;
use Laravel\Jetstream\Rules\Role;

class InviteOrganizationMember implements InvitesTeamMembers
{
/**
* Invite a new team member to the given team.
*
* @throws AuthorizationException
*/
public function invite(User $user, Organization $organization, string $email, ?string $role = null): void
{
Gate::forUser($user)->authorize('addTeamMember', $organization);
if (! app(PermissionStore::class)->has($organization, 'invitations:create')) {
throw new AuthorizationException();
}

$this->validate($organization, $email, $role);

Expand Down Expand Up @@ -59,7 +64,7 @@ protected function validate(Organization $organization, string $email, ?string $
/**
* Get the validation rules for inviting a team member.
*
* @return array<string, array<ValidationRule|Rule|string>>
* @return array<string, array<ValidationRule|Rule|string|In>>
*/
protected function rules(Organization $organization): array
{
Expand All @@ -72,9 +77,16 @@ protected function rules(Organization $organization): array
return $builder->whereBelongsTo($organization, 'organization');
}))->withMessage(__('This user has already been invited to the team.')),
],
'role' => Jetstream::hasRoles()
? ['required', 'string', new Role]
: null,
'role' => [
'required',
'string',
Rule::in([
Role::Owner->value,
Role::Admin->value,
Role::Manager->value,
Role::Employee->value,
]),
],
]);
}

Expand Down
67 changes: 67 additions & 0 deletions app/Actions/Jetstream/UpdateMemberRole.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

namespace App\Actions\Jetstream;

use App\Enums\Role;
use App\Models\Membership;
use App\Models\Organization;
use App\Models\User;
use App\Service\PermissionStore;
use App\Service\UserService;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Laravel\Jetstream\Events\TeamMemberUpdated;

class UpdateMemberRole
{
/**
* Update the role for the given team member.
*
* @throws AuthorizationException
* @throws ValidationException
*/
public function update(User $actingUser, Organization $organization, string $userId, string $role): void
{
if (! app(PermissionStore::class)->has($organization, 'members:change-role')) {
throw new AuthorizationException();
}

$user = User::where('id', '=', $userId)->firstOrFail();
$member = Membership::whereBelongsTo($user)->whereBelongsTo($organization)->firstOrFail();
if ($member->role === Role::Placeholder->value) {
abort(403, 'Cannot update the role of a placeholder member.');
}

Validator::make([
'role' => $role,
], [
'role' => [
'required',
'string',
Rule::in([
Role::Owner->value,
Role::Admin->value,
Role::Manager->value,
Role::Employee->value,
]),
],
])->validate();

DB::transaction(function () use ($organization, $userId, $role, $user) {
$organization->users()->updateExistingPivot($userId, [
'role' => $role,
]);

if ($role === Role::Owner->value) {
app(UserService::class)->changeOwnership($organization, $user);
}
});

TeamMemberUpdated::dispatch($organization->fresh(), User::findOrFail($userId));
}
}
15 changes: 15 additions & 0 deletions app/Enums/Role.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace App\Enums;

enum Role: string
{
case Owner = 'owner';
case Admin = 'admin';
case Manager = 'manager';
case Employee = 'employee';
case Placeholder = 'placeholder';

}
10 changes: 10 additions & 0 deletions app/Exceptions/Api/InactiveUserCanNotBeUsedApiException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace App\Exceptions\Api;

class InactiveUserCanNotBeUsedApiException extends ApiException
{
public const string KEY = 'inactive_user_can_not_be_used';
}
10 changes: 10 additions & 0 deletions app/Exceptions/Api/UserIsAlreadyMemberOfProjectApiException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace App\Exceptions\Api;

class UserIsAlreadyMemberOfProjectApiException extends ApiException
{
public const string KEY = 'user_is_already_member_of_project';
}
57 changes: 57 additions & 0 deletions app/Http/Controllers/Api/V1/InvitationController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api\V1;

use App\Http\Requests\V1\Invitation\InvitationIndexRequest;
use App\Http\Requests\V1\Invitation\InvitationStoreRequest;
use App\Http\Resources\V1\Invitation\InvitationCollection;
use App\Http\Resources\V1\Invitation\InvitationResource;
use App\Models\Organization;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Laravel\Jetstream\Contracts\InvitesTeamMembers;

class InvitationController extends Controller
{
/**
* List all invitations of an organization
*
* @return InvitationCollection<InvitationResource>
*
* @throws AuthorizationException
*
* @operationId getInvitations
*/
public function index(Organization $organization, InvitationIndexRequest $request): InvitationCollection
{
$this->checkPermission($organization, 'invitations:view');

$invitations = $organization->teamInvitations()
->paginate();

return InvitationCollection::make($invitations);
}

/**
* Invite a user to the organization
*
* @throws AuthorizationException
*
* @operationId invite
*/
public function store(Organization $organization, InvitationStoreRequest $request): JsonResponse
{
$this->checkPermission($organization, 'invitations:create');

app(InvitesTeamMembers::class)->invite(
$request->user(),
$organization,
$request->input('email'),
$request->input('role')
);

return response()->json(null, 204);
}
}
Loading

0 comments on commit 0b482bd

Please sign in to comment.