2023-08-10 13:52:54 +00:00
< ? php
2023-12-07 18:06:32 +00:00
namespace App\Livewire\Project\Database ;
2023-08-10 13:52:54 +00:00
2024-05-24 15:20:20 +00:00
use App\Models\ScheduledDatabaseBackup ;
2024-11-04 11:40:10 +00:00
use Exception ;
2025-08-23 16:50:35 +00:00
use Illuminate\Foundation\Auth\Access\AuthorizesRequests ;
2024-11-04 11:40:10 +00:00
use Livewire\Attributes\Locked ;
2024-11-05 08:36:40 +00:00
use Livewire\Attributes\Validate ;
2023-08-10 13:52:54 +00:00
use Livewire\Component ;
class BackupEdit extends Component
{
2025-08-23 16:50:35 +00:00
use AuthorizesRequests ;
2024-11-04 11:40:10 +00:00
public ScheduledDatabaseBackup $backup ;
2024-06-10 20:43:34 +00:00
2024-11-04 11:40:10 +00:00
#[Locked]
2023-08-11 14:13:53 +00:00
public $s3s ;
2024-06-10 20:43:34 +00:00
2024-11-04 11:40:10 +00:00
#[Locked]
public $parameters ;
2024-11-05 08:36:40 +00:00
#[Validate(['required', 'boolean'])]
2024-09-02 17:27:21 +00:00
public bool $delete_associated_backups_locally = false ;
2024-09-23 17:51:31 +00:00
2024-11-05 08:36:40 +00:00
#[Validate(['required', 'boolean'])]
2024-09-02 17:27:21 +00:00
public bool $delete_associated_backups_s3 = false ;
2024-09-23 17:51:31 +00:00
2024-11-05 08:36:40 +00:00
#[Validate(['required', 'boolean'])]
2024-09-02 17:27:21 +00:00
public bool $delete_associated_backups_sftp = false ;
2024-08-31 16:29:19 +00:00
2024-11-05 08:36:40 +00:00
#[Validate(['nullable', 'string'])]
2023-10-10 11:10:43 +00:00
public ? string $status = null ;
2024-06-10 20:43:34 +00:00
2024-11-05 08:36:40 +00:00
#[Validate(['required', 'boolean'])]
2024-11-04 11:40:10 +00:00
public bool $backupEnabled = false ;
2024-11-05 08:36:40 +00:00
#[Validate(['required', 'string'])]
2024-11-04 11:40:10 +00:00
public string $frequency = '' ;
2025-01-07 08:29:38 +00:00
#[Validate(['string'])]
2025-01-03 19:39:27 +00:00
public string $timezone = '' ;
2025-01-13 15:38:16 +00:00
#[Validate(['required', 'integer'])]
public int $databaseBackupRetentionAmountLocally = 0 ;
#[Validate(['required', 'integer'])]
public ? int $databaseBackupRetentionDaysLocally = 0 ;
2025-01-13 18:49:12 +00:00
#[Validate(['required', 'numeric', 'min:0'])]
public ? float $databaseBackupRetentionMaxStorageLocally = 0 ;
2025-01-13 15:38:16 +00:00
#[Validate(['required', 'integer'])]
public ? int $databaseBackupRetentionAmountS3 = 0 ;
#[Validate(['required', 'integer'])]
public ? int $databaseBackupRetentionDaysS3 = 0 ;
2025-01-13 18:49:12 +00:00
#[Validate(['required', 'numeric', 'min:0'])]
public ? float $databaseBackupRetentionMaxStorageS3 = 0 ;
2024-11-04 11:40:10 +00:00
2024-11-05 08:36:40 +00:00
#[Validate(['required', 'boolean'])]
2024-11-04 11:40:10 +00:00
public bool $saveS3 = false ;
2025-08-17 10:34:20 +00:00
#[Validate(['required', 'boolean'])]
public bool $disableLocalBackup = false ;
2025-01-13 20:26:20 +00:00
#[Validate(['nullable', 'integer'])]
2024-11-08 10:48:15 +00:00
public ? int $s3StorageId = 1 ;
2024-11-04 11:40:10 +00:00
2025-01-13 20:26:20 +00:00
#[Validate(['nullable', 'string'])]
2024-11-04 11:40:10 +00:00
public ? string $databasesToBackup = null ;
2024-11-05 08:36:40 +00:00
#[Validate(['required', 'boolean'])]
2024-11-04 11:40:10 +00:00
public bool $dumpAll = false ;
2023-08-10 13:52:54 +00:00
2025-11-11 11:07:35 +00:00
#[Validate(['required', 'int', 'min:60', 'max:36000'])]
2025-07-18 13:47:14 +00:00
public int $timeout = 3600 ;
2023-08-10 14:28:29 +00:00
public function mount ()
{
2024-11-04 11:40:10 +00:00
try {
2025-10-14 15:33:42 +00:00
$this -> authorize ( 'view' , $this -> backup -> database );
2024-11-04 11:40:10 +00:00
$this -> parameters = get_route_parameters ();
$this -> syncData ();
} catch ( Exception $e ) {
return handleError ( $e , $this );
}
}
public function syncData ( bool $toModel = false )
{
if ( $toModel ) {
$this -> backup -> enabled = $this -> backupEnabled ;
$this -> backup -> frequency = $this -> frequency ;
2025-01-13 15:38:16 +00:00
$this -> backup -> database_backup_retention_amount_locally = $this -> databaseBackupRetentionAmountLocally ;
$this -> backup -> database_backup_retention_days_locally = $this -> databaseBackupRetentionDaysLocally ;
2025-01-13 18:49:12 +00:00
$this -> backup -> database_backup_retention_max_storage_locally = $this -> databaseBackupRetentionMaxStorageLocally ;
2025-01-13 15:38:16 +00:00
$this -> backup -> database_backup_retention_amount_s3 = $this -> databaseBackupRetentionAmountS3 ;
$this -> backup -> database_backup_retention_days_s3 = $this -> databaseBackupRetentionDaysS3 ;
$this -> backup -> database_backup_retention_max_storage_s3 = $this -> databaseBackupRetentionMaxStorageS3 ;
2024-11-04 11:40:10 +00:00
$this -> backup -> save_s3 = $this -> saveS3 ;
2025-08-17 10:34:20 +00:00
$this -> backup -> disable_local_backup = $this -> disableLocalBackup ;
2024-11-04 11:40:10 +00:00
$this -> backup -> s3_storage_id = $this -> s3StorageId ;
2025-11-27 13:36:31 +00:00
// Validate databases_to_backup to prevent command injection
if ( filled ( $this -> databasesToBackup )) {
$databases = str ( $this -> databasesToBackup ) -> explode ( ',' );
2025-11-27 13:51:23 +00:00
foreach ( $databases as $index => $db ) {
$dbName = trim ( $db );
try {
validateShellSafePath ( $dbName , 'database name' );
} catch ( \Exception $e ) {
// Provide specific error message indicating which database failed validation
$position = $index + 1 ;
throw new \Exception (
" Database # { $position } (' { $dbName } ') validation failed: " .
$e -> getMessage ()
);
}
2025-11-27 13:36:31 +00:00
}
}
2024-11-04 11:40:10 +00:00
$this -> backup -> databases_to_backup = $this -> databasesToBackup ;
$this -> backup -> dump_all = $this -> dumpAll ;
2025-07-18 13:47:14 +00:00
$this -> backup -> timeout = $this -> timeout ;
2025-01-07 08:29:38 +00:00
$this -> customValidate ();
2024-11-04 11:40:10 +00:00
$this -> backup -> save ();
} else {
$this -> backupEnabled = $this -> backup -> enabled ;
$this -> frequency = $this -> backup -> frequency ;
2025-01-03 19:39:27 +00:00
$this -> timezone = data_get ( $this -> backup -> server (), 'settings.server_timezone' , 'Instance timezone' );
2025-01-13 15:38:16 +00:00
$this -> databaseBackupRetentionAmountLocally = $this -> backup -> database_backup_retention_amount_locally ;
$this -> databaseBackupRetentionDaysLocally = $this -> backup -> database_backup_retention_days_locally ;
2025-01-13 18:49:12 +00:00
$this -> databaseBackupRetentionMaxStorageLocally = $this -> backup -> database_backup_retention_max_storage_locally ;
2025-01-13 15:38:16 +00:00
$this -> databaseBackupRetentionAmountS3 = $this -> backup -> database_backup_retention_amount_s3 ;
$this -> databaseBackupRetentionDaysS3 = $this -> backup -> database_backup_retention_days_s3 ;
$this -> databaseBackupRetentionMaxStorageS3 = $this -> backup -> database_backup_retention_max_storage_s3 ;
2024-11-04 11:40:10 +00:00
$this -> saveS3 = $this -> backup -> save_s3 ;
2025-08-17 10:34:20 +00:00
$this -> disableLocalBackup = $this -> backup -> disable_local_backup ? ? false ;
2024-11-04 11:40:10 +00:00
$this -> s3StorageId = $this -> backup -> s3_storage_id ;
$this -> databasesToBackup = $this -> backup -> databases_to_backup ;
$this -> dumpAll = $this -> backup -> dump_all ;
2025-07-18 13:47:14 +00:00
$this -> timeout = $this -> backup -> timeout ;
2023-08-11 14:13:53 +00:00
}
2023-08-10 14:28:29 +00:00
}
2024-08-31 16:29:19 +00:00
public function delete ( $password )
2023-08-10 14:25:59 +00:00
{
2025-08-23 16:50:35 +00:00
$this -> authorize ( 'manageBackups' , $this -> backup -> database );
2025-12-12 13:12:02 +00:00
if ( ! verifyPasswordConfirmation ( $password , $this )) {
return ;
2024-08-31 16:29:19 +00:00
}
2024-03-21 11:44:32 +00:00
try {
2025-01-13 16:31:55 +00:00
$server = null ;
if ( $this -> backup -> database instanceof \App\Models\ServiceDatabase ) {
$server = $this -> backup -> database -> service -> destination -> server ;
} elseif ( $this -> backup -> database -> destination && $this -> backup -> database -> destination -> server ) {
$server = $this -> backup -> database -> destination -> server ;
2024-09-02 17:27:21 +00:00
}
2025-01-13 16:31:55 +00:00
$filenames = $this -> backup -> executions ()
-> whereNotNull ( 'filename' )
-> where ( 'filename' , '!=' , '' )
-> where ( 'scheduled_database_backup_id' , $this -> backup -> id )
-> pluck ( 'filename' )
-> filter ()
-> all ();
if ( ! empty ( $filenames )) {
if ( $this -> delete_associated_backups_locally && $server ) {
deleteBackupsLocally ( $filenames , $server );
}
if ( $this -> delete_associated_backups_s3 && $this -> backup -> s3 ) {
deleteBackupsS3 ( $filenames , $this -> backup -> s3 );
}
2024-08-31 16:29:19 +00:00
}
2024-03-21 11:44:32 +00:00
$this -> backup -> delete ();
2024-08-31 16:29:19 +00:00
2025-01-07 14:31:43 +00:00
if ( $this -> backup -> database -> getMorphClass () === \App\Models\ServiceDatabase :: class ) {
2026-01-02 15:29:48 +00:00
$serviceDatabase = $this -> backup -> database ;
return redirect () -> route ( 'project.service.database.backups' , [
'project_uuid' => $this -> parameters [ 'project_uuid' ],
'environment_uuid' => $this -> parameters [ 'environment_uuid' ],
'service_uuid' => $serviceDatabase -> service -> uuid ,
'stack_service_uuid' => $serviceDatabase -> uuid ,
]);
2025-01-07 14:31:43 +00:00
} else {
return redirect () -> route ( 'project.database.backup.index' , $this -> parameters );
2024-03-21 11:44:32 +00:00
}
2025-01-13 16:31:55 +00:00
} catch ( \Exception $e ) {
$this -> dispatch ( 'error' , 'Failed to delete backup: ' . $e -> getMessage ());
2024-03-21 11:44:32 +00:00
return handleError ( $e , $this );
2023-11-07 11:11:47 +00:00
}
2023-08-10 14:25:59 +00:00
}
2023-08-10 13:52:54 +00:00
public function instantSave ()
{
2023-08-11 14:13:53 +00:00
try {
2025-08-23 16:50:35 +00:00
$this -> authorize ( 'manageBackups' , $this -> backup -> database );
2024-11-04 11:40:10 +00:00
$this -> syncData ( true );
2024-03-21 11:44:32 +00:00
$this -> dispatch ( 'success' , 'Backup updated successfully.' );
2025-01-07 14:31:43 +00:00
} catch ( \Throwable $e ) {
2023-12-07 18:06:32 +00:00
$this -> dispatch ( 'error' , $e -> getMessage ());
2023-08-11 14:13:53 +00:00
}
2023-08-10 13:52:54 +00:00
}
2024-11-04 11:40:10 +00:00
private function customValidate ()
2023-08-10 13:52:54 +00:00
{
2024-06-10 20:43:34 +00:00
if ( ! is_numeric ( $this -> backup -> s3_storage_id )) {
2023-08-11 18:48:52 +00:00
$this -> backup -> s3_storage_id = null ;
}
2025-08-17 10:34:20 +00:00
// Validate that disable_local_backup can only be true when S3 backup is enabled
if ( $this -> backup -> disable_local_backup && ! $this -> backup -> save_s3 ) {
2025-10-07 13:02:23 +00:00
$this -> backup -> disable_local_backup = $this -> disableLocalBackup = false ;
2025-08-17 10:34:20 +00:00
}
2023-08-10 13:52:54 +00:00
$isValid = validate_cron_expression ( $this -> backup -> frequency );
2024-06-10 20:43:34 +00:00
if ( ! $isValid ) {
2025-01-07 14:31:43 +00:00
throw new \Exception ( 'Invalid Cron / Human expression' );
2023-08-10 13:52:54 +00:00
}
$this -> validate ();
2023-08-11 14:13:53 +00:00
}
public function submit ()
{
try {
2025-08-23 16:50:35 +00:00
$this -> authorize ( 'manageBackups' , $this -> backup -> database );
2024-11-04 11:40:10 +00:00
$this -> syncData ( true );
$this -> dispatch ( 'success' , 'Backup updated successfully.' );
2025-01-07 14:31:43 +00:00
} catch ( \Throwable $e ) {
2023-12-07 18:06:32 +00:00
$this -> dispatch ( 'error' , $e -> getMessage ());
2023-08-11 14:13:53 +00:00
}
2023-08-10 13:52:54 +00:00
}
2024-08-31 16:29:19 +00:00
public function render ()
{
return view ( 'livewire.project.database.backup-edit' , [
'checkboxes' => [
2024-09-27 15:29:36 +00:00
[ 'id' => 'delete_associated_backups_locally' , 'label' => __ ( 'database.delete_backups_locally' )],
2025-01-14 08:22:15 +00:00
[ 'id' => 'delete_associated_backups_s3' , 'label' => 'All backups will be permanently deleted (associated with this backup job) from the selected S3 Storage.' ],
2024-09-02 17:27:21 +00:00
// ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected SFTP Storage.']
2024-09-23 17:51:31 +00:00
],
2024-08-31 16:29:19 +00:00
]);
}
2023-08-10 13:52:54 +00:00
}