Skip to content

feat: Add invitation accept and decline endpoints - #19

Merged
nfebe merged 2 commits into
devfrom
feat/invitation-accept-decline
Jun 13, 2026
Merged

nfebe merged 2 commits into
devfrom
feat/invitation-accept-decline

Conversation

@nfebe

@nfebe nfebe commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

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.

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.
@sourceant

sourceant Bot commented Jun 13, 2026

Copy link
Copy Markdown

Code Review Summary

This PR completes the invitation lifecycle by adding API endpoints for accepting and declining invitations. It also introduces event dispatching (MemberInvited, MemberJoined) which is a significant improvement for extensibility.

🚀 Key Improvements

  • Added MemberInvited and MemberJoined events for better system integration.
  • Comprehensive test coverage for success and failure states of the new endpoints.
  • Updated documentation reflecting the current state of the API.

💡 Minor Suggestions

  • Remove redundant auth()->user() checks if middleware is present.
  • Consider using Route Model Binding for the token if the model implements getRouteKeyName.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

return response()->json(['error' => 'Unauthorized'], 401);
}

$invitation = WorkspaceInvitation::where('token', $token)->firstOrFail();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
$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)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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);
}

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment on lines +293 to +294
$user = auth()->user();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
$user = auth()->user();
$user = auth()->user();

public function acceptInvitation(string $token): JsonResponse
{
$user = auth()->user();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistency: As with acceptInvitation, the manual check for an authenticated user can be removed assuming the route is protected by middleware.

Suggested change
$user = auth()->user();

@nfebe
nfebe merged commit 2f7f5ae into dev Jun 13, 2026
2 checks passed
@nfebe
nfebe deleted the feat/invitation-accept-decline branch June 13, 2026 18:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant