diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php index 27138135ef..62199ba6ec 100644 --- a/app/Http/Controllers/Webhook/Gitlab.php +++ b/app/Http/Controllers/Webhook/Gitlab.php @@ -17,7 +17,6 @@ use Exception; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; -use Visus\Cuid2\Cuid2; class Gitlab extends Controller { @@ -185,7 +184,7 @@ class Gitlab extends Controller continue; } - $deployment_uuid = new Cuid2; + $deployment_uuid = new_public_id(); $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, @@ -203,7 +202,7 @@ class Gitlab extends Controller 'mode' => 'app', 'application_uuid' => $application->uuid, 'application_name' => $application->name, - 'deployment_uuid' => $deployment_uuid->toString(), + 'deployment_uuid' => $deployment_uuid, 'commit' => $commit, ]); @@ -275,7 +274,7 @@ class Gitlab extends Controller continue; } - $deployment_uuid = new Cuid2; + $deployment_uuid = new_public_id(); $found = ApplicationPreview::where('application_id', $application->id) ->where('pull_request_id', $pull_request_id) ->first(); diff --git a/app/Livewire/Server/New/ByDigitalOcean.php b/app/Livewire/Server/New/ByDigitalOcean.php index d05532776e..1cfcc56634 100644 --- a/app/Livewire/Server/New/ByDigitalOcean.php +++ b/app/Livewire/Server/New/ByDigitalOcean.php @@ -296,7 +296,9 @@ class ByDigitalOcean extends Component $details = data_get($e->response->json(), $jsonMessageKey) ?: $e->response->body() ?: $details; } - return "{$providerName} API error: {$details}"; + $prefix = "{$providerName} API error: "; + + return str_starts_with($details, $prefix) ? $details : $prefix.$details; } public function getAvailableSizesProperty(): array diff --git a/app/Livewire/Server/New/ByVultr.php b/app/Livewire/Server/New/ByVultr.php index 3a6ce532df..321d8f6588 100644 --- a/app/Livewire/Server/New/ByVultr.php +++ b/app/Livewire/Server/New/ByVultr.php @@ -375,7 +375,9 @@ class ByVultr extends Component $details = data_get($e->response->json(), $jsonMessageKey) ?: $e->response->body() ?: $details; } - return "{$providerName} API error: {$details}"; + $prefix = "{$providerName} API error: "; + + return str_starts_with($details, $prefix) ? $details : $prefix.$details; } private function createVultrServer(VultrService $vultrService): array diff --git a/app/Livewire/Team/DangerZone.php b/app/Livewire/Team/DangerZone.php index 74cf3d7ef8..643477e24d 100644 --- a/app/Livewire/Team/DangerZone.php +++ b/app/Livewire/Team/DangerZone.php @@ -16,6 +16,7 @@ class DangerZone extends Component public function mount(): void { $this->team = currentTeam(); + $this->authorize('delete', $this->team); } public function delete(): mixed diff --git a/app/Services/DeploymentConfiguration/ConfigurationDiffer.php b/app/Services/DeploymentConfiguration/ConfigurationDiffer.php index ace4888338..688db3f0a4 100644 --- a/app/Services/DeploymentConfiguration/ConfigurationDiffer.php +++ b/app/Services/DeploymentConfiguration/ConfigurationDiffer.php @@ -39,7 +39,9 @@ class ConfigurationDiffer $previous = $previousItems[$key] ?? null; $current = $currentItems[$key] ?? null; - if ($previous === null && ! in_array(data_get($current, 'section'), self::DYNAMIC_SECTIONS, true)) { + // Static settings missing from an older snapshot were newly tracked, not changed. + // Without any previous snapshot (legacy fallback), every setting is reported. + if ($previous === null && $previousItems !== [] && ! in_array(data_get($current, 'section'), self::DYNAMIC_SECTIONS, true)) { continue; } diff --git a/app/Traits/HasSecretManager.php b/app/Traits/HasSecretManager.php index 8b3e50b7bd..a788ae823e 100644 --- a/app/Traits/HasSecretManager.php +++ b/app/Traits/HasSecretManager.php @@ -15,7 +15,10 @@ trait HasSecretManager public static function bootHasSecretManager(): void { - static::deleting(fn ($resource) => $resource->secretManagerLink()->delete()); + // Return nothing: a non-null result halts the "deleting" event and skips later listeners. + static::deleting(function ($resource): void { + $resource->secretManagerLink()->delete(); + }); } public function secretManagerLink(): MorphOne diff --git a/resources/views/components/team/settings-layout.blade.php b/resources/views/components/team/settings-layout.blade.php index 3b6db03c3a..be3ea573e1 100644 --- a/resources/views/components/team/settings-layout.blade.php +++ b/resources/views/components/team/settings-layout.blade.php @@ -24,13 +24,13 @@ 'active' => request()->routeIs('team.admin-view'), 'icon' => 'admin', ] : null, - [ + auth()->user()->can('delete', currentTeam()) ? [ 'label' => 'Danger Zone', 'route' => 'team.danger-zone', 'active' => request()->routeIs('team.danger-zone'), 'icon' => 'shield-alert', 'sectionStart' => true, - ], + ] : null, ])->filter(); @endphp diff --git a/tests/Feature/Authorization/TeamAuthorizationTest.php b/tests/Feature/Authorization/TeamAuthorizationTest.php index 0637a48db5..7d437c823f 100644 --- a/tests/Feature/Authorization/TeamAuthorizationTest.php +++ b/tests/Feature/Authorization/TeamAuthorizationTest.php @@ -171,17 +171,34 @@ test('team index mounts when is_mcp_server_enabled is null on the session team', // --- Team Danger Zone Livewire: delete --- -test('member cannot delete team via danger zone', function () { +test('member cannot open the team danger zone', function () { $this->actingAs($this->member); session(['currentTeam' => $this->team]); - Livewire::test(DangerZone::class) - ->call('delete') - ->assertDispatched('error'); + Livewire::test(DangerZone::class)->assertForbidden(); expect(Team::find($this->team->id))->not->toBeNull(); }); +test('owner can open the team danger zone', function () { + $this->actingAs($this->owner); + session(['currentTeam' => $this->team]); + + Livewire::test(DangerZone::class)->assertSuccessful(); +}); + +test('team settings show the danger zone link only to the team owner', function () { + session(['currentTeam' => $this->team]); + + $this->actingAs($this->owner); + Livewire::test(TeamIndex::class)->assertSee('Danger Zone'); + + foreach ([$this->admin, $this->member] as $user) { + $this->actingAs($user); + Livewire::test(TeamIndex::class)->assertDontSee('Danger Zone'); + } +}); + test('admin cannot delete team via policy', function () { $this->actingAs($this->admin); session(['currentTeam' => $this->team]);