Skip to content

Commit

Permalink
database cleanup
Browse files Browse the repository at this point in the history
closes #100
  • Loading branch information
dangowans committed Jun 15, 2022
1 parent 572e6d6 commit f491ec9
Show file tree
Hide file tree
Showing 19 changed files with 443 additions and 3 deletions.
3 changes: 3 additions & 0 deletions handlers/admin-get/cleanup.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { RequestHandler } from "express";
export declare const handler: RequestHandler;
export default handler;
6 changes: 6 additions & 0 deletions handlers/admin-get/cleanup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const handler = async (_request, response) => {
response.render("admin-cleanup", {
headTitle: "Database Cleanup"
});
};
export default handler;
11 changes: 11 additions & 0 deletions handlers/admin-get/cleanup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { RequestHandler } from "express";

export const handler: RequestHandler = async (_request, response) => {

response.render("admin-cleanup", {
headTitle: "Database Cleanup"
});
};


export default handler;
3 changes: 3 additions & 0 deletions handlers/admin-post/doCleanupDatabase.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { RequestHandler } from "express";
export declare const handler: RequestHandler;
export default handler;
9 changes: 9 additions & 0 deletions handlers/admin-post/doCleanupDatabase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { cleanupDatabase } from "../../helpers/licencesDB/cleanupDatabase.js";
export const handler = async (_request, response) => {
const rowCount = cleanupDatabase();
response.json({
success: true,
rowCount
});
};
export default handler;
17 changes: 17 additions & 0 deletions handlers/admin-post/doCleanupDatabase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { RequestHandler } from "express";

import { cleanupDatabase } from "../../helpers/licencesDB/cleanupDatabase.js";


export const handler: RequestHandler = async (_request, response) => {

const rowCount = cleanupDatabase();

response.json({
success: true,
rowCount
});
};


export default handler;
2 changes: 2 additions & 0 deletions helpers/licencesDB/cleanupDatabase.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export declare const cleanupDatabase: () => number;
export default cleanupDatabase;
53 changes: 53 additions & 0 deletions helpers/licencesDB/cleanupDatabase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import sqlite from "better-sqlite3";
import { licencesDB as databasePath } from "../../data/databasePaths.js";
const recordDelete_timeMillis_is_not_null = "recordDelete_timeMillis is not null";
const cleanupQueries = [
("delete from LicenceTransactions" +
" where " + recordDelete_timeMillis_is_not_null),
("delete from RelatedLicences" +
" where licenceIdA in (select licenceId from Licences where " + recordDelete_timeMillis_is_not_null + ")" +
" or licenceIdB in (select licenceId from Licences where " + recordDelete_timeMillis_is_not_null + ")"),
("delete from LicenceFields" +
" where licenceId in (select licenceId from Licences where " + recordDelete_timeMillis_is_not_null + ")"),
("delete from LicenceApprovals" +
" where licenceId in (select licenceId from Licences where " + recordDelete_timeMillis_is_not_null + ")"),
("delete from LicenceAdditionalFees" +
" where licenceId in (select licenceId from Licences where " + recordDelete_timeMillis_is_not_null + ")"),
("delete from Licences" +
" where " + recordDelete_timeMillis_is_not_null +
" and licenceId not in (select licenceId from LicenceAdditionalFees)" +
" and licenceId not in (select licenceId from LicenceFields)" +
" and licenceId not in (select licenceId from LicenceApprovals)" +
" and licenceId not in (select licenceId from LicenceTransactions)" +
" and licenceId not in (select licenceIdA from RelatedLicences)" +
" and licenceId not in (select licenceIdB from RelatedLicences)"),
("delete from LicenceCategoryFields" +
" where (" + recordDelete_timeMillis_is_not_null + " or licenceCategoryKey in (select licenceCategoryKey from LicenceCategories where " + recordDelete_timeMillis_is_not_null + "))" +
" and licenceFieldKey not in (select licenceFieldKey from LicenceFields)"),
("delete from LicenceCategoryApprovals" +
" where (" + recordDelete_timeMillis_is_not_null + " or licenceCategoryKey in (select licenceCategoryKey from LicenceCategories where " + recordDelete_timeMillis_is_not_null + "))" +
" and licenceApprovalKey not in (select licenceApprovalKey from LicenceApprovals)"),
("delete from LicenceCategoryAdditionalFees" +
" where (" + recordDelete_timeMillis_is_not_null + " or licenceCategoryKey in (select licenceCategoryKey from LicenceCategories where " + recordDelete_timeMillis_is_not_null + "))" +
" and licenceAdditionalFeeKey not in (select licenceAdditionalFeeKey from LicenceAdditionalFees)"),
("delete from LicenceCategoryFees" +
" where (" + recordDelete_timeMillis_is_not_null + " or licenceCategoryKey in (select licenceCategoryKey from LicenceCategories where " + recordDelete_timeMillis_is_not_null + "))" +
" and licenceCategoryKey not in (select licenceCategoryKey from Licences)"),
("delete from LicenceCategories" +
" where " + recordDelete_timeMillis_is_not_null +
" and licenceCategoryKey not in (select licenceCategoryKey from Licences)" +
" and licenceCategoryKey not in (select licenceCategoryKey from LicenceCategoryFields)" +
" and licenceCategoryKey not in (select licenceCategoryKey from LicenceCategoryApprovals)" +
" and licenceCategoryKey not in (select licenceCategoryKey from LicenceAdditionalFees)" +
" and licenceCategoryKey not in (select licenceCategoryKey from LicenceCategoryFees)")
];
export const cleanupDatabase = () => {
const database = sqlite(databasePath);
let rowCount = 0;
for (const cleanupQuery of cleanupQueries) {
rowCount += database.prepare(cleanupQuery).run().changes;
}
database.close();
return rowCount;
};
export default cleanupDatabase;
97 changes: 97 additions & 0 deletions helpers/licencesDB/cleanupDatabase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import sqlite from "better-sqlite3";
import { licencesDB as databasePath } from "../../data/databasePaths.js";


