Browse Source

Update database backup and restore system feature

pull/1/head
Nafies Luthfi 8 years ago
parent
commit
34557310ab
  1. 92
      app/Entities/Backups/BackupsRepository.php
  2. 171
      app/Http/Controllers/BackupsController.php
  3. 52
      app/Http/Requests/Backups/BackupUploadRequest.php
  4. 31
      app/Http/Requests/Backups/CreateRequest.php
  5. 31
      app/Http/Requests/Backups/DeleteRequest.php
  6. 40
      resources/lang/en/backup.php
  7. 21
      resources/views/backups/delete.blade.php
  8. 70
      resources/views/backups/forms.blade.php
  9. 95
      resources/views/backups/index.blade.php
  10. 22
      resources/views/backups/restore.blade.php
  11. 13
      routes/web.php
  12. 14
      routes/web/backup.php

92
app/Entities/Backups/BackupsRepository.php

@ -1,92 +0,0 @@
<?php
namespace App\Entities\Backups;
use App\Entities\Options\Option;
use BackupManager\Filesystems\Destination;
use BackupManager\Manager;
use League\Flysystem\FileExistsException;
use League\Flysystem\FileNotFoundException;
use Storage;
/**
* Backups Repository Class
*/
class BackupsRepository
{
protected $storage;
protected $storageType;
public function __construct()
{
$this->storageType = 'local';
$this->storage = Storage::disk($this->storageType);
}
public function getAllFiles()
{
$backups = \File::allFiles(storage_path('app/backup/db'));
// Sort files by modified time DESC
usort($backups, function($a, $b) {
return -1 * strcmp($a->getMTime(), $b->getMTime());
});
return $backups;
}
public function create($backupData)
{
$manager = app()->make(Manager::class);
$fileName = $backupData['file_name'] ?: date('Y-m-d_Hi');
try {
$manager->makeBackup()->run('mysql', [
new Destination($this->storageType, 'backup/db/' . $fileName)
], 'gzip');
return $fileName;
} catch (FileExistsException $e) {
flash()->error('Database tidak dapat dibackup dengan Nama File yang sama.');
return false;
}
}
public function restore($fileName)
{
try {
$manager = app()->make(Manager::class);
$manager->makeRestore()->run($this->storageType, 'backup/db/' . $fileName, 'mysql', 'gzip');
return true;
} catch (FileNotFoundException $e) {
flash()->error('Tidak dapat mengembalikan Database.');
return false;
}
}
public function delete($fileName)
{
if ($this->storage->has('backup/db/' . $fileName)) {
$this->storage->delete('backup/db/' . $fileName);
return true;
}
return false;
}
public function proccessBackupFileUpload($req)
{
$file = $req->file('backup_file');
if ($this->storage->has('backup/db/' . $file->getClientOriginalName())) {
flash()->error('Upload file gagal, terdapat Nama File yang sama.');
return false;
}
$result = $this->storage->put('backup/db/' . $file->getClientOriginalName(), file_get_contents($file));
return $result;
}
}

171
app/Http/Controllers/BackupsController.php

