fix(backup): throw explicit error when S3 storage missing or deleted (#9038)

This commit is contained in:
Andras Bacsai authored and GitHub committed 2026-03-19 23:39:36 +01:00
commit 2bc8fe3dd7
11 files changed
+393 -49

No files matched your search

+9 -3
View File
@@ -625,10 +625,16 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
private function upload_to_s3(): void
{
if (is_null($this->s3)) {
$this->backup->update([
'save_s3' => false,
's3_storage_id' => null,
]);
throw new \Exception('S3 storage configuration is missing or has been deleted (S3 storage ID: '.($this->backup->s3_storage_id ?? 'null').'). S3 backup has been disabled for this schedule.');
}
try {
if (is_null($this->s3)) {
return;
}
$key = $this->s3->key;
$secret = $this->s3->secret;
// $region = $this->s3->region;
+2 -13
View File
@@ -6,6 +6,7 @@ use App\Models\S3Storage;
use App\Support\ValidationPatterns;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\On;
use Livewire\Component;
class Form extends Component
@@ -131,19 +132,7 @@ class Form extends Component
}
}
public function delete()
{
try {
$this->authorize('delete', $this->storage);
$this->storage->delete();
return redirect()->route('storage.index');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
#[On('submitStorage')]
public function submit()
{
try {
+85
View File
@@ -0,0 +1,85 @@
<?php
namespace App\Livewire\Storage;
use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup;
use Livewire\Component;
class Resources extends Component
{
public S3Storage $storage;
public array $selectedStorages = [];
public function mount(): void
{
$backups = ScheduledDatabaseBackup::where('s3_storage_id', $this->storage->id)
->where('save_s3', true)
->get();
foreach ($backups as $backup) {
$this->selectedStorages[$backup->id] = $this->storage->id;
}
}
public function disableS3(int $backupId): void
{
$backup = ScheduledDatabaseBackup::findOrFail($backupId);
$backup->update([
'save_s3' => false,
's3_storage_id' => null,
]);
unset($this->selectedStorages[$backupId]);
$this->dispatch('success', 'S3 disabled.', 'S3 backup has been disabled for this schedule.');
}
public function moveBackup(int $backupId): void
{
$backup = ScheduledDatabaseBackup::findOrFail($backupId);
$newStorageId = $this->selectedStorages[$backupId] ?? null;
if (! $newStorageId || (int) $newStorageId === $this->storage->id) {
$this->dispatch('error', 'No change.', 'The backup is already using this storage.');
return;
}
$newStorage = S3Storage::where('id', $newStorageId)
->where('team_id', $this->storage->team_id)
->first();
if (! $newStorage) {
$this->dispatch('error', 'Storage not found.');
return;
}
$backup->update(['s3_storage_id' => $newStorage->id]);
unset($this->selectedStorages[$backupId]);
$this->dispatch('success', 'Backup moved.', "Moved to {$newStorage->name}.");
}
public function render()
{
$backups = ScheduledDatabaseBackup::where('s3_storage_id', $this->storage->id)
->where('save_s3', true)
->with('database')
->get()
->groupBy(fn ($backup) => $backup->database_type.'-'.$backup->database_id);
$allStorages = S3Storage::where('team_id', $this->storage->team_id)
->orderBy('name')
->get(['id', 'name', 'is_usable']);
return view('livewire.storage.resources', [
'groupedBackups' => $backups,
'allStorages' => $allStorages,
]);
}
}
+20
View File
@@ -3,6 +3,7 @@
namespace App\Livewire\Storage;
use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
@@ -12,6 +13,10 @@ class Show extends Component
public $storage = null;
public string $currentRoute = '';
public int $backupCount = 0;
public function mount()
{
$this->storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->first();
@@ -19,6 +24,21 @@ class Show extends Component
abort(404);
}
$this->authorize('view', $this->storage);
$this->currentRoute = request()->route()->getName();
$this->backupCount = ScheduledDatabaseBackup::where('s3_storage_id', $this->storage->id)->count();
}
public function delete()
{
try {
$this->authorize('delete', $this->storage);
$this->storage->delete();
return redirect()->route('storage.index');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
+12
View File
@@ -40,6 +40,13 @@ class S3Storage extends BaseModel
$storage->secret = trim($storage->secret);
}
});
static::deleting(function (S3Storage $storage) {
ScheduledDatabaseBackup::where('s3_storage_id', $storage->id)->update([
'save_s3' => false,
's3_storage_id' => null,
]);
});
}
public static function ownedByCurrentTeam(array $select = ['*'])
@@ -59,6 +66,11 @@ class S3Storage extends BaseModel
return $this->belongsTo(Team::class);
}
public function scheduledBackups()
{
return $this->hasMany(ScheduledDatabaseBackup::class, 's3_storage_id');
}
public function awsUrl()
{
return "{$this->endpoint}/{$this->bucket}";