diff --git a/dist/package-shared/functions/backend/backups/su/add-backup/index.d.ts b/dist/package-shared/functions/backend/backups/su/add-backup/index.d.ts new file mode 100644 index 0000000..030e08f --- /dev/null +++ b/dist/package-shared/functions/backend/backups/su/add-backup/index.d.ts @@ -0,0 +1,6 @@ +import { APIResponseObject } from "../../../../../types"; +type Params = { + targetUserId?: string | number; +}; +export default function suAddBackup({ targetUserId, }: Params): Promise; +export {}; diff --git a/dist/package-shared/functions/backend/backups/su/add-backup/index.js b/dist/package-shared/functions/backend/backups/su/add-backup/index.js new file mode 100644 index 0000000..8334a02 --- /dev/null +++ b/dist/package-shared/functions/backend/backups/su/add-backup/index.js @@ -0,0 +1,71 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = suAddBackup; +const write_backup_files_1 = __importDefault(require("./write-backup-files")); +const grab_dir_names_1 = __importDefault(require("../../../../../utils/backend/names/grab-dir-names")); +const addDbEntry_1 = __importDefault(require("../../../db/addDbEntry")); +const numberfy_1 = __importDefault(require("../../../../../utils/numberfy")); +const grab_user_resource_1 = __importDefault(require("../../../../web-app/db/grab-user-resource")); +function suAddBackup(_a) { + return __awaiter(this, arguments, void 0, function* ({ targetUserId, }) { + var _b, _c; + try { + const { mainBackupDir, userBackupDir } = (0, grab_dir_names_1.default)({ + userId: targetUserId, + }); + if (targetUserId && !userBackupDir) { + return { + success: false, + msg: `Error grabbing user backup directory`, + }; + } + const newBackup = { + user_id: targetUserId ? (0, numberfy_1.default)(targetUserId) : undefined, + }; + const newBackupEntry = yield (0, addDbEntry_1.default)({ + tableName: "backups", + data: newBackup, + }); + if (!((_b = newBackupEntry === null || newBackupEntry === void 0 ? void 0 : newBackupEntry.payload) === null || _b === void 0 ? void 0 : _b.insertId)) { + return { + success: false, + msg: `Couldn't create new backup entry`, + }; + } + const { single: newlyAddedBackup } = yield (0, grab_user_resource_1.default)({ + tableName: "backups", + targetID: (_c = newBackupEntry.payload) === null || _c === void 0 ? void 0 : _c.insertId, + isSuperUser: true, + }); + if (!(newlyAddedBackup === null || newlyAddedBackup === void 0 ? void 0 : newlyAddedBackup.id)) { + return { + success: false, + msg: `Couldn't fetch newly added backup`, + }; + } + const writeBackup = yield (0, write_backup_files_1.default)({ + backup: newlyAddedBackup, + }); + return writeBackup; + } + catch (error) { + return { + success: false, + msg: `Backup add failed`, + error: error.message, + }; + } + }); +} diff --git a/dist/package-shared/functions/backend/backups/su/add-backup/restore-backup.d.ts b/dist/package-shared/functions/backend/backups/su/add-backup/restore-backup.d.ts new file mode 100644 index 0000000..9a9fd69 --- /dev/null +++ b/dist/package-shared/functions/backend/backups/su/add-backup/restore-backup.d.ts @@ -0,0 +1,8 @@ +import { APIResponseObject, UserType } from "../../../../../types"; +import { DSQL_DATASQUIREL_BACKUPS } from "../../../../../types/dsql"; +type Params = { + user: UserType; + backup: DSQL_DATASQUIREL_BACKUPS; +}; +export default function suRestoreBackup({ user, backup, }: Params): Promise; +export {}; diff --git a/dist/package-shared/functions/backend/backups/su/add-backup/restore-backup.js b/dist/package-shared/functions/backend/backups/su/add-backup/restore-backup.js new file mode 100644 index 0000000..54f3cb3 --- /dev/null +++ b/dist/package-shared/functions/backend/backups/su/add-backup/restore-backup.js @@ -0,0 +1,81 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = suRestoreBackup; +const fs_1 = __importDefault(require("fs")); +const path_1 = __importDefault(require("path")); +const grab_dir_names_1 = __importDefault(require("../../../../../utils/backend/names/grab-dir-names")); +const import_mariadb_database_1 = __importDefault(require("../../../../../utils/backend/import-mariadb-database")); +function suRestoreBackup(_a) { + return __awaiter(this, arguments, void 0, function* ({ user, backup, }) { + try { + const { mainBackupDir, userBackupDir, sqlBackupDirName, schemasBackupDirName, targetUserPrivateDir, oldSchemasDir, } = (0, grab_dir_names_1.default)({ + userId: backup.user_id, + }); + if (backup.user_id && !userBackupDir) { + return { + success: false, + msg: `Error grabbing user backup directory`, + }; + } + if (!backup.uuid) { + return { + success: false, + msg: `No UUID found for backup`, + }; + } + const existingBackupDir = path_1.default.join(backup.user_id && userBackupDir ? userBackupDir : mainBackupDir, backup.uuid); + const userDatabases = fs_1.default.readdirSync(path_1.default.join(existingBackupDir, sqlBackupDirName)); + const databasesToRestore = (userDatabases === null || userDatabases === void 0 ? void 0 : userDatabases.map((db) => db.split(".")[0])) || [process.env.DSQL_DB_NAME || "datasquirel"]; + for (let i = 0; i < databasesToRestore.length; i++) { + const dbToBackup = databasesToRestore[i]; + if (!dbToBackup) + continue; + const dbFileName = `${dbToBackup}.sql`; + const dbFilePath = path_1.default.join(existingBackupDir, sqlBackupDirName, dbFileName); + (0, import_mariadb_database_1.default)({ + dbFullName: dbToBackup, + targetFilePath: dbFilePath, + }); + } + const userSchemaDirFiles = targetUserPrivateDir + ? fs_1.default + .readdirSync(targetUserPrivateDir) + .filter((dirName) => dirName.match(/^\d+\.json$/)) + : undefined; + const appSchemaDirFiles = fs_1.default + .readdirSync(oldSchemasDir) + .filter((dirName) => dirName.match(/^\d+\.json$/)); + const schemaFilesToWrite = backup.user_id && userSchemaDirFiles + ? userSchemaDirFiles + : appSchemaDirFiles; + for (let i = 0; i < schemaFilesToWrite.length; i++) { + const schemaFileName = schemaFilesToWrite[i]; + const originSchemaFilePath = path_1.default.join(existingBackupDir, schemasBackupDirName, schemaFileName); + const destinationSchemaFilePath = path_1.default.join(backup.user_id && targetUserPrivateDir + ? targetUserPrivateDir + : oldSchemasDir, schemaFileName); + fs_1.default.copyFileSync(originSchemaFilePath, destinationSchemaFilePath); + } + return { success: true }; + } + catch (error) { + return { + success: false, + msg: `Failed to write backup files`, + error: error.message, + }; + } + }); +} diff --git a/dist/package-shared/functions/backend/backups/su/add-backup/write-backup-files.d.ts b/dist/package-shared/functions/backend/backups/su/add-backup/write-backup-files.d.ts new file mode 100644 index 0000000..478b578 --- /dev/null +++ b/dist/package-shared/functions/backend/backups/su/add-backup/write-backup-files.d.ts @@ -0,0 +1,7 @@ +import { DSQL_DATASQUIREL_BACKUPS } from "../../../../../types/dsql"; +import { APIResponseObject } from "../../../../../types"; +type Params = { + backup: DSQL_DATASQUIREL_BACKUPS; +}; +export default function writeBackupFiles({ backup, }: Params): Promise; +export {}; diff --git a/dist/package-shared/functions/backend/backups/su/add-backup/write-backup-files.js b/dist/package-shared/functions/backend/backups/su/add-backup/write-backup-files.js new file mode 100644 index 0000000..6b6fc05 --- /dev/null +++ b/dist/package-shared/functions/backend/backups/su/add-backup/write-backup-files.js @@ -0,0 +1,94 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = writeBackupFiles; +const fs_1 = __importDefault(require("fs")); +const path_1 = __importDefault(require("path")); +const grab_dir_names_1 = __importDefault(require("../../../../../utils/backend/names/grab-dir-names")); +const app_names_1 = require("../../../../../dict/app-names"); +const dbHandler_1 = __importDefault(require("../../../dbHandler")); +const export_mariadb_database_1 = __importDefault(require("../../../../../utils/backend/export-mariadb-database")); +function writeBackupFiles(_a) { + return __awaiter(this, arguments, void 0, function* ({ backup, }) { + try { + const { mainBackupDir, userBackupDir, sqlBackupDirName, schemasBackupDirName, targetUserPrivateDir, oldSchemasDir, } = (0, grab_dir_names_1.default)({ + userId: backup.user_id, + }); + if (backup.user_id && !userBackupDir) { + return { + success: false, + msg: `Error grabbing user backup directory`, + }; + } + if (!backup.uuid) { + return { + success: false, + msg: `No UUID found for backup`, + }; + } + const newBackupDir = path_1.default.join(backup.user_id && userBackupDir ? userBackupDir : mainBackupDir, backup.uuid); + fs_1.default.mkdirSync(newBackupDir, { recursive: true }); + fs_1.default.mkdirSync(path_1.default.join(newBackupDir, sqlBackupDirName), { + recursive: true, + }); + fs_1.default.mkdirSync(path_1.default.join(newBackupDir, schemasBackupDirName), { + recursive: true, + }); + const userDatabases = backup.user_id + ? (yield (0, dbHandler_1.default)({ + query: `SHOW DATABASES LIKE '${app_names_1.AppNames["DsqlDbPrefix"]}${backup.user_id}_%'`, + })) + : undefined; + const databasesToBackup = (userDatabases === null || userDatabases === void 0 ? void 0 : userDatabases.map((db) => Object.values(db)[0])) || [process.env.DSQL_DB_NAME || "datasquirel"]; + for (let i = 0; i < databasesToBackup.length; i++) { + const dbToBackup = databasesToBackup[i]; + if (!dbToBackup) + continue; + const dbFileName = `${dbToBackup}.sql`; + const dbFilePath = path_1.default.join(newBackupDir, sqlBackupDirName, dbFileName); + (0, export_mariadb_database_1.default)({ + dbFullName: dbToBackup, + targetFilePath: dbFilePath, + }); + } + const userSchemaDirFiles = targetUserPrivateDir + ? fs_1.default + .readdirSync(targetUserPrivateDir) + .filter((dirName) => dirName.match(/^\d+\.json$/)) + : undefined; + const appSchemaDirFiles = fs_1.default + .readdirSync(oldSchemasDir) + .filter((dirName) => dirName.match(/^\d+\.json$/)); + const schemaFilesToWrite = backup.user_id && userSchemaDirFiles + ? userSchemaDirFiles + : appSchemaDirFiles; + for (let i = 0; i < schemaFilesToWrite.length; i++) { + const schemaFileName = schemaFilesToWrite[i]; + const originSchemaFilePath = path_1.default.join(backup.user_id && targetUserPrivateDir + ? targetUserPrivateDir + : oldSchemasDir, schemaFileName); + const destinationSchemaFilePath = path_1.default.join(newBackupDir, schemasBackupDirName, schemaFileName); + fs_1.default.copyFileSync(originSchemaFilePath, destinationSchemaFilePath); + } + return { success: true }; + } + catch (error) { + return { + success: false, + msg: `Failed to write backup files`, + error: error.message, + }; + } + }); +} diff --git a/dist/package-shared/functions/backend/backups/su/delete-backup.d.ts b/dist/package-shared/functions/backend/backups/su/delete-backup.d.ts new file mode 100644 index 0000000..96938c0 --- /dev/null +++ b/dist/package-shared/functions/backend/backups/su/delete-backup.d.ts @@ -0,0 +1,7 @@ +import { DSQL_DATASQUIREL_BACKUPS } from "../../../../types/dsql"; +import { APIResponseObject } from "../../../../types"; +type Params = { + backup: DSQL_DATASQUIREL_BACKUPS; +}; +export default function deleteBackup({ backup, }: Params): Promise; +export {}; diff --git a/dist/package-shared/functions/backend/backups/su/delete-backup.js b/dist/package-shared/functions/backend/backups/su/delete-backup.js new file mode 100644 index 0000000..ad01be8 --- /dev/null +++ b/dist/package-shared/functions/backend/backups/su/delete-backup.js @@ -0,0 +1,56 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = deleteBackup; +const fs_1 = __importDefault(require("fs")); +const path_1 = __importDefault(require("path")); +const grab_dir_names_1 = __importDefault(require("../../../../utils/backend/names/grab-dir-names")); +const deleteDbEntry_1 = __importDefault(require("../../db/deleteDbEntry")); +const numberfy_1 = __importDefault(require("../../../../utils/numberfy")); +function deleteBackup(_a) { + return __awaiter(this, arguments, void 0, function* ({ backup, }) { + try { + const { mainBackupDir, userBackupDir } = (0, grab_dir_names_1.default)({ + userId: backup.user_id, + }); + if (backup.user_id && !userBackupDir) { + return { + success: false, + msg: `Error grabbing user backup directory`, + }; + } + if (!backup.uuid) { + return { + success: false, + msg: `No UUID found for backup`, + }; + } + const newBackupDir = path_1.default.join(backup.user_id && userBackupDir ? userBackupDir : mainBackupDir, backup.uuid); + fs_1.default.rmSync(newBackupDir, { recursive: true, force: true }); + yield (0, deleteDbEntry_1.default)({ + identifierColumnName: "id", + identifierValue: (0, numberfy_1.default)(backup.id), + tableName: "backups", + }); + return { success: true }; + } + catch (error) { + return { + success: false, + msg: `Failed to write backup files`, + error: error.message, + }; + } + }); +} diff --git a/dist/package-shared/functions/backend/handle-backup.js b/dist/package-shared/functions/backend/handle-backup.js index 61fc2f7..cffb7e8 100644 --- a/dist/package-shared/functions/backend/handle-backup.js +++ b/dist/package-shared/functions/backend/handle-backup.js @@ -13,10 +13,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = handleBackup; -const grab_user_resource_1 = __importDefault(require("@/src/functions/db/grab-user-resource")); -const add_backup_1 = __importDefault(require("@/src/functions/api/su/add-backup")); -const grab_config_1 = __importDefault(require("@/package-shared/utils/backend/config/grab-config")); -const delete_backup_1 = __importDefault(require("@/src/functions/api/su/add-backup/delete-backup")); +const grab_config_1 = __importDefault(require("../../utils/backend/config/grab-config")); +const grab_user_resource_1 = __importDefault(require("../web-app/db/grab-user-resource")); +const add_backup_1 = __importDefault(require("./backups/su/add-backup")); +const delete_backup_1 = __importDefault(require("./backups/su/delete-backup")); function handleBackup(_a) { return __awaiter(this, arguments, void 0, function* ({ appBackup, userId, }) { var _b; diff --git a/dist/package-shared/utils/docker-test-db-connection.js b/dist/package-shared/utils/docker-test-db-connection.js index 593dd6f..80ded12 100644 --- a/dist/package-shared/utils/docker-test-db-connection.js +++ b/dist/package-shared/utils/docker-test-db-connection.js @@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.default = dockerTestDbConnection; const debug_log_1 = __importDefault(require("./logging/debug-log")); const mariadb_local_query_1 = __importDefault(require("./mariadb-local-query")); +const sleep_1 = __importDefault(require("./sleep")); let checkDbRetries = 0; const MAX_CHECK_DB_RETRIES = 10; const DEFAULT_SLEEP_TIME = 3000; @@ -39,7 +40,7 @@ function dockerTestDbConnection(params) { } else { checkDbRetries++; - yield Bun.sleep(sleepTime); + yield (0, sleep_1.default)(sleepTime); } } }); diff --git a/dist/package-shared/utils/mariadb-local-query.js b/dist/package-shared/utils/mariadb-local-query.js index 93fad9e..4890781 100644 --- a/dist/package-shared/utils/mariadb-local-query.js +++ b/dist/package-shared/utils/mariadb-local-query.js @@ -5,9 +5,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); exports.default = mariaDBlocalQuery; exports.removeQueryDoubleQuotes = removeQueryDoubleQuotes; -const grab_docker_stack_services_names_1 = __importDefault(require("@/package-shared/utils/backend/names/grab-docker-stack-services-names")); -const normalize_text_1 = __importDefault(require("@/package-shared/utils/normalize-text")); +const grab_docker_stack_services_names_1 = __importDefault(require("./backend/names/grab-docker-stack-services-names")); const execute_1 = __importDefault(require("./execute")); +const normalize_text_1 = __importDefault(require("./normalize-text")); function mariaDBlocalQuery(query) { const { dbServiceName, maxScaleServiceName } = (0, grab_docker_stack_services_names_1.default)(); const MARIADB_CMD_PREFIX = `docker exec ${dbServiceName} mariadb -u root -p"${process.env.DSQL_MARIADB_ROOT_PASSWORD}"`; diff --git a/dist/package-shared/utils/sleep.d.ts b/dist/package-shared/utils/sleep.d.ts new file mode 100644 index 0000000..b559aa4 --- /dev/null +++ b/dist/package-shared/utils/sleep.d.ts @@ -0,0 +1 @@ +export default function sleep(time: number): Promise; diff --git a/dist/package-shared/utils/sleep.js b/dist/package-shared/utils/sleep.js new file mode 100644 index 0000000..e4189d3 --- /dev/null +++ b/dist/package-shared/utils/sleep.js @@ -0,0 +1,17 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = sleep; +function sleep(time) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve) => setTimeout(resolve, time)); + }); +} diff --git a/dist/package-shared/utils/test-db-connection.js b/dist/package-shared/utils/test-db-connection.js index 743aaac..9e1fc84 100644 --- a/dist/package-shared/utils/test-db-connection.js +++ b/dist/package-shared/utils/test-db-connection.js @@ -14,6 +14,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); exports.default = testDbConnection; const dbHandler_1 = __importDefault(require("../functions/backend/dbHandler")); +const sleep_1 = __importDefault(require("./sleep")); let testDbConnRetries = 0; const MAX_TEST_DB_CONN_RETRIES = 10; const SLEEP_TIME = 2000; @@ -28,7 +29,7 @@ function testDbConnection(params) { console.log("Database Connection Complete!"); break; } - yield Bun.sleep((params === null || params === void 0 ? void 0 : params.sleepTime) || SLEEP_TIME); + yield (0, sleep_1.default)((params === null || params === void 0 ? void 0 : params.sleepTime) || SLEEP_TIME); if (testDbConnRetries > ((params === null || params === void 0 ? void 0 : params.maxRetries) || MAX_TEST_DB_CONN_RETRIES)) { console.log("Database Connection Failed!"); @@ -36,7 +37,7 @@ function testDbConnection(params) { } } catch (error) { - yield Bun.sleep((params === null || params === void 0 ? void 0 : params.sleepTime) || SLEEP_TIME); + yield (0, sleep_1.default)((params === null || params === void 0 ? void 0 : params.sleepTime) || SLEEP_TIME); } } return true; diff --git a/package-shared/functions/api/users/api-create-user.ts b/package-shared/functions/api/users/api-create-user.ts index 7d2b7b3..7612c45 100644 --- a/package-shared/functions/api/users/api-create-user.ts +++ b/package-shared/functions/api/users/api-create-user.ts @@ -6,7 +6,7 @@ import updateUsersTableSchema from "../../backend/updateUsersTableSchema"; import dbHandler from "../../backend/dbHandler"; import hashPassword from "../../dsql/hashPassword"; import validateEmail from "../../email/fns/validate-email"; -import { DSQL_DATASQUIREL_USERS } from "@/package-shared/types/dsql"; +import { DSQL_DATASQUIREL_USERS } from "../../../types/dsql"; /** * # API Create User diff --git a/package-shared/functions/backend/backups/su/add-backup/index.ts b/package-shared/functions/backend/backups/su/add-backup/index.ts new file mode 100644 index 0000000..21709e3 --- /dev/null +++ b/package-shared/functions/backend/backups/su/add-backup/index.ts @@ -0,0 +1,78 @@ +import _ from "lodash"; +import path from "path"; +import writeBacupFiles from "./write-backup-files"; +import { APIResponseObject } from "../../../../../types"; +import grabDirNames from "../../../../../utils/backend/names/grab-dir-names"; +import { + DSQL_DATASQUIREL_BACKUPS, + DsqlTables, +} from "../../../../../types/dsql"; +import addDbEntry from "../../../db/addDbEntry"; +import numberfy from "../../../../../utils/numberfy"; +import dbGrabUserResource from "../../../../web-app/db/grab-user-resource"; + +type Params = { + targetUserId?: string | number; +}; + +export default async function suAddBackup({ + targetUserId, +}: Params): Promise { + try { + const { mainBackupDir, userBackupDir } = grabDirNames({ + userId: targetUserId, + }); + + if (targetUserId && !userBackupDir) { + return { + success: false, + msg: `Error grabbing user backup directory`, + }; + } + + const newBackup: DSQL_DATASQUIREL_BACKUPS = { + user_id: targetUserId ? numberfy(targetUserId) : undefined, + }; + + const newBackupEntry = await addDbEntry< + DSQL_DATASQUIREL_BACKUPS, + (typeof DsqlTables)[number] + >({ + tableName: "backups", + data: newBackup, + }); + + if (!newBackupEntry?.payload?.insertId) { + return { + success: false, + msg: `Couldn't create new backup entry`, + }; + } + + const { single: newlyAddedBackup } = + await dbGrabUserResource({ + tableName: "backups", + targetID: newBackupEntry.payload?.insertId, + isSuperUser: true, + }); + + if (!newlyAddedBackup?.id) { + return { + success: false, + msg: `Couldn't fetch newly added backup`, + }; + } + + const writeBackup = await writeBacupFiles({ + backup: newlyAddedBackup, + }); + + return writeBackup; + } catch (error: any) { + return { + success: false, + msg: `Backup add failed`, + error: error.message, + }; + } +} diff --git a/package-shared/functions/backend/backups/su/add-backup/restore-backup.ts b/package-shared/functions/backend/backups/su/add-backup/restore-backup.ts new file mode 100644 index 0000000..6886918 --- /dev/null +++ b/package-shared/functions/backend/backups/su/add-backup/restore-backup.ts @@ -0,0 +1,114 @@ +import fs from "fs"; +import path from "path"; +import grabDirNames from "../../../../../utils/backend/names/grab-dir-names"; +import { APIResponseObject, UserType } from "../../../../../types"; +import { DSQL_DATASQUIREL_BACKUPS } from "../../../../../types/dsql"; +import importMariadbDatabase from "../../../../../utils/backend/import-mariadb-database"; + +type Params = { + user: UserType; + backup: DSQL_DATASQUIREL_BACKUPS; +}; + +export default async function suRestoreBackup({ + user, + backup, +}: Params): Promise { + try { + const { + mainBackupDir, + userBackupDir, + sqlBackupDirName, + schemasBackupDirName, + targetUserPrivateDir, + oldSchemasDir, + } = grabDirNames({ + userId: backup.user_id, + }); + + if (backup.user_id && !userBackupDir) { + return { + success: false, + msg: `Error grabbing user backup directory`, + }; + } + + if (!backup.uuid) { + return { + success: false, + msg: `No UUID found for backup`, + }; + } + + const existingBackupDir = path.join( + backup.user_id && userBackupDir ? userBackupDir : mainBackupDir, + backup.uuid + ); + + const userDatabases = fs.readdirSync( + path.join(existingBackupDir, sqlBackupDirName) + ); + + const databasesToRestore = userDatabases?.map( + (db) => db.split(".")[0] + ) || [process.env.DSQL_DB_NAME || "datasquirel"]; + + for (let i = 0; i < databasesToRestore.length; i++) { + const dbToBackup = databasesToRestore[i]; + if (!dbToBackup) continue; + + const dbFileName = `${dbToBackup}.sql`; + const dbFilePath = path.join( + existingBackupDir, + sqlBackupDirName, + dbFileName + ); + + importMariadbDatabase({ + dbFullName: dbToBackup, + targetFilePath: dbFilePath, + }); + } + + const userSchemaDirFiles = targetUserPrivateDir + ? fs + .readdirSync(targetUserPrivateDir) + .filter((dirName) => dirName.match(/^\d+\.json$/)) + : undefined; + const appSchemaDirFiles = fs + .readdirSync(oldSchemasDir) + .filter((dirName) => dirName.match(/^\d+\.json$/)); + + const schemaFilesToWrite = + backup.user_id && userSchemaDirFiles + ? userSchemaDirFiles + : appSchemaDirFiles; + + for (let i = 0; i < schemaFilesToWrite.length; i++) { + const schemaFileName = schemaFilesToWrite[i]; + + const originSchemaFilePath = path.join( + existingBackupDir, + schemasBackupDirName, + schemaFileName + ); + + const destinationSchemaFilePath = path.join( + backup.user_id && targetUserPrivateDir + ? targetUserPrivateDir + : oldSchemasDir, + schemaFileName + ); + + fs.copyFileSync(originSchemaFilePath, destinationSchemaFilePath); + } + + return { success: true }; + } catch (error: any) { + return { + success: false, + msg: `Failed to write backup files`, + error: error.message, + }; + } +} diff --git a/package-shared/functions/backend/backups/su/add-backup/write-backup-files.ts b/package-shared/functions/backend/backups/su/add-backup/write-backup-files.ts new file mode 100644 index 0000000..a523da6 --- /dev/null +++ b/package-shared/functions/backend/backups/su/add-backup/write-backup-files.ts @@ -0,0 +1,124 @@ +import fs from "fs"; +import path from "path"; +import { DSQL_DATASQUIREL_BACKUPS } from "../../../../../types/dsql"; +import { APIResponseObject } from "../../../../../types"; +import grabDirNames from "../../../../../utils/backend/names/grab-dir-names"; +import { AppNames } from "../../../../../dict/app-names"; +import dbHandler from "../../../dbHandler"; +import exportMariadbDatabase from "../../../../../utils/backend/export-mariadb-database"; + +type Params = { + backup: DSQL_DATASQUIREL_BACKUPS; +}; + +export default async function writeBackupFiles({ + backup, +}: Params): Promise { + try { + const { + mainBackupDir, + userBackupDir, + sqlBackupDirName, + schemasBackupDirName, + targetUserPrivateDir, + oldSchemasDir, + } = grabDirNames({ + userId: backup.user_id, + }); + + if (backup.user_id && !userBackupDir) { + return { + success: false, + msg: `Error grabbing user backup directory`, + }; + } + + if (!backup.uuid) { + return { + success: false, + msg: `No UUID found for backup`, + }; + } + + const newBackupDir = path.join( + backup.user_id && userBackupDir ? userBackupDir : mainBackupDir, + backup.uuid + ); + + fs.mkdirSync(newBackupDir, { recursive: true }); + fs.mkdirSync(path.join(newBackupDir, sqlBackupDirName), { + recursive: true, + }); + fs.mkdirSync(path.join(newBackupDir, schemasBackupDirName), { + recursive: true, + }); + + const userDatabases = backup.user_id + ? ((await dbHandler({ + query: `SHOW DATABASES LIKE '${AppNames["DsqlDbPrefix"]}${backup.user_id}_%'`, + })) as { [k: string]: string }[]) + : undefined; + + const databasesToBackup = userDatabases?.map( + (db) => Object.values(db)[0] + ) || [process.env.DSQL_DB_NAME || "datasquirel"]; + + for (let i = 0; i < databasesToBackup.length; i++) { + const dbToBackup = databasesToBackup[i]; + if (!dbToBackup) continue; + + const dbFileName = `${dbToBackup}.sql`; + const dbFilePath = path.join( + newBackupDir, + sqlBackupDirName, + dbFileName + ); + + exportMariadbDatabase({ + dbFullName: dbToBackup, + targetFilePath: dbFilePath, + }); + } + + const userSchemaDirFiles = targetUserPrivateDir + ? fs + .readdirSync(targetUserPrivateDir) + .filter((dirName) => dirName.match(/^\d+\.json$/)) + : undefined; + const appSchemaDirFiles = fs + .readdirSync(oldSchemasDir) + .filter((dirName) => dirName.match(/^\d+\.json$/)); + + const schemaFilesToWrite = + backup.user_id && userSchemaDirFiles + ? userSchemaDirFiles + : appSchemaDirFiles; + + for (let i = 0; i < schemaFilesToWrite.length; i++) { + const schemaFileName = schemaFilesToWrite[i]; + + const originSchemaFilePath = path.join( + backup.user_id && targetUserPrivateDir + ? targetUserPrivateDir + : oldSchemasDir, + schemaFileName + ); + + const destinationSchemaFilePath = path.join( + newBackupDir, + schemasBackupDirName, + schemaFileName + ); + + fs.copyFileSync(originSchemaFilePath, destinationSchemaFilePath); + } + + return { success: true }; + } catch (error: any) { + return { + success: false, + msg: `Failed to write backup files`, + error: error.message, + }; + } +} diff --git a/package-shared/functions/backend/backups/su/delete-backup.ts b/package-shared/functions/backend/backups/su/delete-backup.ts new file mode 100644 index 0000000..b8a93e7 --- /dev/null +++ b/package-shared/functions/backend/backups/su/delete-backup.ts @@ -0,0 +1,60 @@ +import fs from "fs"; +import path from "path"; +import _ from "lodash"; +import { DSQL_DATASQUIREL_BACKUPS, DsqlTables } from "../../../../types/dsql"; +import { APIResponseObject } from "../../../../types"; +import grabDirNames from "../../../../utils/backend/names/grab-dir-names"; +import deleteDbEntry from "../../db/deleteDbEntry"; +import numberfy from "../../../../utils/numberfy"; + +type Params = { + backup: DSQL_DATASQUIREL_BACKUPS; +}; + +export default async function deleteBackup({ + backup, +}: Params): Promise { + try { + const { mainBackupDir, userBackupDir } = grabDirNames({ + userId: backup.user_id, + }); + + if (backup.user_id && !userBackupDir) { + return { + success: false, + msg: `Error grabbing user backup directory`, + }; + } + + if (!backup.uuid) { + return { + success: false, + msg: `No UUID found for backup`, + }; + } + + const newBackupDir = path.join( + backup.user_id && userBackupDir ? userBackupDir : mainBackupDir, + backup.uuid + ); + + fs.rmSync(newBackupDir, { recursive: true, force: true }); + + await deleteDbEntry< + DSQL_DATASQUIREL_BACKUPS, + (typeof DsqlTables)[number] + >({ + identifierColumnName: "id", + identifierValue: numberfy(backup.id), + tableName: "backups", + }); + + return { success: true }; + } catch (error: any) { + return { + success: false, + msg: `Failed to write backup files`, + error: error.message, + }; + } +} diff --git a/package-shared/functions/backend/handle-backup.ts b/package-shared/functions/backend/handle-backup.ts index 55ecdd9..5bd7de0 100644 --- a/package-shared/functions/backend/handle-backup.ts +++ b/package-shared/functions/backend/handle-backup.ts @@ -1,8 +1,8 @@ -import dbGrabUserResource from "@/src/functions/db/grab-user-resource"; -import { DSQL_DATASQUIREL_BACKUPS } from "@/package-shared/types/dsql"; -import suAddBackup from "@/src/functions/api/su/add-backup"; -import grabConfig from "@/package-shared/utils/backend/config/grab-config"; -import deleteBackup from "@/src/functions/api/su/add-backup/delete-backup"; +import { DSQL_DATASQUIREL_BACKUPS } from "../../types/dsql"; +import grabConfig from "../../utils/backend/config/grab-config"; +import dbGrabUserResource from "../web-app/db/grab-user-resource"; +import suAddBackup from "./backups/su/add-backup"; +import deleteBackup from "./backups/su/delete-backup"; type HandleBackupParams = { appBackup?: boolean; diff --git a/package-shared/utils/docker-test-db-connection.ts b/package-shared/utils/docker-test-db-connection.ts index 84b7063..4ef0ad1 100644 --- a/package-shared/utils/docker-test-db-connection.ts +++ b/package-shared/utils/docker-test-db-connection.ts @@ -1,5 +1,6 @@ import debugLog from "./logging/debug-log"; import mariaDBlocalQuery from "./mariadb-local-query"; +import sleep from "./sleep"; let checkDbRetries = 0; const MAX_CHECK_DB_RETRIES = 10; @@ -34,7 +35,7 @@ export default async function dockerTestDbConnection(params?: Params) { break; } else { checkDbRetries++; - await Bun.sleep(sleepTime); + await sleep(sleepTime); } } } diff --git a/package-shared/utils/mariadb-local-query.ts b/package-shared/utils/mariadb-local-query.ts index f9ca4d9..b11f319 100644 --- a/package-shared/utils/mariadb-local-query.ts +++ b/package-shared/utils/mariadb-local-query.ts @@ -1,6 +1,6 @@ -import grabDockerStackServicesNames from "@/package-shared/utils/backend/names/grab-docker-stack-services-names"; -import normalizeText from "@/package-shared/utils/normalize-text"; +import grabDockerStackServicesNames from "./backend/names/grab-docker-stack-services-names"; import execute from "./execute"; +import normalizeText from "./normalize-text"; export default function mariaDBlocalQuery(query: string | string[]) { const { dbServiceName, maxScaleServiceName } = diff --git a/package-shared/utils/sleep.ts b/package-shared/utils/sleep.ts new file mode 100644 index 0000000..04d0e91 --- /dev/null +++ b/package-shared/utils/sleep.ts @@ -0,0 +1,3 @@ +export default async function sleep(time: number) { + return new Promise((resolve) => setTimeout(resolve, time)); +} diff --git a/package-shared/utils/test-db-connection.ts b/package-shared/utils/test-db-connection.ts index 2e804a2..6afaaa0 100644 --- a/package-shared/utils/test-db-connection.ts +++ b/package-shared/utils/test-db-connection.ts @@ -1,4 +1,5 @@ import dbHandler from "../functions/backend/dbHandler"; +import sleep from "./sleep"; let testDbConnRetries = 0; const MAX_TEST_DB_CONN_RETRIES = 10; @@ -25,7 +26,7 @@ export default async function testDbConnection(params?: Params) { break; } - await Bun.sleep(params?.sleepTime || SLEEP_TIME); + await sleep(params?.sleepTime || SLEEP_TIME); if ( testDbConnRetries > @@ -35,7 +36,7 @@ export default async function testDbConnection(params?: Params) { process.exit(1); } } catch (error) { - await Bun.sleep(params?.sleepTime || SLEEP_TIME); + await sleep(params?.sleepTime || SLEEP_TIME); } } diff --git a/package.json b/package.json index 34c0519..dbf8d31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@moduletrace/datasquirel", - "version": "4.9.0", + "version": "4.9.1", "description": "Cloud-based SQL data management tool", "main": "dist/index.js", "bin": {