From 4e880092c11583f9e20c5f93fdb4d35438ae71ad Mon Sep 17 00:00:00 2001 From: ro Date: Wed, 3 Jul 2024 14:30:32 -0600 Subject: [PATCH] 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. --- .../Controllers/API/SettingsAPIController.php | 29 +++++- app/Services/Data/SortingService.php | 12 ++- app/Services/Upkeep/MaintenanceService.php | 98 ++++++++++++++----- content/init/settings-template.json | 3 +- .../app/controllers/MaintenanceManager.js | 4 +- .../dash/app/controllers/SettingsIndex.js | 20 +++- resources/views/back/settings.blade.php | 21 ++-- routes/api.php | 3 +- 8 files changed, 144 insertions(+), 46 deletions(-) diff --git a/app/Http/Controllers/API/SettingsAPIController.php b/app/Http/Controllers/API/SettingsAPIController.php index bf56e43..782ba59 100644 --- a/app/Http/Controllers/API/SettingsAPIController.php +++ b/app/Http/Controllers/API/SettingsAPIController.php @@ -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'] + ); } } diff --git a/app/Services/Data/SortingService.php b/app/Services/Data/SortingService.php index df54ff3..094c3f6 100644 --- a/app/Services/Data/SortingService.php +++ b/app/Services/Data/SortingService.php @@ -266,10 +266,11 @@ class SortingService public function settings() { - $global = $this->settings->getGlobal(); - $updated = new Carbon($global['last_backup']); - $status = session('member') != '' ? true : false; - $pageOptions = [ + $global = $this->settings->getGlobal(); + $updatedContent = new Carbon($global['last_content_backup']); + $updatedFiles = new Carbon($global['last_files_backup']); + $status = session('member') != '' ? true : false; + $pageOptions = [ 'title' => 'Settings', 'private' => $global['private'], 'renderOnSave' => $global['renderOnSave'], @@ -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', diff --git a/app/Services/Upkeep/MaintenanceService.php b/app/Services/Upkeep/MaintenanceService.php index 1b06d40..21853ff 100644 --- a/app/Services/Upkeep/MaintenanceService.php +++ b/app/Services/Upkeep/MaintenanceService.php @@ -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 = []; - $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()]; - }; + 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 = []; - $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()]; - }; + 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 = []; - $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()]; - }; + 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!"]; } } diff --git a/content/init/settings-template.json b/content/init/settings-template.json index 6f648af..c89da9c 100644 --- a/content/init/settings-template.json +++ b/content/init/settings-template.json @@ -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" }, diff --git a/public/assets/scripts/dash/app/controllers/MaintenanceManager.js b/public/assets/scripts/dash/app/controllers/MaintenanceManager.js index bc4ba28..ce961c9 100644 --- a/public/assets/scripts/dash/app/controllers/MaintenanceManager.js +++ b/public/assets/scripts/dash/app/controllers/MaintenanceManager.js @@ -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); diff --git a/public/assets/scripts/dash/app/controllers/SettingsIndex.js b/public/assets/scripts/dash/app/controllers/SettingsIndex.js index 1bdb82e..f20d5ae 100644 --- a/public/assets/scripts/dash/app/controllers/SettingsIndex.js +++ b/public/assets/scripts/dash/app/controllers/SettingsIndex.js @@ -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); }) diff --git a/resources/views/back/settings.blade.php b/resources/views/back/settings.blade.php index 83e4df2..c862106 100644 --- a/resources/views/back/settings.blade.php +++ b/resources/views/back/settings.blade.php @@ -94,19 +94,18 @@ - - @if($lastBackup != '') - LAST BACK UP - {{ $lastBackup }}
+ @if($lastContentBackup != '') + MOST RECENT: + {{ $lastContentBackup }}
@else span No back ups. Frowny face. @endif -
@@ -124,7 +129,7 @@ RESET TO DEFAULT - Restores site to default state. !CANNOT UNDO! + Deletes all content and configs CANNOT UNDO
diff --git a/routes/api.php b/routes/api.php index f6532d1..71729f0 100644 --- a/routes/api.php +++ b/routes/api.php @@ -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']);