mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-02 14:36:17 +00:00
database backups
This commit is contained in:
@ -1,14 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Backup;
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Enums\BackupStatus;
|
||||
use App\Enums\DatabaseStatus;
|
||||
use App\Models\Backup;
|
||||
use App\Models\Database;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@ -19,23 +17,18 @@ class CreateBackup
|
||||
* @throws AuthorizationException
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function create($type, Server $server, User $user, array $input): Backup
|
||||
public function create($type, Server $server, array $input): Backup
|
||||
{
|
||||
$this->validate($type, $server, $user, $input);
|
||||
|
||||
if ($type == 'database') {
|
||||
Gate::forUser($user)->authorize('viewAny', [Database::class, $server]);
|
||||
}
|
||||
$this->validate($type, $server, $input);
|
||||
|
||||
$backup = new Backup([
|
||||
'name' => $input['name'],
|
||||
'type' => $type,
|
||||
'server_id' => $server->id,
|
||||
'database_id' => $input['database'] ?? null,
|
||||
'storage_id' => $input['storage'],
|
||||
'interval' => $input['interval'],
|
||||
'keep_backups' => $input['keep_backups'],
|
||||
'status' => 'running',
|
||||
'interval' => $input['interval'] == 'custom' ? $input['custom'] : $input['interval'],
|
||||
'keep_backups' => $input['keep'],
|
||||
'status' => BackupStatus::RUNNING,
|
||||
]);
|
||||
$backup->save();
|
||||
|
||||
@ -47,17 +40,14 @@ public function create($type, Server $server, User $user, array $input): Backup
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate($type, Server $server, User $user, array $input): void
|
||||
private function validate($type, Server $server, array $input): void
|
||||
{
|
||||
$rules = [
|
||||
'name' => 'required',
|
||||
'storage' => [
|
||||
'required',
|
||||
Rule::exists('storage_providers', 'id')
|
||||
->where('user_id', $user->id)
|
||||
->where('connected', 1),
|
||||
Rule::exists('storage_providers', 'id'),
|
||||
],
|
||||
'keep_backups' => [
|
||||
'keep' => [
|
||||
'required',
|
||||
'numeric',
|
||||
'min:1',
|
||||
@ -69,9 +59,15 @@ private function validate($type, Server $server, User $user, array $input): void
|
||||
'0 0 * * *',
|
||||
'0 0 * * 0',
|
||||
'0 0 1 * *',
|
||||
'custom'
|
||||
]),
|
||||
],
|
||||
];
|
||||
if ($input['interval'] == 'custom') {
|
||||
$rules['custom'] = [
|
||||
'required',
|
||||
];
|
||||
}
|
||||
if ($type === 'database') {
|
||||
$rules['database'] = [
|
||||
'required',
|
||||
@ -80,6 +76,6 @@ private function validate($type, Server $server, User $user, array $input): void
|
||||
->where('status', DatabaseStatus::READY),
|
||||
];
|
||||
}
|
||||
Validator::make($input, $rules)->validateWithBag('createBackup');
|
||||
Validator::make($input, $rules)->validate();
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\StorageProvider;
|
||||
|
||||
use App\Models\StorageProvider;
|
||||
use App\Models\User;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class AddStorageProvider
|
||||
{
|
||||
use ValidateProvider;
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function add(User $user, array $input): mixed
|
||||
{
|
||||
$this->validate($user, $input);
|
||||
|
||||
$storageProvider = new StorageProvider([
|
||||
'user_id' => $user->id,
|
||||
'provider' => $input['provider'],
|
||||
'label' => $input['label'],
|
||||
'connected' => false,
|
||||
]);
|
||||
$storageProvider->save();
|
||||
|
||||
return $storageProvider->provider()->connect();
|
||||
}
|
||||
}
|
52
app/Actions/StorageProvider/CreateStorageProvider.php
Normal file
52
app/Actions/StorageProvider/CreateStorageProvider.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\StorageProvider;
|
||||
|
||||
use App\Models\StorageProvider;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class CreateStorageProvider
|
||||
{
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function create(User $user, array $input): void
|
||||
{
|
||||
$this->validate($user, $input);
|
||||
|
||||
$storageProvider = new StorageProvider([
|
||||
'user_id' => $user->id,
|
||||
'provider' => $input['provider'],
|
||||
'profile' => $input['name'],
|
||||
'credentials' => [
|
||||
'token' => $input['token']
|
||||
]
|
||||
]);
|
||||
if (! $storageProvider->provider()->connect()) {
|
||||
throw ValidationException::withMessages([
|
||||
'token' => __("Couldn't connect to the provider")
|
||||
]);
|
||||
}
|
||||
$storageProvider->save();;
|
||||
}
|
||||
|
||||
private function validate(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'provider' => [
|
||||
'required',
|
||||
Rule::in(config('core.storage_providers')),
|
||||
],
|
||||
'name' => [
|
||||
'required',
|
||||
Rule::unique('storage_providers', 'profile')->where('user_id', $user->id),
|
||||
],
|
||||
'token' => [
|
||||
'required'
|
||||
]
|
||||
])->validate();
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\StorageProvider;
|
||||
|
||||
use App\Models\StorageProvider;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Laravel\Socialite\Two\User;
|
||||
use Throwable;
|
||||
|
||||
class HandleProviderCallback
|
||||
{
|
||||
public function callback(Request $request, string $provider): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$providerId = $request->session()->get('storage_provider_id');
|
||||
/** @var StorageProvider $storageProvider */
|
||||
$storageProvider = StorageProvider::query()->findOrFail($providerId);
|
||||
/** @var User $oauthUser */
|
||||
$oauthUser = Socialite::driver($provider)->user();
|
||||
$storageProvider->token = $oauthUser->token;
|
||||
$storageProvider->refresh_token = $oauthUser->refreshToken;
|
||||
$storageProvider->token_expires_at = now()->addSeconds($oauthUser->expiresIn);
|
||||
$storageProvider->connected = true;
|
||||
$storageProvider->save();
|
||||
/** @TODO toast success message */
|
||||
} catch (Throwable) {
|
||||
/** @TODO toast failed message */
|
||||
}
|
||||
|
||||
return redirect()->route('storage-providers');
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\StorageProvider;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
trait ValidateProvider
|
||||
{
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'label' => [
|
||||
'required',
|
||||
Rule::unique('storage_providers', 'label')->where('user_id', $user->id),
|
||||
],
|
||||
'provider' => [
|
||||
'required',
|
||||
Rule::in(config('core.storage_providers')),
|
||||
],
|
||||
])->validateWithBag('addStorageProvider');
|
||||
}
|
||||
}
|
49
app/Console/Commands/RunBackup.php
Normal file
49
app/Console/Commands/RunBackup.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Backup;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class RunBackup extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'backups:run {interval}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Run backup';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
Backup::query()
|
||||
->where('interval', $this->argument('interval'))
|
||||
->where('status', 'running')
|
||||
->chunk(100, function ($backups) {
|
||||
/** @var Backup $backup */
|
||||
foreach ($backups as $backup) {
|
||||
$backup->run();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -12,7 +12,10 @@ class Kernel extends ConsoleKernel
|
||||
*/
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
// $schedule->command('inspire')->hourly();
|
||||
$schedule->command('backups:run "0 * * * *"')->hourly();
|
||||
$schedule->command('backups:run "0 0 * * *"')->daily();
|
||||
$schedule->command('backups:run "0 0 * * 0"')->weekly();
|
||||
$schedule->command('backups:run "0 0 1 * *"')->monthly();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
interface StorageProvider
|
||||
{
|
||||
public function connect(): RedirectResponse;
|
||||
public function connect(): bool;
|
||||
|
||||
public function upload(Server $server, string $src, string $dest): array;
|
||||
|
||||
|
22
app/Enums/BackupFileStatus.php
Normal file
22
app/Enums/BackupFileStatus.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BenSampo\Enum\Enum;
|
||||
|
||||
final class BackupFileStatus extends Enum
|
||||
{
|
||||
const CREATED = 'created';
|
||||
|
||||
const CREATING = 'creating';
|
||||
|
||||
const FAILED = 'failed';
|
||||
|
||||
const DELETING = 'deleting';
|
||||
|
||||
const RESTORING = 'restoring';
|
||||
|
||||
const RESTORED = 'restored';
|
||||
|
||||
const RESTORE_FAILED = 'restore_failed';
|
||||
}
|
16
app/Enums/BackupStatus.php
Normal file
16
app/Enums/BackupStatus.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BenSampo\Enum\Enum;
|
||||
|
||||
final class BackupStatus extends Enum
|
||||
{
|
||||
const READY = 'ready';
|
||||
|
||||
const RUNNING = 'running';
|
||||
|
||||
const FAILED = 'failed';
|
||||
|
||||
const DELETING = 'deleting';
|
||||
}
|
9
app/Exceptions/BackupFileException.php
Normal file
9
app/Exceptions/BackupFileException.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class BackupFileException extends Exception
|
||||
{
|
||||
}
|
@ -2,14 +2,24 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Backup;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
class DatabaseController extends Controller
|
||||
{
|
||||
public function index(Server $server)
|
||||
public function index(Server $server): View
|
||||
{
|
||||
return view('databases.index', [
|
||||
'server' => $server,
|
||||
]);
|
||||
}
|
||||
|
||||
public function backups(Server $server, Backup $backup): View
|
||||
{
|
||||
return view('databases.backups', [
|
||||
'server' => $server,
|
||||
'backup' => $backup,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
67
app/Http/Livewire/Databases/DatabaseBackupFiles.php
Normal file
67
app/Http/Livewire/Databases/DatabaseBackupFiles.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Databases;
|
||||
|
||||
use App\Models\Backup;
|
||||
use App\Models\BackupFile;
|
||||
use App\Models\Database;
|
||||
use App\Models\Server;
|
||||
use App\Traits\HasCustomPaginationView;
|
||||
use App\Traits\RefreshComponentOnBroadcast;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
class DatabaseBackupFiles extends Component
|
||||
{
|
||||
use HasCustomPaginationView;
|
||||
use RefreshComponentOnBroadcast;
|
||||
|
||||
public Server $server;
|
||||
|
||||
public Backup $backup;
|
||||
|
||||
public string $restoreId = "";
|
||||
|
||||
public string $restoreDatabaseId = "";
|
||||
|
||||
public int $deleteId;
|
||||
|
||||
public function backup(): void
|
||||
{
|
||||
$this->backup->run();
|
||||
|
||||
$this->refreshComponent([]);
|
||||
}
|
||||
|
||||
public function restore(): void
|
||||
{
|
||||
/** @var BackupFile $file */
|
||||
$file = BackupFile::query()->findOrFail($this->restoreId);
|
||||
|
||||
/** @var Database $database */
|
||||
$database = Database::query()->findOrFail($this->restoreDatabaseId);
|
||||
|
||||
$file->restore($database);
|
||||
|
||||
$this->refreshComponent([]);
|
||||
|
||||
$this->dispatchBrowserEvent('restored', true);
|
||||
}
|
||||
|
||||
public function delete(): void
|
||||
{
|
||||
/** @var BackupFile $file */
|
||||
$file = BackupFile::query()->findOrFail($this->deleteId);
|
||||
|
||||
$file->delete();
|
||||
|
||||
$this->dispatchBrowserEvent('confirmed', true);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.databases.database-backup-files', [
|
||||
'files' => $this->backup->files()->orderByDesc('id')->simplePaginate(10)
|
||||
]);
|
||||
}
|
||||
}
|
81
app/Http/Livewire/Databases/DatabaseBackups.php
Normal file
81
app/Http/Livewire/Databases/DatabaseBackups.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Databases;
|
||||
|
||||
use App\Actions\Database\CreateBackup;
|
||||
use App\Models\Backup;
|
||||
use App\Models\Server;
|
||||
use App\Traits\RefreshComponentOnBroadcast;
|
||||
use Illuminate\Contracts\Pagination\Paginator;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class DatabaseBackups extends Component
|
||||
{
|
||||
use RefreshComponentOnBroadcast;
|
||||
use WithPagination;
|
||||
|
||||
public Server $server;
|
||||
|
||||
public int $deleteId;
|
||||
|
||||
public string $database = '';
|
||||
|
||||
public string $storage = '';
|
||||
|
||||
public string $interval = '';
|
||||
|
||||
public string $custom = '';
|
||||
|
||||
public int $keep = 10;
|
||||
|
||||
public ?Backup $backup = null;
|
||||
|
||||
protected ?Paginator $files = null;
|
||||
|
||||
public function create(): void
|
||||
{
|
||||
app(CreateBackup::class)->create('database', $this->server, $this->all());
|
||||
|
||||
$this->refreshComponent([]);
|
||||
|
||||
$this->dispatchBrowserEvent('backup-created', true);
|
||||
}
|
||||
|
||||
public function files(int $id): void
|
||||
{
|
||||
$backup = Backup::query()->findOrFail($id);
|
||||
$this->backup = $backup;
|
||||
$this->files = $backup->files()->orderByDesc('id')->simplePaginate(1);
|
||||
$this->dispatchBrowserEvent('show-files', true);
|
||||
}
|
||||
|
||||
public function backup(): void
|
||||
{
|
||||
$this->backup?->run();
|
||||
|
||||
$this->files = $this->backup?->files()->orderByDesc('id')->simplePaginate();
|
||||
|
||||
$this->dispatchBrowserEvent('backup-running', true);
|
||||
}
|
||||
|
||||
public function delete(): void
|
||||
{
|
||||
/** @var Backup $backup */
|
||||
$backup = Backup::query()->findOrFail($this->deleteId);
|
||||
|
||||
$backup->delete();
|
||||
|
||||
$this->dispatchBrowserEvent('confirmed', true);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.databases.database-backups', [
|
||||
'backups' => $this->server->backups,
|
||||
'databases' => $this->server->databases,
|
||||
'files' => $this->files
|
||||
]);
|
||||
}
|
||||
}
|
30
app/Http/Livewire/StorageProviders/ConnectProvider.php
Normal file
30
app/Http/Livewire/StorageProviders/ConnectProvider.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\StorageProviders;
|
||||
|
||||
use App\Actions\StorageProvider\CreateStorageProvider;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
class ConnectProvider extends Component
|
||||
{
|
||||
public string $provider = '';
|
||||
|
||||
public string $name;
|
||||
|
||||
public string $token;
|
||||
|
||||
public function connect(): void
|
||||
{
|
||||
app(CreateStorageProvider::class)->create(auth()->user(), $this->all());
|
||||
|
||||
$this->emitTo(ProvidersList::class, '$refresh');
|
||||
|
||||
$this->dispatchBrowserEvent('connected', true);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.storage-providers.connect-provider');
|
||||
}
|
||||
}
|
37
app/Http/Livewire/StorageProviders/ProvidersList.php
Normal file
37
app/Http/Livewire/StorageProviders/ProvidersList.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\StorageProviders;
|
||||
|
||||
use App\Models\StorageProvider;
|
||||
use App\Traits\RefreshComponentOnBroadcast;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
class ProvidersList extends Component
|
||||
{
|
||||
use RefreshComponentOnBroadcast;
|
||||
|
||||
public int $deleteId;
|
||||
|
||||
protected $listeners = [
|
||||
'$refresh',
|
||||
];
|
||||
|
||||
public function delete(): void
|
||||
{
|
||||
$provider = StorageProvider::query()->findOrFail($this->deleteId);
|
||||
|
||||
$provider->delete();
|
||||
|
||||
$this->refreshComponent([]);
|
||||
|
||||
$this->dispatchBrowserEvent('confirmed', true);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.storage-providers.providers-list', [
|
||||
'providers' => StorageProvider::query()->latest()->get(),
|
||||
]);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Jobs\Backup;
|
||||
|
||||
use App\Enums\BackupFileStatus;
|
||||
use App\Events\Broadcast;
|
||||
use App\Jobs\Job;
|
||||
use App\Models\BackupFile;
|
||||
@ -23,7 +24,7 @@ public function handle(): void
|
||||
{
|
||||
$this->database->server->database()->handler()->restoreBackup($this->backupFile, $this->database->name);
|
||||
|
||||
$this->backupFile->status = 'restored';
|
||||
$this->backupFile->status = BackupFileStatus::RESTORED;
|
||||
$this->backupFile->restored_at = now();
|
||||
$this->backupFile->save();
|
||||
|
||||
@ -36,7 +37,7 @@ public function handle(): void
|
||||
|
||||
public function failed(): void
|
||||
{
|
||||
$this->backupFile->status = 'restore_failed';
|
||||
$this->backupFile->status = BackupFileStatus::RESTORE_FAILED;
|
||||
$this->backupFile->save();
|
||||
event(
|
||||
new Broadcast('backup-restore-failed', [
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Jobs\Backup;
|
||||
|
||||
use App\Enums\BackupFileStatus;
|
||||
use App\Events\Broadcast;
|
||||
use App\Jobs\Job;
|
||||
use App\Models\BackupFile;
|
||||
@ -21,7 +22,7 @@ public function handle(): void
|
||||
$this->backupFile->backup->server->database()->handler()->runBackup($this->backupFile);
|
||||
}
|
||||
|
||||
$this->backupFile->status = 'finished';
|
||||
$this->backupFile->status = BackupFileStatus::CREATED;
|
||||
$this->backupFile->save();
|
||||
|
||||
event(
|
||||
@ -33,7 +34,7 @@ public function handle(): void
|
||||
|
||||
public function failed(): void
|
||||
{
|
||||
$this->backupFile->status = 'failed';
|
||||
$this->backupFile->status = BackupFileStatus::FAILED;
|
||||
$this->backupFile->save();
|
||||
event(
|
||||
new Broadcast('run-backup-failed', [
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\BackupFileStatus;
|
||||
use App\Jobs\Backup\RunBackup;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@ -10,7 +11,6 @@
|
||||
|
||||
/**
|
||||
* @property string $type
|
||||
* @property string $name
|
||||
* @property int $server_id
|
||||
* @property int $storage_id
|
||||
* @property int $database_id
|
||||
@ -28,7 +28,6 @@ class Backup extends AbstractModel
|
||||
|
||||
protected $fillable = [
|
||||
'type',
|
||||
'name',
|
||||
'server_id',
|
||||
'storage_id',
|
||||
'database_id',
|
||||
@ -77,8 +76,8 @@ public function run(): void
|
||||
{
|
||||
$file = new BackupFile([
|
||||
'backup_id' => $this->id,
|
||||
'name' => Str::of($this->name)->slug().'-'.now()->format('YmdHis'),
|
||||
'status' => 'creating',
|
||||
'name' => Str::of($this->database->name)->slug().'-'.now()->format('YmdHis'),
|
||||
'status' => BackupFileStatus::CREATING,
|
||||
]);
|
||||
$file->save();
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\BackupFileStatus;
|
||||
use App\Jobs\Backup\RestoreDatabase;
|
||||
use App\Jobs\StorageProvider\DeleteFile;
|
||||
use Carbon\Carbon;
|
||||
@ -75,12 +76,12 @@ public function getPathAttribute(): string
|
||||
|
||||
public function getStoragePathAttribute(): string
|
||||
{
|
||||
return '/'.$this->backup->name.'/'.$this->name.'.zip';
|
||||
return '/'.$this->backup->database->name.'/'.$this->name.'.zip';
|
||||
}
|
||||
|
||||
public function restore(Database $database): void
|
||||
{
|
||||
$this->status = 'restoring';
|
||||
$this->status = BackupFileStatus::RESTORING;
|
||||
$this->restored_to = $database->name;
|
||||
$this->save();
|
||||
dispatch(new RestoreDatabase($this, $database))->onConnection('ssh');
|
||||
|
@ -2,18 +2,14 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $user_id
|
||||
* @property string $profile
|
||||
* @property string $provider
|
||||
* @property string $label
|
||||
* @property string $token
|
||||
* @property string $refresh_token
|
||||
* @property bool $connected
|
||||
* @property Carbon $token_expires_at
|
||||
* @property array $credentials
|
||||
* @property User $user
|
||||
*/
|
||||
class StorageProvider extends AbstractModel
|
||||
@ -22,18 +18,14 @@ class StorageProvider extends AbstractModel
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'profile',
|
||||
'provider',
|
||||
'label',
|
||||
'token',
|
||||
'refresh_token',
|
||||
'connected',
|
||||
'token_expires_at',
|
||||
'credentials',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'user_id' => 'integer',
|
||||
'connected' => 'boolean',
|
||||
'token_expires_at' => 'datetime',
|
||||
'credentials' => 'encrypted:array',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
|
@ -99,7 +99,7 @@ public function runBackup(BackupFile $backupFile): void
|
||||
'backup-database'
|
||||
);
|
||||
|
||||
// upload to cloud
|
||||
// upload to storage
|
||||
$upload = $backupFile->backup->storage->provider()->upload(
|
||||
$backupFile->backup->server,
|
||||
$backupFile->path,
|
||||
|
@ -2,21 +2,25 @@
|
||||
|
||||
namespace App\StorageProviders;
|
||||
|
||||
use App\Exceptions\BackupFileException;
|
||||
use App\Models\Server;
|
||||
use App\SSHCommands\Storage\DownloadFromDropboxCommand;
|
||||
use App\SSHCommands\Storage\UploadToDropboxCommand;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Throwable;
|
||||
|
||||
class Dropbox extends AbstractStorageProvider
|
||||
{
|
||||
public function connect(): RedirectResponse
|
||||
{
|
||||
session()->put('storage_provider_id', $this->storageProvider->id);
|
||||
protected string $apiUrl = 'https://api.dropboxapi.com/2';
|
||||
|
||||
return Socialite::driver('dropbox')->redirect();
|
||||
public function connect(): bool
|
||||
{
|
||||
$res = Http::withToken($this->storageProvider->credentials['token'])
|
||||
->post($this->apiUrl.'/check/user', [
|
||||
'query' => ''
|
||||
]);
|
||||
|
||||
return $res->successful();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -28,15 +32,19 @@ public function upload(Server $server, string $src, string $dest): array
|
||||
new UploadToDropboxCommand(
|
||||
$src,
|
||||
$dest,
|
||||
$this->storageProvider->token
|
||||
$this->storageProvider->credentials['token']
|
||||
),
|
||||
'upload-to-dropbox'
|
||||
);
|
||||
|
||||
$data = json_decode($upload);
|
||||
$data = json_decode($upload, true);
|
||||
|
||||
if (isset($data['error'])) {
|
||||
throw new BackupFileException("Failed to upload to Dropbox ".$data['error_summary'] ?? '');
|
||||
}
|
||||
|
||||
return [
|
||||
'size' => $data?->size,
|
||||
'size' => $data['size'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
@ -49,7 +57,7 @@ public function download(Server $server, string $src, string $dest): void
|
||||
new DownloadFromDropboxCommand(
|
||||
$src,
|
||||
$dest,
|
||||
$this->storageProvider->token
|
||||
$this->storageProvider->credentials['token']
|
||||
),
|
||||
'download-from-dropbox'
|
||||
);
|
||||
@ -61,11 +69,11 @@ public function delete(array $paths): void
|
||||
foreach ($paths as $path) {
|
||||
$data[] = ['path' => $path];
|
||||
}
|
||||
Http::withToken($this->storageProvider->token)
|
||||
Http::withToken($this->storageProvider->credentials['token'])
|
||||
->withHeaders([
|
||||
'Content-Type:application/json',
|
||||
])
|
||||
->post('https://api.dropboxapi.com/2/files/delete_batch', [
|
||||
->post($this->apiUrl.'/files/delete_batch', [
|
||||
'entries' => $data,
|
||||
]);
|
||||
}
|
||||
|
Reference in New Issue
Block a user