@ -2,83 +2,98 @@
namespace App\Http\Controllers;
use App\Http\Requests\Backups\CreateRequest;
use App\Http\Requests\Backups\BackupUploadRequest;
use App\Http\Requests\Backups\DeleteRequest;
use App\Http\Controllers\Controller;
use App\Entities\Backups\BackupsRepository;
use App\Http\Requests\BackupUploadRequest;
use BackupManager\Filesystems\Destination;
use BackupManager\Manager;
use Illuminate\Http\Request;
class BackupsController extends Controller {
private $repo;
public function __construct(BackupsRepository $repo)
{
$this->repo = $repo;
}
public function index(Request $req)
{
$backups = $this->repo->getAllFiles();
return view('backups.index',compact('backups'));
}
public function restore($fileName)
{
return view('backups.restore', compact('fileName'));
}
public function postRestore(Request $req, $fileName)
{
$result = $this->repo->restore($fileName);
if ($result)
flash()->success('Database berhasil dikembalikan dengan file ' . $fileName);
return redirect()->route('backups.index');
}
public function store(CreateRequest $req)
{
$fileName = $this->repo->create($req->except('_token'));
if ($fileName)
flash()->success('Backup berhasil dilakukan, nama File : ' . $fileName);
return redirect()->route('backups.index');
}
public function delete($fileName)
{
return view('backups.delete', compact('fileName'));
}
public function destroy(Request $req, $fileName)
{
$result = $this->repo->delete($fileName);
if ($result)
flash()->success('File ' . $fileName . ' berhasil dihapus.');
else
flash()->error('File ' . $fileName . ' gagal dihapus.');
return redirect()->route('backups.index');
}
public function upload(BackupUploadRequest $req)
{
$result = $this->repo->proccessBackupFileUpload($req);
if ($result)
flash()->success('Upload file berhasil.');
return redirect()->route('backups.index');
}
public function download($fileName)
{
return response()->download(storage_path('app') . '/backup/db/'.$fileName);
}
use League\Flysystem\FileExistsException;
use League\Flysystem\FileNotFoundException;
class BackupsController extends Controller
{
public function index(Request $request)
{
if (!file_exists(storage_path('app/backup/db'))) {
$backups = [];
} else {
$backups = \File::allFiles(storage_path('app/backup/db'));
// Sort files by modified time DESC
usort($backups, function ($a, $b) {
return -1 * strcmp($a->getMTime(), $b->getMTime());
});
}
return view('backups.index', compact('backups'));
}
public function store(Request $request)
{
$this->validate($request, [
'file_name' => 'nullable|max:30|regex:/^[\w._-]+$/',
]);
try {
$manager = app()->make(Manager::class);
$fileName = $request->get('file_name') ?: date('Y-m-d_Hi');
$manager->makeBackup()->run('mysql', [
new Destination('local', 'backup/db/'.$fileName),
], 'gzip');
flash(trans('backup.created', ['filename' => $fileName.'.gz']), 'success');
return redirect()->route('backups.index');
} catch (FileExistsException $e) {
flash(trans('backup.not_created', ['filename' => $fileName.'.gz']), 'danger');
return redirect()->route('backups.index');
}
}
public function destroy($fileName)
{
if (file_exists(storage_path('app/backup/db/').$fileName)) {
unlink(storage_path('app/backup/db/').$fileName);
}
flash(trans('backup.deleted', ['filename' => $fileName]), 'warning');
return redirect()->route('backups.index');
}
public function download($fileName)
{
return response()->download(storage_path('app/backup/db/').$fileName);
}
public function restore($fileName)
{
try {
$manager = app()->make(Manager::class);
$manager->makeRestore()->run('local', 'backup/db/'.$fileName, 'mysql', 'gzip');
} catch (FileNotFoundException $e) {
}
flash(trans('backup.restored', ['filename' => $fileName]), 'success');
return redirect()->route('backups.index');
}
public function upload(Request $request)
{
$data = $request->validate([
'backup_file' => 'required|mimetypes:application/x-gzip',
], [
'backup_file.mimetypes' => 'Invalid file type, must be <strong>.gz</strong> file',
]);
$file = $data['backup_file'];
$fileName = $file->getClientOriginalName();
if (file_exists(storage_path('app/backup/db/').$fileName) == false) {
$file->storeAs('backup/db', $fileName);
}
flash(trans('backup.uploaded', ['filename' => $fileName]), 'success');
return redirect()->route('backups.index');
}
}

52
app/Http/Requests/Backups/BackupUploadRequest.php

@ -1,52 +0,0 @@
<?php
namespace App\Http\Requests\Backups;
use App\Http\Requests\Request;
class BackupUploadRequest extends Request {
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->user()->can('manage_backups');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'backup_file' => 'required|sql_zip'
];
}
public function messages()
{
return [
'file.sql_zip' => 'Isian file harus dokumen berjenis .zip, .gz atau .sql',
];
}
protected function getValidatorInstance()
{
$validator = parent::getValidatorInstance();
$validator->addImplicitExtension('sql_zip', function($attribute, $value, $parameters) {
if ($value)
return in_array($value->getClientOriginalExtension(), ['zip','gz','sql']);
return false;
});
return $validator;
}
}

31
app/Http/Requests/Backups/CreateRequest.php

