lockForUpdate()->findOrFail($team->id); $role = DB::table('team_user') ->where('team_id', $team->id) ->where('user_id', $user->id) ->lockForUpdate() ->value('role'); if ($role !== 'owner') { throw new AuthorizationException('Only team owners can delete a team.'); } $hasRunningApplications = Application::query() ->whereHas('environment.project', fn ($query) => $query->where('team_id', $team->id)) ->lockForUpdate() ->get(['id', 'status']) ->contains(fn (Application $application): bool => $application->isRunning()); if ($hasRunningApplications) { throw new RuntimeException('Stop all running applications before deleting this team.'); } if ($team->servers()->lockForUpdate()->get(['servers.id'])->isNotEmpty()) { throw new RuntimeException('Delete all team servers before deleting this team.'); } if (! $team->isEmpty()) { throw new RuntimeException('Delete all team resources before deleting this team.'); } $team->members() ->where('users.id', '!=', $user->id) ->get() ->each(function (User $member) use ($team): void { $member->teams()->detach($team); $member->clearStoredTeamIfMatches($team->id); DB::table('sessions')->where('user_id', $member->id)->delete(); }); // The deleting owner is excluded from the loop above; clear their // stored team too so the deleted id is not restored on next login. $user->clearStoredTeamIfMatches($team->id); $team->delete(); // Resolve the next active team the same way login does: the user's // stored choice when still valid, or their sole remaining team. // Returns null for a multi-team user whose active team was just // deleted, so refreshSession sends them to the selection screen // instead of silently dropping them into an arbitrary first team. return User::query()->find($user->id)?->resolveStoredTeam(); }); Cache::forget("user:{$user->id}:team:{$team->id}"); return $newTeam; } }