FEATURE: File Backup

Turned on file backups that grabs uploaded images in the public
directory and saves them in a zip so they can be downloaded and
archived.
This commit is contained in:
ro 2024-07-03 14:30:32 -06:00
parent bc7b1fe7ec
commit 4e880092c1
No known key found for this signature in database
GPG key ID: 29B551CDBD4D3B50
8 changed files with 144 additions and 46 deletions

View file

@ -58,15 +58,36 @@ class SettingsAPIController extends Controller
public function createBackup(Request $request)
{
return response()->json($this->maintenance->createBackup())->header('Content-Type', 'application/json');
$body = json_decode($request->getContent());
if ($body->task == 'content_backup') {
return response()->json(
$this->maintenance->createContentBackUp()
)->header('Content-Type', 'application/json');
} else {
return response()->json(
$this->maintenance->createFileBackUp()
)->header('Content-Type', 'application/json');
}
}
public function downloadBackup(Request $request)
{
if ($this->member::status()) {
$latest = $this->settings->getGlobal()['last_backup'];
$file = 'backup-' . $latest . '.zip';
return response()->download('../content/backups/' . $file, $file, ['Content-Type: application/zip']);
$latest = '';
$file = '';
if (explode('/', $request->getRequestUri())[4] == 'content-download') {
$latest = $this->settings->getGlobal()['last_content_backup'];
$file = 'backup-content-' . $latest . '.zip';
} else {
$latest = $this->settings->getGlobal()['last_files_backup'];
$file = 'backup-files-' . $latest . '.zip';
}
return response()->download(
'../content/backups/' . $file,
$file,
['Content-Type: application/zip']
);
}
}

View file

@ -267,7 +267,8 @@ class SortingService
public function settings()
{
$global = $this->settings->getGlobal();
$updated = new Carbon($global['last_backup']);
$updatedContent = new Carbon($global['last_content_backup']);
$updatedFiles = new Carbon($global['last_files_backup']);
$status = session('member') != '' ? true : false;
$pageOptions = [
'title' => 'Settings',
@ -279,7 +280,8 @@ class SortingService
'siteTitle' => $global['title'],
'baseUrl' => $global['base_url'],
'desc' => $global['descriptions'],
'lastBackup' => $updated->format('Y F d H i s'),
'lastContentBackup' => $updatedContent->format('Y F d H i s'),
'lastFilesBackup' => $updatedFiles->format('Y F d H i s'),
'currentTheme' => $global['theme'],
'themes' => $this->themes->getThemes(),
'apiStatus' => isset($global['externalAPI']) ? $global['externalAPI'] : 'false',

View file

@ -14,7 +14,7 @@ class MaintenanceService
$this->settings = $settingsService;
}
public function createBackUp()
public function createContentBackUp()
{
//make sure back directory is there
$stamp = Carbon::now()->format("YmdGis");
@ -25,7 +25,7 @@ class MaintenanceService
$zip = new \ZipArchive();
$zip->open(
env('FIPAMO_BACKUPS') . '/backup-' . $stamp . '.zip',
env('FIPAMO_BACKUPS') . '/backup-content-' . $stamp . '.zip',
\ZipArchive::CREATE | \ZipArchive::OVERWRITE
);
//gather data and path info for md pages
@ -63,29 +63,36 @@ class MaintenanceService
//gather paths for user images
$userImages = [];
if (is_dir('../public/assets/images/user')) {
$dir = new \RecursiveDirectoryIterator('../public/assets/images/user');
$flat = new \RecursiveIteratorIterator($dir);
$files = new \RegexIterator($flat, '/\.png|jpg|gif$/i');
foreach ($files as $file) {
$userImages[] = ['path' => $file->getPath(), 'file' => $file->getFilename()];
};
}
//gather paths for blog documents
$blogDocs = [];
if (is_dir('../public/assets/docs/blog')) {
$dir = new \RecursiveDirectoryIterator('../public/assets/docs/blog');
$flat = new \RecursiveIteratorIterator($dir);
$files = new \RegexIterator($flat, '/\.txt|pdf|rtf$/i');
foreach ($files as $file) {
$blogDocs[] = ['path' => $file->getPath(), 'file' => $file->getFilename()];
};
}
//gather paths for blog videos
$blogVids = [];
if (is_dir('../public/assets/video/blog')) {
$dir = new \RecursiveDirectoryIterator('../public/assets/video/blog');
$flat = new \RecursiveIteratorIterator($dir);
$files = new \RegexIterator($flat, '/\.mp4$/i');
foreach ($files as $file) {
$blogVids[] = ['path' => $file->getPath(), 'file' => $file->getFilename()];
};
}
//add directory for settings and save them
$zip->addEmptyDir('config');
@ -112,8 +119,55 @@ class MaintenanceService
unlink(env('FIPAMO_BACKUPS') . '/blog_vids_temp.json');
//update settings file with latest back up date
$this->settings->updateGlobalData('last_backup', $stamp);
$this->settings->updateGlobalData('last_content_backup', $stamp);
return ['message' => "Backup created. THIS IS A SAFE SPACE!"];
return ['message' => "Content backup created. THIS IS A SAFE SPACE!"];
}
public function createFileBackUp()
{
$stamp = Carbon::now()->format("YmdGis");
$zip = new \ZipArchive();
$zip->open(
env('FIPAMO_BACKUPS') . '/backup-files-' . $stamp . '.zip',
\ZipArchive::CREATE | \ZipArchive::OVERWRITE
);
//gather data and path info for blog images
$blogImagesPath = '../public/assets/images/blog';
$yearPaths = glob($blogImagesPath . '/*', GLOB_ONLYDIR);
foreach ($yearPaths as $years) {
$year = explode('/', $years);
$monthsPath = glob($blogImagesPath . '/' . $year[5] . '/*', GLOB_ONLYDIR);
foreach ($monthsPath as $months) {
$month = explode('/', $months);
//once info is collected, add images pages to zip
$options = [
'add_path' => 'public/assets/images/blog/' . $year[5] . '/' . $month[6] . '/',
'remove_all_path' => true,
];
$zip->addGlob($months . '/*.*', GLOB_BRACE, $options);
}
}
//gather data and path info for user images
$userImagesPath = '../public/assets/images/user';
$yearPaths = glob($userImagesPath . '/*', GLOB_ONLYDIR);
foreach ($yearPaths as $years) {
$year = explode('/', $years);
$monthsPath = glob($userImagesPath . '/' . $year[5] . '/*', GLOB_ONLYDIR);
foreach ($monthsPath as $months) {
$month = explode('/', $months);
//once info is collected, add images pages to zip
$options = [
'add_path' => 'public/assets/images/user/' . $year[5] . '/' . $month[6] . '/',
'remove_all_path' => true,
];
$zip->addGlob($months . '/*.*', GLOB_BRACE, $options);
}
}
$zip->close();
$this->settings->updateGlobalData('last_files_backup', $stamp);
return ['message' => "Files are backed up. Breath Easy!"];
}
}