// Avoid mistyping!!!
const recordDelete_timeMillis_is_not_null = "recordDelete_timeMillis is not null";


const cleanupQueries = [

/*
* Licences
*/

// Purge deleted transactions
("delete from LicenceTransactions" +
" where " + recordDelete_timeMillis_is_not_null),

// Purge related licences
("delete from RelatedLicences" +
" where licenceIdA in (select licenceId from Licences where " + recordDelete_timeMillis_is_not_null + ")" +
" or licenceIdB in (select licenceId from Licences where " + recordDelete_timeMillis_is_not_null + ")"),

// Purge fields
("delete from LicenceFields" +
" where licenceId in (select licenceId from Licences where " + recordDelete_timeMillis_is_not_null + ")"),

// Purge approvals
("delete from LicenceApprovals" +
" where licenceId in (select licenceId from Licences where " + recordDelete_timeMillis_is_not_null + ")"),

// Purge additional fees
("delete from LicenceAdditionalFees" +
" where licenceId in (select licenceId from Licences where " + recordDelete_timeMillis_is_not_null + ")"),

// Purge deleted licences with no foreign keys
("delete from Licences" +
" where " + recordDelete_timeMillis_is_not_null +
" and licenceId not in (select licenceId from LicenceAdditionalFees)" +
" and licenceId not in (select licenceId from LicenceFields)" +
" and licenceId not in (select licenceId from LicenceApprovals)" +
" and licenceId not in (select licenceId from LicenceTransactions)" +
" and licenceId not in (select licenceIdA from RelatedLicences)" +
" and licenceId not in (select licenceIdB from RelatedLicences)"),

/*
* Licence Categories
*/

// Purge fields
("delete from LicenceCategoryFields" +
" where (" + recordDelete_timeMillis_is_not_null + " or licenceCategoryKey in (select licenceCategoryKey from LicenceCategories where " + recordDelete_timeMillis_is_not_null + "))" +
" and licenceFieldKey not in (select licenceFieldKey from LicenceFields)"),

// Purge approvals
("delete from LicenceCategoryApprovals" +
" where (" + recordDelete_timeMillis_is_not_null + " or licenceCategoryKey in (select licenceCategoryKey from LicenceCategories where " + recordDelete_timeMillis_is_not_null + "))" +
" and licenceApprovalKey not in (select licenceApprovalKey from LicenceApprovals)"),

// Purge additional fees
("delete from LicenceCategoryAdditionalFees" +
" where (" + recordDelete_timeMillis_is_not_null + " or licenceCategoryKey in (select licenceCategoryKey from LicenceCategories where " + recordDelete_timeMillis_is_not_null + "))" +
" and licenceAdditionalFeeKey not in (select licenceAdditionalFeeKey from LicenceAdditionalFees)"),

// Purge fees
("delete from LicenceCategoryFees" +
" where (" + recordDelete_timeMillis_is_not_null + " or licenceCategoryKey in (select licenceCategoryKey from LicenceCategories where " + recordDelete_timeMillis_is_not_null + "))" +
" and licenceCategoryKey not in (select licenceCategoryKey from Licences)"),

// Purge categories
("delete from LicenceCategories" +
" where " + recordDelete_timeMillis_is_not_null +
" and licenceCategoryKey not in (select licenceCategoryKey from Licences)" +
" and licenceCategoryKey not in (select licenceCategoryKey from LicenceCategoryFields)" +
" and licenceCategoryKey not in (select licenceCategoryKey from LicenceCategoryApprovals)" +
" and licenceCategoryKey not in (select licenceCategoryKey from LicenceAdditionalFees)" +
" and licenceCategoryKey not in (select licenceCategoryKey from LicenceCategoryFees)")
];


