diff --git a/app/Models/Team.php b/app/Models/Team.php index 15085203a..d5957af5a 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -275,13 +275,22 @@ public function invitations() return $this->hasMany(TeamInvitation::class); } - public function isEmpty() + /** + * @return array + */ + public function deletionBlockers(): array { - if ($this->projects()->count() === 0 && $this->servers()->count() === 0 && $this->privateKeys()->count() === 0 && $this->sources()->count() === 0) { - return true; - } + return array_filter([ + 'projects' => $this->projects()->count(), + 'servers' => $this->servers()->count(), + 'sources' => GithubApp::query()->where('team_id', $this->id)->count() + + GitlabApp::query()->where('team_id', $this->id)->count(), + ]); + } - return false; + public function isEmpty(): bool + { + return $this->deletionBlockers() === []; } public function projects() diff --git a/resources/views/livewire/team/danger-zone.blade.php b/resources/views/livewire/team/danger-zone.blade.php index 8d0c022b1..3d8d78a91 100644 --- a/resources/views/livewire/team/danger-zone.blade.php +++ b/resources/views/livewire/team/danger-zone.blade.php @@ -1,4 +1,12 @@
+ @php + $deletionBlockers = currentTeam()->deletionBlockers(); + $blockerDetails = [ + 'projects' => ['label' => 'project', 'route' => 'project.index'], + 'servers' => ['label' => 'server', 'route' => 'server.index'], + 'sources' => ['label' => 'Git source', 'route' => 'source.all'], + ]; + @endphp Team Danger Zone | Coolify @@ -34,7 +42,7 @@ class="rounded-lg border border-red-300 bg-red-50 p-4 ring-1 ring-inset ring-red {{ wireNavigate() }} href="{{ route('subscription.show') }}">subscription before deleting this team.

- @elseif(currentTeam()->isEmpty()) + @elseif($deletionBlockers === [])

Permanently delete {{ currentTeam()->name }} from Coolify. This action cannot be undone. @@ -45,7 +53,20 @@ class="rounded-lg border border-red-300 bg-red-50 p-4 ring-1 ring-inset ring-red @else

- Remove or move every resource owned by this team before deleting it. + This team still owns: +

+ +

+ Remove or move these resources before deleting the team.

@endif
@@ -57,7 +78,7 @@ class="rounded-lg border border-red-300 bg-red-50 p-4 ring-1 ring-inset ring-red auth()->user()->teams()->count() > 1 && !auth()->user()->currentTeam()->personal_team && !currentTeam()->subscription && - currentTeam()->isEmpty()) + $deletionBlockers === []) assertSuccessful(); }); +test('unused private keys do not block team deletion', function () { + $privateKey = PrivateKey::factory()->create([ + 'team_id' => $this->teamToDelete->id, + 'description' => 'Created by Coolify', + ]); + + expect($this->teamToDelete->isEmpty())->toBeTrue(); + + app(DeleteTeam::class)->handle($this->teamToDelete, $this->owner); + + expect(Team::find($this->teamToDelete->id))->toBeNull() + ->and(PrivateKey::find($privateKey->id))->toBeNull(); +}); + +test('the danger zone names and links blocking resource types', function () { + Project::factory()->count(2)->create(['team_id' => $this->teamToDelete->id]); + + $this->actingAs($this->owner); + session(['currentTeam' => $this->teamToDelete]); + + Livewire::test(DangerZone::class) + ->assertSee('This team still owns:') + ->assertSee('2 projects') + ->assertSeeHtml('href="'.route('project.index').'"'); +}); + test('a team with a running application cannot be deleted', function () { $server = Server::factory()->create(['team_id' => $this->teamToDelete->id]); $destination = StandaloneDocker::query()->where('server_id', $server->id)->firstOrFail();