View file

@ -8,7 +8,8 @@
"renderOnSave": "false",
"theme": "fipamo-default-v2",
"display_limit": 5,
"last_backup": null,
"last_content_backup": null,
"last_file_backup": null,
"externalAPI": "false",
"dynamicRender": "false"
},

View file

@ -151,7 +151,7 @@ class MaintenanceManager {
* Promise method for creating a zip back up of current site. For local use only.
*/
backup() {
backup(backup_task) {
return new Promise((resolve, reject) => {
var url, event, method, type, data;
@ -159,7 +159,7 @@ class MaintenanceManager {
event = TASK_BACKUP_CREATE;
method = REQUEST_TYPE_PUT;
type = CONTENT_TYPE_JSON;
data = { task: 'create_backup' };
data = backup_task;
this._request(url, null, event, method, type, data)
.then(result => {
resolve(result);

View file

@ -156,7 +156,11 @@ export default class SettingsIndex {
}
//handle backup from settings
document
.getElementById('create-backup')
.getElementById('create-content-backup')
.addEventListener('click', e => this.handleBackup(e));
document
.getElementById('create-file-backup')
.addEventListener('click', e => this.handleBackup(e));
}
//--------------------------
@ -259,9 +263,19 @@ export default class SettingsIndex {
handleBackup(e) {
e.preventDefault();
e.stopPropagation();
notify.alert('Creating backup', null);
let id = '';
let type = '';
e.target.id == '' ? (id = e.target.parentNode.id) : (id = e.target.id);
if (id == 'create-content-backup') {
notify.alert('Creating Content Backup', null);
type = { task: 'content_backup' };
} else {
notify.alert('Creating File Backup', null);
type = { task: 'file_backup' };
}
this.mm
.backup()
.backup(type)
.then(r => {
notify.alert(r.message, true);
})

View file

@ -94,19 +94,18 @@
<svg id="nav-menu-icon" class="icon">
<use id="nav-menu-icon" xlink:href="/assets/images/global/sprite.svg#entypo-copy"/>
</svg>
<button id="create-backup">
<button id="create-content-backup">
<span>CONTENT BACKUP</span>
</button>
<span>
@if($lastBackup != '')
LAST BACK UP
<a href="/api/v1/backup/download">{{ $lastBackup }}</a><br/>
@if($lastContentBackup != '')
MOST RECENT:
<a href="/api/v1/backup/content-download">{{ $lastContentBackup }}</a><br/>
@else
<span>span No back ups. Frowny face.</span>
@endif
<span>
</div>
<!-- TODO: File Back up option
<div class="option-container">
<svg id="nav-menu-icon" class="icon">
<use id="nav-menu-icon" xlink:href="/assets/images/global/sprite.svg#entypo-images"/>
@ -114,9 +113,15 @@
<button id="create-file-backup">
<span>FILE BACKUP</span>
</button>
<span>COMING SOON</span>
<span>
@if($lastFilesBackup != '')
MOST RECENT:
<a href="/api/v1/backup/files-download">{{ $lastFilesBackup }}</a><br/>
@else
<span>span No back ups. Frowny face.</span>
@endif
</span>
</div>
-->
<div class="option-container">
<svg id="nav-menu-icon" class="icon">
<use id="nav-menu-icon" xlink:href="/assets/images/global/sprite.svg#entypo-back-in-time"/>
@ -124,7 +129,7 @@
<button id="reset-to-default">
<span>RESET TO DEFAULT</span>
</button>
<span>Restores site to default state. !CANNOT UNDO!</span>
<span>Deletes all content and configs <strong>CANNOT UNDO</strong></span>
</div>
</div>
</section>

View file

@ -31,7 +31,8 @@ Route::put("/v1/settings/publish", [SettingsAPIController::class, 'publish']);
Route::put("/v1/settings/sync", [SettingsAPIController::class, 'sync']);
Route::put("/v1/settings/nav-sync", [SettingsAPIController::class, 'navSync']);
Route::put("/v1/backup/create", [SettingsAPIController::class, 'createBackup']);
Route::get("/v1/backup/download", [SettingsAPIController::class, 'downloadBackup']);
Route::get("/v1/backup/content-download", [SettingsAPIController::class, 'downloadBackup']);
Route::get("/v1/backup/files-download", [SettingsAPIController::class, 'downloadBackup']);
//init
Route::post("/v1/init", [InitAPIController::class, 'setupFresh']);
Route::post("/v1/restore", [InitAPIController::class, 'setupRestore']);