validate([ 'user_id' => ['required', 'integer', 'exists:users,id'], ]); $client = User::query()->findOrFail((int) $validated['user_id']); // Membership is clients-only — staff never belong to groups. Worth // enforcing here as well as on the web: a group is a sharing // target, and a staff member inside one would start receiving // shares as though they were a customer. if (! $client->isClient()) { throw ValidationException::withMessages([ 'user_id' => __('Only clients can be group members.'), ]); } // syncWithoutDetaching, so adding an existing member is a no-op and // a retried request is safe. $group->members()->syncWithoutDetaching([$client->id]); $this->activity->log(Action::GroupMemberAdded, subject: $group, context: ['member' => $client->name]); return new GroupResource($group->loadCount('members')->load('members')); } public function destroy(Group $group, User $member): GroupResource { $group->members()->detach($member->id); $this->activity->log(Action::GroupMemberRemoved, subject: $group, context: ['member' => $member->name]); return new GroupResource($group->loadCount('members')->load('members')); } }