export const cleanupDatabase = (): number => {

const database = sqlite(databasePath);

let rowCount = 0;

for (const cleanupQuery of cleanupQueries) {
rowCount += database.prepare(cleanupQuery).run().changes;
}

database.close();

return rowCount;
};


export default cleanupDatabase;
1 change: 1 addition & 0 deletions public-typescript/admin-cleanup.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
53 changes: 53 additions & 0 deletions public-typescript/admin-cleanup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
(() => {
const urlPrefix = document.querySelector("main").dataset.urlPrefix;
document.querySelector("#cleanup--backupDatabase").addEventListener("click", (clickEvent) => {
clickEvent.preventDefault();
const doBackup = () => {
cityssm.postJSON(urlPrefix + "/admin/doBackupDatabase", {}, (responseJSON) => {
if (responseJSON.success) {
bulmaJS.alert({
title: "Database Backed Up Successfully",
message: "Database backed up as <strong>" + cityssm.escapeHTML(responseJSON.fileName) + "</strong>.",
messageIsHtml: true,
contextualColorName: "success"
});
}
});
};
bulmaJS.confirm({
title: "Backup Database",
message: "To ensure all data is backed up properly," +
" please make sure all users with update privileges avoid making changes while the backup is running.",
contextualColorName: "info",
okButton: {
text: "Backup Database Now",
callbackFunction: doBackup
}
});
});
document.querySelector("#cleanup--cleanupDatabase").addEventListener("click", (clickEvent) => {
clickEvent.preventDefault();
const doCleanup = () => {
cityssm.postJSON(urlPrefix + "/admin/doCleanupDatabase", {}, (responseJSON) => {
if (responseJSON.success) {
bulmaJS.alert({
title: "Database Cleaned Up Successfully",
message: responseJSON.rowCount + " row" + (responseJSON.rowCount === 1 ? "" : "s") + " deleted.",
contextualColorName: "success"
});
}
});
};
bulmaJS.confirm({
title: "Cleanup Database",
message: "Are you sure you want to cleanup the database?",
contextualColorName: "info",
okButton: {
text: "Cleanup Database Now",
callbackFunction: doCleanup
}
});
});
})();
71 changes: 71 additions & 0 deletions public-typescript/admin-cleanup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* eslint-disable unicorn/filename-case, unicorn/prefer-module */