@ -1,31 +0,0 @@
<?php
namespace App\Http\Requests\Backups;
use App\Http\Requests\Request;
class CreateRequest extends Request {
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->user()->can('manage_backups');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'file_name' => 'nullable|max:20|alpha_dash',
];
}
}

31
app/Http/Requests/Backups/DeleteRequest.php

@ -1,31 +0,0 @@
<?php
namespace App\Http\Requests\Backups;
use App\Http\Requests\Request;
class DeleteRequest extends Request {
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->user()->can('manage_backups');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'file_name' => 'required'
];
}
}

40
resources/lang/en/backup.php

@ -0,0 +1,40 @@
<?php
return [
// Labels
'index_title' => 'Database Backup Manager',
'list' => 'Backup File List',
'file_name' => 'File Name',
'file_size' => 'File Size',
'created_at' => 'Created at',
'actions' => 'Actions',
'empty' => 'No backup file available.',
// Create backup file
'create' => 'Create Backup File',
'created' => 'Backup file :filename created.',
'not_created' => 'Backup file named :filename already exists.',
// Delete backup file
'delete' => 'Delete',
'delete_title' => 'Delete this backup file',
'sure_to_delete_file' => 'Are you sure to delete this file <strong>":filename"</strong>?',
'cancel_delete' => 'Cancel Delete',
'confirm_delete' => 'YES, please delete this file!',
'deleted' => 'Backup file :filename has been deleted!',
// Download backup file
'download' => 'Download',
// Restore backup
'restore' => 'Restore',
'restore_title' => 'Restore database from file',
'sure_to_restore' => 'Are you sure to restore database with this backup file "<strong>:filename</strong>"? <br><br>Please make sure your <strong>current database has been backed up</strong>.',
'cancel_restore' => 'Cancel Restore',
'confirm_restore' => 'YES, Restore Database!',
'restored' => 'Database restored with backup file :filename',
// Upload backup fle
'upload' => 'Upload Backup File',
'uploaded' => 'Backup file :filename uploaded.',
];

21
resources/views/backups/delete.blade.php

@ -1,21 +0,0 @@
@extends('layouts.app')
@section('content')
<br>
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">
Apakah anda yakin akan menghapus file backup <strong>"{{ $fileName }}"</strong> ini?
</h3>
</div>
<div class="panel-footer">
{!! link_to_route('backups.index', trans('app.cancel'), [], ['class' => 'btn btn-default']) !!}
<div class="pull-right">
{!! Form::open(['route'=>['backups.destroy', $fileName], 'method' => 'delete']) !!}
{!! Form::hidden('file_name', $fileName) !!}
{!! Form::submit('Hapus File', ['class'=>'btn btn-danger']) !!}
{!! Form::close() !!}
</div>
</div>
</div>
@endsection

70
resources/views/backups/forms.blade.php

