feat: Add invitation accept and decline endpoints - #19
Conversation
Invited users can now accept or decline a workspace invitation through the API, completing the invitation lifecycle that previously supported only creating and cancelling invites. The invite and join moments now emit events, so a host application can react to them, for example to deliver the invitation email.
Code Review SummaryThis PR completes the invitation lifecycle by adding API endpoints for accepting and declining invitations. It also introduces event dispatching ( 🚀 Key Improvements
💡 Minor Suggestions
|
| return response()->json(['error' => 'Unauthorized'], 401); | ||
| } | ||
|
|
||
| $invitation = WorkspaceInvitation::where('token', $token)->firstOrFail(); |
There was a problem hiding this comment.
Retrieving the invitation first by token and then checking the email separately is safe, but for performance and conciseness, you can include the email check in the query. This also prevents leaking existence of a token to the wrong user via 403 vs 404.
| $invitation = WorkspaceInvitation::where('token', $token)->firstOrFail(); | |
| $invitation = WorkspaceInvitation::where('token', $token)->where('email', $user->email)->firstOrFail(); |
| return response()->json(['error' => $reason], 422); | ||
| } | ||
|
|
||
| if (! $user->acceptInvitation($invitation)) { |
There was a problem hiding this comment.
It is safer to wrap the acceptance logic in a database transaction to ensure that the workspace member record creation and invitation status update happen atomically.
| if (! $user->acceptInvitation($invitation)) { | |
| try { | |
| $accepted = \Illuminate\Support\Facades\DB::transaction(fn() => $user->acceptInvitation($invitation)); | |
| if (! $accepted) throw new \Exception(); | |
| } catch (\Exception $e) { | |
| return response()->json(['error' => 'Unable to accept invitation'], 422); | |
| } |
| $user = auth()->user(); | ||
|
|
There was a problem hiding this comment.
The authentication check if (! $user) is redundant here because this route should be protected by the auth:sanctum (or similar) middleware. Removing it simplifies the controller and leverages the framework's built-in handling.
| $user = auth()->user(); | |
| $user = auth()->user(); |
| public function acceptInvitation(string $token): JsonResponse | ||
| { | ||
| $user = auth()->user(); | ||
|
|
There was a problem hiding this comment.
Consistency: As with acceptInvitation, the manual check for an authenticated user can be removed assuming the route is protected by middleware.
| $user = auth()->user(); |
Invited users can now accept or decline a workspace invitation through the API, completing the invitation lifecycle that previously supported only creating and cancelling invites.
The invite and join moments now emit events, so a host application can react to them, for example to deliver the invitation email.