import type { cityssmGlobal } from "@cityssm/bulma-webapp-js/src/types";
import type { BulmaJS } from "@cityssm/bulma-js/types";

declare const cityssm: cityssmGlobal;
declare const bulmaJS: BulmaJS;

(() => {
const urlPrefix = document.querySelector("main").dataset.urlPrefix;

document.querySelector("#cleanup--backupDatabase").addEventListener("click", (clickEvent) => {
clickEvent.preventDefault();

const doBackup = () => {

cityssm.postJSON(urlPrefix + "/admin/doBackupDatabase", {},
(responseJSON: { success: boolean; fileName?: string; }) => {

if (responseJSON.success) {
bulmaJS.alert({
title: "Database Backed Up Successfully",
message: "Database backed up as <strong>" + cityssm.escapeHTML(responseJSON.fileName) + "</strong>.",
messageIsHtml: true,
contextualColorName: "success"
});
}
});
};

bulmaJS.confirm({
title: "Backup Database",
message: "To ensure all data is backed up properly," +
" please make sure all users with update privileges avoid making changes while the backup is running.",
contextualColorName: "info",
okButton: {
text: "Backup Database Now",
callbackFunction: doBackup
}
});
});

document.querySelector("#cleanup--cleanupDatabase").addEventListener("click", (clickEvent) => {
clickEvent.preventDefault();

const doCleanup = () => {

cityssm.postJSON(urlPrefix + "/admin/doCleanupDatabase", {},
(responseJSON: { success: boolean; rowCount?: number; }) => {

if (responseJSON.success) {
bulmaJS.alert({
title: "Database Cleaned Up Successfully",
message: responseJSON.rowCount + " row" + (responseJSON.rowCount === 1 ? "" : "s") + " deleted.",
contextualColorName: "success"
});
}
});
};

bulmaJS.confirm({
title: "Cleanup Database",
message: "Are you sure you want to cleanup the database?",
contextualColorName: "info",
okButton: {
text: "Cleanup Database Now",
callbackFunction: doCleanup
}
});
});
})();
1 change: 1 addition & 0 deletions public/javascripts/admin-cleanup.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion routes/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import handler_doAddLicenceCategoryAdditionalFee from "../handlers/admin-post/do
import handler_doUpdateLicenceCategoryAdditionalFee from "../handlers/admin-post/doUpdateLicenceCategoryAdditionalFee.js";
import handler_doMoveLicenceCategoryAdditionalFee from "../handlers/admin-post/doMoveLicenceCategoryAdditionalFee.js";
import handler_doDeleteLicenceCategoryAdditionalFee from "../handlers/admin-post/doDeleteLicenceCategoryAdditionalFee.js";
import handler_yearEnd from "../handlers/admin-get/yearEnd.js";
import handler_doBackupDatabase from "../handlers/admin-post/doBackupDatabase.js";
import handler_yearEnd from "../handlers/admin-get/yearEnd.js";
import handler_doRefreshDatabase from "../handlers/admin-post/doRefreshDatabase.js";
import handler_cleanup from "../handlers/admin-get/cleanup.js";
import handler_doCleanupDatabase from "../handlers/admin-post/doCleanupDatabase.js";
export const router = Router();
router.get("/licenceCategories", permissionHandlers.adminGetHandler, handler_licenceCategories);
router.post("/doGetLicenceCategories", permissionHandlers.adminPostHandler, handler_doGetLicenceCategories);
Expand Down Expand Up @@ -52,4 +54,6 @@ if (configFunctions.getProperty("settings.includeYearEnd")) {
router.get("/yearEnd", permissionHandlers.adminGetHandler, handler_yearEnd);
router.post("/doRefreshDatabase", permissionHandlers.adminPostHandler, handler_doRefreshDatabase);
}
router.get("/cleanup", permissionHandlers.adminGetHandler, handler_cleanup);
router.post("/doCleanupDatabase", permissionHandlers.adminPostHandler, handler_doCleanupDatabase);
export default router;
Loading

0 comments on commit f491ec9

Please sign in to comment.