@ -0,0 +1,70 @@
@if (request('action') == 'delete' && Request::has('file_name'))
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">@lang('backup.delete')</h3>
</div>
<div class="panel-body">
<p>@lang('backup.sure_to_delete_file', ['filename' => request('file_name')])</p>
</div>
<div class="panel-footer">
<a href="{{ route('backups.index') }}" class="btn btn-default">@lang('backup.cancel_delete')</a>
<form action="{{ route('backups.destroy', request('file_name')) }}"
method="post"
class="pull-right"
onsubmit="return confirm('Click OK to Delete.')">
{{ method_field('delete') }}
{{ csrf_field() }}
<input type="hidden" name="file_name" value="{{ request('file_name') }}">
<input type="submit" class="btn btn-danger" value="@lang('backup.confirm_delete')">
</form>
</div>
</div>
@endif
@if (request('action') == 'restore' && Request::has('file_name'))
<div class="panel panel-warning">
<div class="panel-heading"><h3 class="panel-title">@lang('backup.restore')</h3></div>
<div class="panel-body">
<p>@lang('backup.sure_to_restore', ['filename' => request('file_name')])</p>
</div>
<div class="panel-footer">
<a href="{{ route('backups.index') }}" class="btn btn-default">@lang('backup.cancel_restore')</a>
<form action="{{ route('backups.restore', request('file_name')) }}"
method="post"
class="pull-right"
onsubmit="return confirm('Click OK to Restore.')">
{{ csrf_field() }}
<input type="hidden" name="file_name" value="{{ request('file_name') }}">
<input type="submit" class="btn btn-warning" value="@lang('backup.confirm_restore')">
</form>
</div>
</div>
@endif
@if (request('action') == null)
<div class="panel panel-default">
<div class="panel-body">
<form action="{{ route('backups.store') }}" method="post">
{{ csrf_field() }}
<div class="form-group">
<label for="file_name" class="control-label">@lang('backup.create')</label>
<input type="text" name="file_name" class="form-control" placeholder="{{ date('Y-m-d_Hi') }}">
{!! $errors->first('file_name', '<div class="text-danger text-right">:message</div>') !!}
</div>
<div class="form-group">
<input type="submit" value="@lang('backup.create')" class="btn btn-success">
</div>
</form>
<hr>
<form action="{{ route('backups.upload') }}" method="post" enctype="multipart/form-data">
{{ csrf_field() }}
<div class="form-group">
<label for="backup_file" class="control-label">@lang('backup.upload')</label>
<input type="file" name="backup_file" class="form-control">
{!! $errors->first('backup_file', '<div class="text-danger text-right">:message</div>') !!}
</div>
<div class="form-group">
<input type="submit" value="@lang('backup.upload')" class="btn btn-primary">
</div>
</form>
</div>
</div>
@endif

95
resources/views/backups/index.blade.php

@ -1,64 +1,57 @@
@extends('layouts.app')
@section('title','Daftar Backup Database Sistem')
@section('title',trans('backup.index_title'))
@section('content')
<h1 class="page-header">
Daftar Backup Database Sistem
</h1>
<ul class="breadcrumb hidden-print">
<li><a href="{{ route('backups.index') }}">@lang('backup.index_title')</a></li>
<li class="active">@lang('backup.list')</li>
</ul>
<div class="row">
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">Daftar File Backup</h3></div>
<div class="panel-body">
<table class="table table-condensed">
<thead>
<th>#</th>
<th>Nama File</th>
<th>Ukuran</th>
<th>Tanggal Jam</th>
<th>{{ trans('app.action') }}</th>
</thead>
<tbody>
@forelse($backups as $key => $backup)
<tr>
<td>{{ $key + 1 }}</td>
<td>{{ $backup->getFilename() }}</td>
<td>{{ formatSizeUnits($backup->getSize()) }}</td>
<td>{{ date('Y-m-d H:i:s', $backup->getMTime()) }}</td>
<td>
{!! link_to_route('backups.download','Download',[$backup->getFilename()],['class'=>'btn btn-default btn-xs','target' => '_blank']) !!}
{!! link_to_route('backups.restore','Restore',[$backup->getFilename()],['class'=>'btn btn-warning btn-xs']) !!}
{!! link_to_route('backups.delete','x',[$backup->getFilename()],['class'=>'btn btn-danger btn-xs']) !!}
</td>
</tr>
@empty
<tr>
<td colspan="3">Belum ada file backup</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<table class="table table-condensed">
<thead>
<th>@lang('app.table_no')</th>
<th>@lang('backup.file_name')</th>
<th>@lang('backup.file_size')</th>
<th>@lang('backup.created_at')</th>
<th class="text-center">@lang('backup.actions')</th>
</thead>
<tbody>
@forelse($backups as $key => $backup)
<tr>
<td>{{ $key + 1 }}</td>
<td>{{ $backup->getFilename() }}</td>
<td>{{ formatSizeUnits($backup->getSize()) }}</td>
<td>{{ date('Y-m-d H:i:s', $backup->getMTime()) }}</td>
<td class="text-center">
<a href="{{ route('backups.index', ['action' => 'restore', 'file_name' => $backup->getFilename()]) }}"
id="restore_{{ str_replace('.gz', '', $backup->getFilename()) }}"
class="btn btn-warning btn-xs"
title="@lang('backup.download')">@lang('backup.restore')</a>
<a href="{{ route('backups.download', [$backup->getFilename()]) }}"
id="download_{{ str_replace('.gz', '', $backup->getFilename()) }}"
class="btn btn-info btn-xs"
title="@lang('backup.download')">@lang('backup.download')</a>
<a href="{{ route('backups.index', ['action' => 'delete', 'file_name' => $backup->getFilename()]) }}"
id="del_{{ str_replace('.gz', '', $backup->getFilename()) }}"
class="btn btn-danger btn-xs"
title="@lang('backup.delete')">@lang('backup.delete')</a>
</td>
</tr>
@empty
<tr>
<td colspan="3">@lang('backup.empty')</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-body">
{!! Form::open(['route' => 'backups.store','class' => '']) !!}
{!! FormField::text('file_name', ['label' => 'Buat Backup Database', 'placeholder' => date('Y-m-d_Hi')]) !!}
{!! Form::submit('Buat Backup', ['class' => 'btn btn-success']) !!}
{!! Form::close() !!}
<hr>
{!! Form::open(['route' => 'backups.upload','class' => '', 'files' => true]) !!}
{!! FormField::file('backup_file', ['label' => 'Upload File Backup','placeholder'=>'Pilih File']) !!}
{!! Form::submit('Upload', ['class' => 'btn btn-info']) !!}
{!! Form::close() !!}
</div>
</div>
@include('backups.forms')
</div>
</div>
@endsection

