Use resolveStoredTeam() instead of teams()->first() when picking the next active team after a team deletion or when an admin switches into a user's account, so a valid stored preference wins over an arbitrary first team. DeleteTeam now returns null when the deletion leaves the owner with multiple teams, deferring to the selection screen instead of guessing. refreshSession also stops persisting current_team_id while impersonating, so viewing another user's account no longer overwrites their real last-active team.
75 lines
2.8 KiB
PHP
75 lines
2.8 KiB
PHP
<?php
|
|
|
|
namespace App\Actions\Team;
|
|
|
|
use App\Models\Application;
|
|
use App\Models\Team;
|
|
use App\Models\User;
|
|
use Illuminate\Auth\Access\AuthorizationException;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\DB;
|
|
use RuntimeException;
|
|
|
|
class DeleteTeam
|
|
{
|
|
public function handle(Team $team, User $user): ?Team
|
|
{
|
|
$newTeam = DB::transaction(function () use ($team, $user): ?Team {
|
|
$team = Team::query()->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;
|
|
}
|
|
}
|