From 94af5ba3fe0c090beb50f45c7949a6b03ba809d9 Mon Sep 17 00:00:00 2001 From: Dan Gowans Date: Wed, 25 Jan 2023 10:32:43 -0500 Subject: [PATCH] database backup --- .gitignore | 3 + ...eanupDatabase.cy.d.ts => database.cy.d.ts} | 0 .../{cleanupDatabase.cy.js => database.cy.js} | 16 +++- .../{cleanupDatabase.cy.ts => database.cy.ts} | 22 ++++- cypress/e2e/02-update/updateUser.cy.js | 2 +- cypress/e2e/02-update/updateUser.cy.ts | 2 +- cypress/e2e/xx-other/loginPage.cy.js | 5 ++ cypress/e2e/xx-other/loginPage.cy.ts | 6 ++ data/backups/README.md | 3 + data/databasePaths.d.ts | 1 + data/databasePaths.js | 1 + data/databasePaths.ts | 2 + handlers/admin-get/cleanup.js | 6 -- .../admin-get/{cleanup.d.ts => database.d.ts} | 0 handlers/admin-get/database.js | 6 ++ .../admin-get/{cleanup.ts => database.ts} | 4 +- handlers/admin-post/doBackupDatabase.d.ts | 3 + handlers/admin-post/doBackupDatabase.js | 19 +++++ handlers/admin-post/doBackupDatabase.ts | 28 +++++++ helpers/functions.database.d.ts | 1 + helpers/functions.database.js | 17 ++++ helpers/functions.database.ts | 23 +++++ public-typescript/adminCleanup.js | 38 --------- .../{adminCleanup.d.ts => adminDatabase.d.ts} | 0 public-typescript/adminDatabase.js | 68 +++++++++++++++ .../{adminCleanup.ts => adminDatabase.ts} | 43 +++++++++- public/javascripts/adminCleanup.min.js | 1 - public/javascripts/adminDatabase.min.js | 1 + routes/admin.js | 6 +- routes/admin.ts | 13 +-- tsconfig.client.json | 2 +- views/_menu-admin.ejs | 6 +- views/admin-cleanup.ejs | 60 ------------- views/admin-database.ejs | 84 +++++++++++++++++++ views/dashboard.ejs | 7 +- 35 files changed, 368 insertions(+), 131 deletions(-) rename cypress/e2e/01-admin/{cleanupDatabase.cy.d.ts => database.cy.d.ts} (100%) rename cypress/e2e/01-admin/{cleanupDatabase.cy.js => database.cy.js} (57%) rename cypress/e2e/01-admin/{cleanupDatabase.cy.ts => database.cy.ts} (60%) create mode 100644 data/backups/README.md delete mode 100644 handlers/admin-get/cleanup.js rename handlers/admin-get/{cleanup.d.ts => database.d.ts} (100%) create mode 100644 handlers/admin-get/database.js rename handlers/admin-get/{cleanup.ts => database.ts} (66%) create mode 100644 handlers/admin-post/doBackupDatabase.d.ts create mode 100644 handlers/admin-post/doBackupDatabase.js create mode 100644 handlers/admin-post/doBackupDatabase.ts create mode 100644 helpers/functions.database.d.ts create mode 100644 helpers/functions.database.js create mode 100644 helpers/functions.database.ts delete mode 100644 public-typescript/adminCleanup.js rename public-typescript/{adminCleanup.d.ts => adminDatabase.d.ts} (100%) create mode 100644 public-typescript/adminDatabase.js rename public-typescript/{adminCleanup.ts => adminDatabase.ts} (56%) delete mode 100644 public/javascripts/adminCleanup.min.js create mode 100644 public/javascripts/adminDatabase.min.js delete mode 100644 views/admin-cleanup.ejs create mode 100644 views/admin-database.ejs diff --git a/.gitignore b/.gitignore index 7c89f161..58defa15 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ cypress/downloads/ cypress/screenshots/ cypress/videos/ +data/backups/* +!data/backups/README.md + data/sessions/ node_modules/ diff --git a/cypress/e2e/01-admin/cleanupDatabase.cy.d.ts b/cypress/e2e/01-admin/database.cy.d.ts similarity index 100% rename from cypress/e2e/01-admin/cleanupDatabase.cy.d.ts rename to cypress/e2e/01-admin/database.cy.d.ts diff --git a/cypress/e2e/01-admin/cleanupDatabase.cy.js b/cypress/e2e/01-admin/database.cy.js similarity index 57% rename from cypress/e2e/01-admin/cleanupDatabase.cy.js rename to cypress/e2e/01-admin/database.cy.js index 2c93c468..c493debb 100644 --- a/cypress/e2e/01-admin/cleanupDatabase.cy.js +++ b/cypress/e2e/01-admin/database.cy.js @@ -1,17 +1,27 @@ import { testAdmin } from '../../../test/_globals.js'; import { logout, login, ajaxDelayMillis } from '../../support/index.js'; -describe('Admin - Cleanup Database', () => { +describe('Admin - Database Maintenance', () => { beforeEach('Loads page', () => { logout(); login(testAdmin); - cy.visit('/admin/cleanup'); - cy.location('pathname').should('equal', '/admin/cleanup'); + cy.visit('/admin/database'); + cy.location('pathname').should('equal', '/admin/database'); }); afterEach(logout); it('Has no detectable accessibility issues', () => { cy.injectAxe(); cy.checkA11y(); }); + it('Backs up the database', () => { + cy.get("button[data-cy='backup']").click(); + cy.get('.modal').should('be.visible').should('contain.text', 'Backup'); + cy.get(".modal button[data-cy='ok']").click(); + cy.wait(ajaxDelayMillis); + cy.get('.modal') + .should('contain.text', 'Backed Up') + .should('contain.text', 'Success'); + cy.get(".modal button[data-cy='ok']").click(); + }); it('Cleans up the database', () => { cy.get("button[data-cy='cleanup']").click(); cy.get('.modal').should('be.visible').should('contain.text', 'Cleanup'); diff --git a/cypress/e2e/01-admin/cleanupDatabase.cy.ts b/cypress/e2e/01-admin/database.cy.ts similarity index 60% rename from cypress/e2e/01-admin/cleanupDatabase.cy.ts rename to cypress/e2e/01-admin/database.cy.ts index d52bdf03..45d04ff5 100644 --- a/cypress/e2e/01-admin/cleanupDatabase.cy.ts +++ b/cypress/e2e/01-admin/database.cy.ts @@ -4,12 +4,12 @@ import { testAdmin } from '../../../test/_globals.js' import { logout, login, ajaxDelayMillis } from '../../support/index.js' -describe('Admin - Cleanup Database', () => { +describe('Admin - Database Maintenance', () => { beforeEach('Loads page', () => { logout() login(testAdmin) - cy.visit('/admin/cleanup') - cy.location('pathname').should('equal', '/admin/cleanup') + cy.visit('/admin/database') + cy.location('pathname').should('equal', '/admin/database') }) afterEach(logout) @@ -19,6 +19,22 @@ describe('Admin - Cleanup Database', () => { cy.checkA11y() }) + it('Backs up the database', () => { + cy.get("button[data-cy='backup']").click() + + cy.get('.modal').should('be.visible').should('contain.text', 'Backup') + + cy.get(".modal button[data-cy='ok']").click() + + cy.wait(ajaxDelayMillis) + + cy.get('.modal') + .should('contain.text', 'Backed Up') + .should('contain.text', 'Success') + + cy.get(".modal button[data-cy='ok']").click() + }) + it('Cleans up the database', () => { cy.get("button[data-cy='cleanup']").click() diff --git a/cypress/e2e/02-update/updateUser.cy.js b/cypress/e2e/02-update/updateUser.cy.js index 0eb84703..b5d832ab 100644 --- a/cypress/e2e/02-update/updateUser.cy.js +++ b/cypress/e2e/02-update/updateUser.cy.js @@ -15,7 +15,7 @@ describe('Update User', () => { cy.get("a[href*='/admin']").should('not.exist'); }); it('Redirects to Dashboard when attempting to access admin area', () => { - cy.visit('/admin/cleanup'); + cy.visit('/admin/tables'); cy.wait(200); cy.location('pathname').should('equal', '/dashboard/'); }); diff --git a/cypress/e2e/02-update/updateUser.cy.ts b/cypress/e2e/02-update/updateUser.cy.ts index 38982bc7..ba7a654b 100644 --- a/cypress/e2e/02-update/updateUser.cy.ts +++ b/cypress/e2e/02-update/updateUser.cy.ts @@ -24,7 +24,7 @@ describe('Update User', () => { }) it('Redirects to Dashboard when attempting to access admin area', () => { - cy.visit('/admin/cleanup') + cy.visit('/admin/tables') cy.wait(200) cy.location('pathname').should('equal', '/dashboard/') }) diff --git a/cypress/e2e/xx-other/loginPage.cy.js b/cypress/e2e/xx-other/loginPage.cy.js index 528ec59e..2db0f367 100644 --- a/cypress/e2e/xx-other/loginPage.cy.js +++ b/cypress/e2e/xx-other/loginPage.cy.js @@ -25,4 +25,9 @@ describe('Login Page', () => { matchCase: false }); }); + it('Redirects to login when attempting to access dashboard', () => { + cy.visit('/dashboard'); + cy.wait(200); + cy.location('pathname').should('contain', '/login'); + }); }); diff --git a/cypress/e2e/xx-other/loginPage.cy.ts b/cypress/e2e/xx-other/loginPage.cy.ts index f3e9b3f6..48d94fe7 100644 --- a/cypress/e2e/xx-other/loginPage.cy.ts +++ b/cypress/e2e/xx-other/loginPage.cy.ts @@ -32,4 +32,10 @@ describe('Login Page', () => { matchCase: false }) }) + + it('Redirects to login when attempting to access dashboard', () => { + cy.visit('/dashboard') + cy.wait(200) + cy.location('pathname').should('contain', '/login') + }) }) diff --git a/data/backups/README.md b/data/backups/README.md new file mode 100644 index 00000000..ae9fd799 --- /dev/null +++ b/data/backups/README.md @@ -0,0 +1,3 @@ +# Placeholder + +This is a placeholder file to assist with the creation of this folder. diff --git a/data/databasePaths.d.ts b/data/databasePaths.d.ts index 1962de74..2b563041 100644 --- a/data/databasePaths.d.ts +++ b/data/databasePaths.d.ts @@ -2,3 +2,4 @@ export declare const useTestDatabases: boolean; export declare const lotOccupancyDBLive = "data/lotOccupancy.db"; export declare const lotOccupancyDBTesting = "data/lotOccupancy-testing.db"; export declare const lotOccupancyDB: string; +export declare const backupFolder = "data/backups"; diff --git a/data/databasePaths.js b/data/databasePaths.js index 4b40e92e..bd81c818 100644 --- a/data/databasePaths.js +++ b/data/databasePaths.js @@ -11,3 +11,4 @@ export const lotOccupancyDBTesting = 'data/lotOccupancy-testing.db'; export const lotOccupancyDB = useTestDatabases ? lotOccupancyDBTesting : lotOccupancyDBLive; +export const backupFolder = 'data/backups'; diff --git a/data/databasePaths.ts b/data/databasePaths.ts index de3e2eac..865b946f 100644 --- a/data/databasePaths.ts +++ b/data/databasePaths.ts @@ -19,3 +19,5 @@ export const lotOccupancyDBTesting = 'data/lotOccupancy-testing.db' export const lotOccupancyDB = useTestDatabases ? lotOccupancyDBTesting : lotOccupancyDBLive + +export const backupFolder = 'data/backups' diff --git a/handlers/admin-get/cleanup.js b/handlers/admin-get/cleanup.js deleted file mode 100644 index 0798077a..00000000 --- a/handlers/admin-get/cleanup.js +++ /dev/null @@ -1,6 +0,0 @@ -export function handler(_request, response) { - response.render('admin-cleanup', { - headTitle: 'Database Cleanup' - }); -} -export default handler; diff --git a/handlers/admin-get/cleanup.d.ts b/handlers/admin-get/database.d.ts similarity index 100% rename from handlers/admin-get/cleanup.d.ts rename to handlers/admin-get/database.d.ts diff --git a/handlers/admin-get/database.js b/handlers/admin-get/database.js new file mode 100644 index 00000000..61cfdf49 --- /dev/null +++ b/handlers/admin-get/database.js @@ -0,0 +1,6 @@ +export function handler(_request, response) { + response.render('admin-database', { + headTitle: 'Database Maintenance' + }); +} +export default handler; diff --git a/handlers/admin-get/cleanup.ts b/handlers/admin-get/database.ts similarity index 66% rename from handlers/admin-get/cleanup.ts rename to handlers/admin-get/database.ts index 38c7d87e..52fea163 100644 --- a/handlers/admin-get/cleanup.ts +++ b/handlers/admin-get/database.ts @@ -1,8 +1,8 @@ import type { Request, Response } from 'express' export function handler(_request: Request, response: Response): void { - response.render('admin-cleanup', { - headTitle: 'Database Cleanup' + response.render('admin-database', { + headTitle: 'Database Maintenance' }) } diff --git a/handlers/admin-post/doBackupDatabase.d.ts b/handlers/admin-post/doBackupDatabase.d.ts new file mode 100644 index 00000000..c674b8e3 --- /dev/null +++ b/handlers/admin-post/doBackupDatabase.d.ts @@ -0,0 +1,3 @@ +import type { Request, Response } from 'express'; +export declare function handler(_request: Request, response: Response): Promise; +export default handler; diff --git a/handlers/admin-post/doBackupDatabase.js b/handlers/admin-post/doBackupDatabase.js new file mode 100644 index 00000000..53b3e8b5 --- /dev/null +++ b/handlers/admin-post/doBackupDatabase.js @@ -0,0 +1,19 @@ +import { backupDatabase } from '../../helpers/functions.database.js'; +export async function handler(_request, response) { + const backupDatabasePath = await backupDatabase(); + if (typeof backupDatabasePath === 'string') { + const backupDatabasePathSplit = backupDatabasePath.split(/[/\\]/g); + const fileName = backupDatabasePathSplit[backupDatabasePathSplit.length - 1]; + response.json({ + success: true, + fileName + }); + } + else { + response.json({ + success: false, + errorMessage: 'Unable to write backup file.' + }); + } +} +export default handler; diff --git a/handlers/admin-post/doBackupDatabase.ts b/handlers/admin-post/doBackupDatabase.ts new file mode 100644 index 00000000..9ee8e6a1 --- /dev/null +++ b/handlers/admin-post/doBackupDatabase.ts @@ -0,0 +1,28 @@ +import type { Request, Response } from 'express' + +import { backupDatabase } from '../../helpers/functions.database.js' + +export async function handler( + _request: Request, + response: Response +): Promise { + const backupDatabasePath = await backupDatabase() + + if (typeof backupDatabasePath === 'string') { + const backupDatabasePathSplit = backupDatabasePath.split(/[/\\]/g) + + const fileName = backupDatabasePathSplit[backupDatabasePathSplit.length - 1] + + response.json({ + success: true, + fileName + }) + } else { + response.json({ + success: false, + errorMessage: 'Unable to write backup file.' + }) + } +} + +export default handler diff --git a/helpers/functions.database.d.ts b/helpers/functions.database.d.ts new file mode 100644 index 00000000..d01aee43 --- /dev/null +++ b/helpers/functions.database.d.ts @@ -0,0 +1 @@ +export declare const backupDatabase: () => Promise; diff --git a/helpers/functions.database.js b/helpers/functions.database.js new file mode 100644 index 00000000..c4f2491e --- /dev/null +++ b/helpers/functions.database.js @@ -0,0 +1,17 @@ +import fs from 'node:fs/promises'; +import { lotOccupancyDB as databasePath, backupFolder } from '../data/databasePaths.js'; +export const backupDatabase = async () => { + const databasePathSplit = databasePath.split(/[/\\]/g); + const backupDatabasePath = backupFolder + + '/' + + databasePathSplit[databasePathSplit.length - 1] + + '.' + + Date.now().toString(); + try { + await fs.copyFile(databasePath, backupDatabasePath); + return backupDatabasePath; + } + catch { + return false; + } +}; diff --git a/helpers/functions.database.ts b/helpers/functions.database.ts new file mode 100644 index 00000000..ac1fdb4d --- /dev/null +++ b/helpers/functions.database.ts @@ -0,0 +1,23 @@ +import fs from 'node:fs/promises' +import { + lotOccupancyDB as databasePath, + backupFolder +} from '../data/databasePaths.js' + +export const backupDatabase = async (): Promise => { + const databasePathSplit = databasePath.split(/[/\\]/g) + + const backupDatabasePath = + backupFolder + + '/' + + databasePathSplit[databasePathSplit.length - 1] + + '.' + + Date.now().toString() + + try { + await fs.copyFile(databasePath, backupDatabasePath) + return backupDatabasePath + } catch { + return false + } +} diff --git a/public-typescript/adminCleanup.js b/public-typescript/adminCleanup.js deleted file mode 100644 index 3f0c8df1..00000000 --- a/public-typescript/adminCleanup.js +++ /dev/null @@ -1,38 +0,0 @@ -"use strict"; -/* eslint-disable @typescript-eslint/no-non-null-assertion, unicorn/prefer-module */ -Object.defineProperty(exports, "__esModule", { value: true }); -(() => { - const los = exports.los; - function doCleanup() { - cityssm.postJSON(los.urlPrefix + '/admin/doCleanupDatabase', {}, (responseJSON) => { - var _a; - if (responseJSON.success) { - bulmaJS.alert({ - title: 'Database Cleaned Up Successfully', - message: `${responseJSON.inactivedRecordCount} records inactivated, - ${responseJSON.purgedRecordCount} permanently deleted.`, - contextualColorName: 'success' - }); - } - else { - bulmaJS.alert({ - title: 'Error Cleaning Database', - message: (_a = responseJSON.errorMessage) !== null && _a !== void 0 ? _a : '', - contextualColorName: 'danger' - }); - } - }); - } - document - .querySelector('#button--cleanupDatabase') - .addEventListener('click', () => { - bulmaJS.confirm({ - title: 'Cleanup Database', - message: 'Are you sure you want to cleanup up the database?', - okButton: { - text: 'Yes, Cleanup Database', - callbackFunction: doCleanup - } - }); - }); -})(); diff --git a/public-typescript/adminCleanup.d.ts b/public-typescript/adminDatabase.d.ts similarity index 100% rename from public-typescript/adminCleanup.d.ts rename to public-typescript/adminDatabase.d.ts diff --git a/public-typescript/adminDatabase.js b/public-typescript/adminDatabase.js new file mode 100644 index 00000000..12ea2d24 --- /dev/null +++ b/public-typescript/adminDatabase.js @@ -0,0 +1,68 @@ +"use strict"; +/* eslint-disable @typescript-eslint/no-non-null-assertion, unicorn/prefer-module */ +Object.defineProperty(exports, "__esModule", { value: true }); +(() => { + var _a, _b; + const los = exports.los; + function doBackup() { + cityssm.postJSON(los.urlPrefix + '/admin/doBackupDatabase', {}, (responseJSON) => { + var _a; + if (responseJSON.success) { + bulmaJS.alert({ + title: 'Database Backed Up Successfully', + message: `Backed up to ${responseJSON.fileName}`, + contextualColorName: 'success' + }); + } + else { + bulmaJS.alert({ + title: 'Error Backing Up Database', + message: (_a = responseJSON.errorMessage) !== null && _a !== void 0 ? _a : '', + contextualColorName: 'danger' + }); + } + }); + } + function doCleanup() { + cityssm.postJSON(los.urlPrefix + '/admin/doCleanupDatabase', {}, (responseJSON) => { + var _a; + if (responseJSON.success) { + bulmaJS.alert({ + title: 'Database Cleaned Up Successfully', + message: `${responseJSON.inactivedRecordCount} records inactivated, + ${responseJSON.purgedRecordCount} permanently deleted.`, + contextualColorName: 'success' + }); + } + else { + bulmaJS.alert({ + title: 'Error Cleaning Database', + message: (_a = responseJSON.errorMessage) !== null && _a !== void 0 ? _a : '', + contextualColorName: 'danger' + }); + } + }); + } + (_a = document + .querySelector('#button--cleanupDatabase')) === null || _a === void 0 ? void 0 : _a.addEventListener('click', () => { + bulmaJS.confirm({ + title: 'Cleanup Database', + message: 'Are you sure you want to cleanup up the database?', + okButton: { + text: 'Yes, Cleanup Database', + callbackFunction: doCleanup + } + }); + }); + (_b = document + .querySelector('#button--backupDatabase')) === null || _b === void 0 ? void 0 : _b.addEventListener('click', () => { + bulmaJS.confirm({ + title: 'Backup Database', + message: 'Are you sure you want to backup up the database?', + okButton: { + text: 'Yes, Backup Database', + callbackFunction: doBackup + } + }); + }); +})(); diff --git a/public-typescript/adminCleanup.ts b/public-typescript/adminDatabase.ts similarity index 56% rename from public-typescript/adminCleanup.ts rename to public-typescript/adminDatabase.ts index d1220984..b7194b3a 100644 --- a/public-typescript/adminCleanup.ts +++ b/public-typescript/adminDatabase.ts @@ -11,6 +11,32 @@ declare const bulmaJS: BulmaJS ;(() => { const los = exports.los as globalTypes.LOS + function doBackup(): void { + cityssm.postJSON( + los.urlPrefix + '/admin/doBackupDatabase', + {}, + (responseJSON: { + success: boolean + errorMessage?: string + fileName?: string + }) => { + if (responseJSON.success) { + bulmaJS.alert({ + title: 'Database Backed Up Successfully', + message: `Backed up to ${responseJSON.fileName!}`, + contextualColorName: 'success' + }) + } else { + bulmaJS.alert({ + title: 'Error Backing Up Database', + message: responseJSON.errorMessage ?? '', + contextualColorName: 'danger' + }) + } + } + ) + } + function doCleanup(): void { cityssm.postJSON( los.urlPrefix + '/admin/doCleanupDatabase', @@ -40,8 +66,8 @@ declare const bulmaJS: BulmaJS } document - .querySelector('#button--cleanupDatabase')! - .addEventListener('click', () => { + .querySelector('#button--cleanupDatabase') + ?.addEventListener('click', () => { bulmaJS.confirm({ title: 'Cleanup Database', message: 'Are you sure you want to cleanup up the database?', @@ -51,4 +77,17 @@ declare const bulmaJS: BulmaJS } }) }) + + document + .querySelector('#button--backupDatabase') + ?.addEventListener('click', () => { + bulmaJS.confirm({ + title: 'Backup Database', + message: 'Are you sure you want to backup up the database?', + okButton: { + text: 'Yes, Backup Database', + callbackFunction: doBackup + } + }) + }) })() diff --git a/public/javascripts/adminCleanup.min.js b/public/javascripts/adminCleanup.min.js deleted file mode 100644 index f77f5de3..00000000 --- a/public/javascripts/adminCleanup.min.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),(()=>{const e=exports.los;function a(){cityssm.postJSON(e.urlPrefix+"/admin/doCleanupDatabase",{},e=>{var a;e.success?bulmaJS.alert({title:"Database Cleaned Up Successfully",message:`${e.inactivedRecordCount} records inactivated,\n ${e.purgedRecordCount} permanently deleted.`,contextualColorName:"success"}):bulmaJS.alert({title:"Error Cleaning Database",message:null!==(a=e.errorMessage)&&void 0!==a?a:"",contextualColorName:"danger"})})}document.querySelector("#button--cleanupDatabase").addEventListener("click",()=>{bulmaJS.confirm({title:"Cleanup Database",message:"Are you sure you want to cleanup up the database?",okButton:{text:"Yes, Cleanup Database",callbackFunction:a}})})})(); \ No newline at end of file diff --git a/public/javascripts/adminDatabase.min.js b/public/javascripts/adminDatabase.min.js new file mode 100644 index 00000000..20466482 --- /dev/null +++ b/public/javascripts/adminDatabase.min.js @@ -0,0 +1 @@ +"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),(()=>{var e,a;const t=exports.los;function s(){cityssm.postJSON(t.urlPrefix+"/admin/doBackupDatabase",{},e=>{var a;e.success?bulmaJS.alert({title:"Database Backed Up Successfully",message:`Backed up to ${e.fileName}`,contextualColorName:"success"}):bulmaJS.alert({title:"Error Backing Up Database",message:null!==(a=e.errorMessage)&&void 0!==a?a:"",contextualColorName:"danger"})})}function u(){cityssm.postJSON(t.urlPrefix+"/admin/doCleanupDatabase",{},e=>{var a;e.success?bulmaJS.alert({title:"Database Cleaned Up Successfully",message:`${e.inactivedRecordCount} records inactivated,\n ${e.purgedRecordCount} permanently deleted.`,contextualColorName:"success"}):bulmaJS.alert({title:"Error Cleaning Database",message:null!==(a=e.errorMessage)&&void 0!==a?a:"",contextualColorName:"danger"})})}null===(e=document.querySelector("#button--cleanupDatabase"))||void 0===e||e.addEventListener("click",()=>{bulmaJS.confirm({title:"Cleanup Database",message:"Are you sure you want to cleanup up the database?",okButton:{text:"Yes, Cleanup Database",callbackFunction:u}})}),null===(a=document.querySelector("#button--backupDatabase"))||void 0===a||a.addEventListener("click",()=>{bulmaJS.confirm({title:"Backup Database",message:"Are you sure you want to backup up the database?",okButton:{text:"Yes, Backup Database",callbackFunction:s}})})})(); \ No newline at end of file diff --git a/routes/admin.js b/routes/admin.js index b1163c78..373470b6 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -57,7 +57,8 @@ import handler_doUpdateLotOccupantType from '../handlers/admin-post/doUpdateLotO import handler_doMoveLotOccupantTypeUp from '../handlers/admin-post/doMoveLotOccupantTypeUp.js'; import handler_doMoveLotOccupantTypeDown from '../handlers/admin-post/doMoveLotOccupantTypeDown.js'; import handler_doDeleteLotOccupantType from '../handlers/admin-post/doDeleteLotOccupantType.js'; -import handler_cleanup from '../handlers/admin-get/cleanup.js'; +import handler_database from '../handlers/admin-get/database.js'; +import handler_doBackupDatabase from '../handlers/admin-post/doBackupDatabase.js'; import handler_doCleanupDatabase from '../handlers/admin-post/doCleanupDatabase.js'; import handler_ntfyStartup from '../handlers/admin-get/ntfyStartup.js'; export const router = Router(); @@ -119,7 +120,8 @@ router.post('/doUpdateLotOccupantType', handler_doUpdateLotOccupantType); router.post('/doMoveLotOccupantTypeUp', handler_doMoveLotOccupantTypeUp); router.post('/doMoveLotOccupantTypeDown', handler_doMoveLotOccupantTypeDown); router.post('/doDeleteLotOccupantType', handler_doDeleteLotOccupantType); -router.get('/cleanup', handler_cleanup); +router.get('/database', handler_database); +router.post('/doBackupDatabase', handler_doBackupDatabase); router.post('/doCleanupDatabase', handler_doCleanupDatabase); router.get('/ntfyStartup', handler_ntfyStartup); export default router; diff --git a/routes/admin.ts b/routes/admin.ts index 222f0f57..014dc8d6 100644 --- a/routes/admin.ts +++ b/routes/admin.ts @@ -1,4 +1,4 @@ -import { Router, RequestHandler } from 'express' +import { Router, type RequestHandler } from 'express' // Fee Management @@ -81,9 +81,10 @@ import handler_doMoveLotOccupantTypeUp from '../handlers/admin-post/doMoveLotOcc import handler_doMoveLotOccupantTypeDown from '../handlers/admin-post/doMoveLotOccupantTypeDown.js' import handler_doDeleteLotOccupantType from '../handlers/admin-post/doDeleteLotOccupantType.js' -// Cleanup +// Database Maintenance -import handler_cleanup from '../handlers/admin-get/cleanup.js' +import handler_database from '../handlers/admin-get/database.js' +import handler_doBackupDatabase from '../handlers/admin-post/doBackupDatabase.js' import handler_doCleanupDatabase from '../handlers/admin-post/doCleanupDatabase.js' // Ntfy Startup @@ -346,9 +347,11 @@ router.post( handler_doDeleteLotOccupantType as RequestHandler ) -// Cleanup +// Database Maintenance -router.get('/cleanup', handler_cleanup) +router.get('/database', handler_database) + +router.post('/doBackupDatabase', handler_doBackupDatabase as RequestHandler) router.post('/doCleanupDatabase', handler_doCleanupDatabase as RequestHandler) diff --git a/tsconfig.client.json b/tsconfig.client.json index 673c6b66..6c997842 100644 --- a/tsconfig.client.json +++ b/tsconfig.client.json @@ -11,7 +11,7 @@ "strictNullChecks": true }, "files": [ - "public-typescript/adminCleanup.ts", + "public-typescript/adminDatabase.ts", "public-typescript/adminFees.ts", "public-typescript/adminLotTypes.ts", "public-typescript/adminOccupancyTypes.ts", diff --git a/views/_menu-admin.ejs b/views/_menu-admin.ejs index a71f5bad..16d922bb 100644 --- a/views/_menu-admin.ejs +++ b/views/_menu-admin.ejs @@ -28,9 +28,9 @@
  • - " href="<%= urlPrefix %>/admin/cleanup"> - - Database Cleanup + " href="<%= urlPrefix %>/admin/database"> + + Database Maintenance
  • <% if (configFunctions.getProperty("application.ntfyStartup")) { %> diff --git a/views/admin-cleanup.ejs b/views/admin-cleanup.ejs deleted file mode 100644 index 9ebea1db..00000000 --- a/views/admin-cleanup.ejs +++ /dev/null @@ -1,60 +0,0 @@ -<%- include('_header'); -%> - -
    -
    - <%- include('_menu-admin'); -%> -
    -
    - - -

    - Database Cleanup -

    - -
    -
    - Important Note -
    -
    -

    - When records are deleted in this application, they are not removed entirely. - This gives systems administrators the ability to recover deleted records. - This also can leave a small amount of garbage behind in the database. -

    -

    - This process permanently deletes records that have already been deleted over <%= configFunctions.getProperty("settings.adminCleanup.recordDeleteAgeDays") %> days ago. - Note that no active records will be affected by the cleanup process. -

    -
    -
    - -

    - -

    -
    -
    - -<%- include('_footerA'); -%> - - - -<%- include('_footerB'); -%> diff --git a/views/admin-database.ejs b/views/admin-database.ejs new file mode 100644 index 00000000..f74f4efb --- /dev/null +++ b/views/admin-database.ejs @@ -0,0 +1,84 @@ +<%- include('_header'); -%> + +
    +
    + <%- include('_menu-admin'); -%> +
    +
    + + +

    + Database Maintenance +

    + +

    + Database Backup +

    + +
    +
    +

    + Before making significant changes to the records in the database, + it is a good idea to back up first! +

    +
    +
    + +

    + +

    + +

    + Database Cleanup +

    + +
    +
    + Important Note about Cleanup +
    +
    +

    + When records are deleted in this application, they are not removed entirely. + This gives systems administrators the ability to recover deleted records. + This also can leave a small amount of garbage behind in the database. +

    +

    + This process permanently deletes records that have already been deleted over + <%= configFunctions.getProperty("settings.adminCleanup.recordDeleteAgeDays") %> days ago. + Note that no active records will be affected by the cleanup process. +

    +
    +
    + +

    + +

    +
    +
    + +<%- include('_footerA'); -%> + + + +<%- include('_footerB'); -%> diff --git a/views/dashboard.ejs b/views/dashboard.ejs index d611bdcf..df9ab0e5 100644 --- a/views/dashboard.ejs +++ b/views/dashboard.ejs @@ -365,14 +365,15 @@
    - +

    - Database Cleanup + Database Maintenance

    - Permanently delete records that have been previously deleted from the database. + Backup the database before making significant updates. + Permanently delete records that have been previously deleted from the database.