22
resources/views/backups/restore.blade.php

@ -1,22 +0,0 @@
@extends('layouts.app')
@section('content')
<br>
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">
Apakah anda yakin akan mengembalikan seluruh data sesuai file ini <strong>"{{ $fileName }}"</strong>? <br>
Pastikan data saat ini sudah dibackup.
</h3>
</div>
<div class="panel-footer">
{!! link_to_route('backups.index', trans('app.cancel'), [], ['class' => 'btn btn-default']) !!}
<div class="pull-right">
{!! Form::open(['route'=>['backups.restore', $fileName]]) !!}
{!! Form::hidden('file_name', $fileName) !!}
{!! Form::submit('Restore Database', ['class'=>'btn btn-danger']) !!}
{!! Form::close() !!}
</div>
</div>
</div>
@endsection

13
routes/web.php

@ -6,11 +6,20 @@ require __DIR__ . '/web/pages.php';
require __DIR__ . '/web/users.php';
require __DIR__ . '/web/references.php';
require __DIR__ . '/web/account.php';
require __DIR__ . '/web/backup.php';
require __DIR__ . '/web/projects.php';
require __DIR__ . '/web/payments.php';
require __DIR__ . '/web/subscriptions.php';
require __DIR__ . '/web/reports.php';
require __DIR__ . '/web/invoices.php';
require __DIR__ . '/web/options-vue.php';
require __DIR__ . '/web/calendar.php';
require __DIR__ . '/web/calendar.php';
Route::group(['middleware' => ['web','role:admin']], function () {
/*
* Backup Restore Database Routes
*/
Route::post('backups/upload', ['as'=>'backups.upload', 'uses'=>'BackupsController@upload']);
Route::post('backups/{fileName}/restore', ['as'=>'backups.restore', 'uses'=>'BackupsController@restore']);
Route::get('backups/{fileName}/dl', ['as'=>'backups.download', 'uses'=>'BackupsController@download']);
Route::resource('backups', 'BackupsController', ['except' => ['create', 'show', 'edit']]);
});

14
routes/web/backup.php

@ -1,14 +0,0 @@
<?php
Route::group(['middleware' => ['web','role:admin']], function() {
/**
* Backups Routes
*/
Route::get('backups/{fileName}/restore', ['as'=>'backups.restore', 'uses'=>'BackupsController@restore']);
Route::post('backups/{fileName}/restore', ['as'=>'backups.restore', 'uses'=>'BackupsController@postRestore']);
Route::get('backups/{fileName}/dl', ['as'=>'backups.download', 'uses'=>'BackupsController@download']);
Route::post('backups/upload', ['as'=>'backups.upload', 'uses'=>'BackupsController@upload']);
Route::get('backups/{id}/delete', ['as'=>'backups.delete', 'uses'=>'BackupsController@delete']);
Route::resource('backups','BackupsController');
});
Loading…
Cancel
Save