UNPKG

@topgroup/diginext

Version:

A BUILD SERVER & CLI to deploy apps to any Kubernetes clusters.

391 lines (390 loc) 19.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CloudDatabaseService = void 0; const makeDaySlug_1 = require("diginext-utils/dist/string/makeDaySlug"); const log_1 = require("diginext-utils/dist/xconsole/log"); const fs_1 = require("fs"); const lodash_1 = require("lodash"); const path_1 = __importDefault(require("path")); const app_config_1 = require("../app.config"); const CloudDatabase_1 = require("../entities/CloudDatabase"); const Cronjob_1 = require("../entities/Cronjob"); const interfaces_1 = require("../interfaces"); const SystemTypes_1 = require("../interfaces/SystemTypes"); const schedule_1 = require("../modules/cronjob/schedule"); const mysql_1 = __importDefault(require("../modules/db/mysql")); const pg_1 = __importDefault(require("../modules/db/pg")); const mongodb_1 = require("../plugins/mongodb"); const user_utils_1 = require("../plugins/user-utils"); const BaseService_1 = __importDefault(require("./BaseService")); const CloudDatabaseBackupService_1 = require("./CloudDatabaseBackupService"); class CloudDatabaseService extends BaseService_1.default { constructor(ownership) { super(CloudDatabase_1.cloudDatabaseSchema, ownership); } async create(data) { // validate if (!data.type) throw new Error(`Database type is required, should be one of: ${SystemTypes_1.cloudDatabaseList.join(",")}.`); if (data.type !== "mongodb" && data.type !== "mariadb" && data.type !== "mysql" && data.type !== "postgresql") throw new Error(`Database type "" is not supported at the moment.`); if (data.type !== "mongodb" && data.type !== "postgresql" && !data.url) { if (!data.host) throw new Error(`Database host is required.`); if (!data.pass) throw new Error(`Password is required.`); } // console.log("data :>> ", data); // test connection let verified = false; try { switch (data.type) { case "mariadb": case "mysql": verified = await mysql_1.default.checkConnection({ host: data.host, port: (0, lodash_1.toString)(data.port), user: data.user, pass: data.pass }); break; case "mongodb": const MongoShell = await Promise.resolve().then(() => __importStar(require("../modules/db/mongo"))); verified = await MongoShell.checkConnection({ url: data.url, host: data.host, port: (0, lodash_1.toString)(data.port), user: data.user, pass: data.pass, }); break; case "postgresql": verified = await pg_1.default.checkConnection({ host: data.host, port: (0, lodash_1.toString)(data.port), user: data.user, pass: data.pass }); break; default: console.warn(`Database type "${data.type}" is not supported at the moment.`); break; } } catch (e) { console.warn(e); } data.verified = verified; // insert const newDb = await super.create(data); if (!newDb) throw new Error(`Internal Server Error: Unable to create database: "${data.name}"`); return newDb; } async find(filter, options, pagination) { var _a, _b, _c, _d, _e; // check access permissions if (((_c = (_b = (_a = this.user) === null || _a === void 0 ? void 0 : _a.allowAccess) === null || _b === void 0 ? void 0 : _b.databases) === null || _c === void 0 ? void 0 : _c.length) > 0) filter = { $or: [filter, { _id: { $in: (_e = (_d = this.user) === null || _d === void 0 ? void 0 : _d.allowAccess) === null || _e === void 0 ? void 0 : _e.databases } }] }; return super.find(filter, options, pagination); } async update(filter, data, options) { // check permissions await (0, user_utils_1.checkPermissionsByFilter)("cloud_databases", this, filter, this.user); return super.update(filter, data, options); } async updateOne(filter, data, options) { // check permissions await (0, user_utils_1.checkPermissionsByFilter)("cloud_databases", this, filter, this.user); return super.updateOne(filter, data, options); } async delete(filter, options) { // check permissions await (0, user_utils_1.checkPermissionsByFilter)("cloud_databases", this, filter, this.user); return super.delete(filter, options); } async softDelete(filter, options) { // check permissions await (0, user_utils_1.checkPermissionsByFilter)("cloud_databases", this, filter, this.user); return super.softDelete(filter, options); } // healthz async checkHealthById(id) { const { DB } = await Promise.resolve().then(() => __importStar(require("../modules/api/DB"))); const db = await DB.findOne("database", { _id: id }); if (!db) throw new Error(`Cloud database not found.`); return this.checkHealth(db); } async checkHealth(db) { // validate if (!db.type) throw new Error(`Database type is required, should be one of: ${SystemTypes_1.cloudDatabaseList.join(",")}.`); if (db.type !== "mongodb" && db.type !== "mariadb" && db.type !== "mysql" && db.type !== "postgresql") throw new Error(`Database type "" is not supported at the moment.`); if (db.type !== "mongodb" && db.type !== "postgresql" && !db.url) { if (!db.host) throw new Error(`Database host is required.`); if (!db.pass) throw new Error(`Password is required.`); } // test connection let verified = false; try { switch (db.type) { case "mariadb": case "mysql": verified = await mysql_1.default.checkConnection({ host: db.host, port: (0, lodash_1.toString)(db.port), user: db.user, pass: db.pass }); break; case "mongodb": const MongoShell = await Promise.resolve().then(() => __importStar(require("../modules/db/mongo"))); verified = await MongoShell.checkConnection({ host: db.host, port: (0, lodash_1.toString)(db.port), user: db.user, pass: db.pass }); break; case "postgresql": verified = await pg_1.default.checkConnection({ host: db.host, port: (0, lodash_1.toString)(db.port), user: db.user, pass: db.pass }); break; default: console.warn(`Database type "${db.type}" is not supported at the moment.`); break; } } catch (e) { console.warn(e); } return verified; } async backupById(id) { const { DB } = await Promise.resolve().then(() => __importStar(require("../modules/api/DB"))); const db = await DB.findOne("database", { _id: id }); if (!db) throw new Error(`Cloud database not found.`); return this.backup(db); } async backup(db, options) { var _a, _b; // validate if (!db.type) throw new Error(`Database type is required, should be one of: ${SystemTypes_1.cloudDatabaseList.join(",")}.`); if (db.type !== "mongodb" && db.type !== "mariadb" && db.type !== "mysql" && db.type !== "postgresql") throw new Error(`Database type "${db.type}" is not supported at the moment.`); if (db.type !== "mongodb" && db.type !== "postgresql" && !db.url) { if (!db.host) throw new Error(`Database host is required.`); if (!db.pass) throw new Error(`Password is required.`); } // backup try { const bkSvc = new CloudDatabaseBackupService_1.CloudDatabaseBackupService(this.ownership); // check if this process has been done by other pods, if yes, ignore this. const bkName = `${db.type}-backup-${(0, makeDaySlug_1.makeDaySlug)()}`; let backup = await bkSvc.findOne({ name: bkName }); if (backup) return; // create backup in db backup = await bkSvc.create({ database: mongodb_1.MongoDB.toString(db._id), status: "start", name: bkName, type: db.type, dbSlug: db.slug, // ownerships workspace: (_a = this.req.workspace) === null || _a === void 0 ? void 0 : _a._id, owner: (_b = this.req.user) === null || _b === void 0 ? void 0 : _b._id, }); console.log("[DB_BACKUP] backup :>> ", backup); console.log(`[DB_BACKUP] Start backing up > ${db.slug} :>> `, bkName); switch (db.type) { case "mariadb": case "mysql": mysql_1.default.backup({ backupId: backup._id.toString(), dbName: options === null || options === void 0 ? void 0 : options.dbName, host: db.host, port: (0, lodash_1.toString)(db.port), user: db.user, pass: db.pass, }) .then((res) => bkSvc.updateStatus(res.backupId, { status: "success", path: res.path })) .catch((e) => { (0, log_1.logError)(e.stack); if (backup) bkSvc.updateStatus(backup._id, { status: "failed" }); }); break; case "mongodb": const MongoShell = await Promise.resolve().then(() => __importStar(require("../modules/db/mongo"))); MongoShell.backup({ dbName: options === null || options === void 0 ? void 0 : options.dbName, authDb: (options === null || options === void 0 ? void 0 : options.authDb) || db.authDb, url: db.url, host: db.host, port: (0, lodash_1.toString)(db.port), user: db.user, pass: db.pass, }) .then((res) => bkSvc.updateStatus(backup._id, { status: "success", path: res.path })) .catch((e) => { (0, log_1.logError)(e.stack); if (backup) bkSvc.updateStatus(backup._id, { status: "failed" }); }); break; case "postgresql": pg_1.default.checkConnection({ dbName: options === null || options === void 0 ? void 0 : options.dbName, authDb: (options === null || options === void 0 ? void 0 : options.authDb) || db.authDb, url: db.url, host: db.host, port: (0, lodash_1.toString)(db.port), user: db.user, pass: db.pass, }); pg_1.default.backup({ dbName: options === null || options === void 0 ? void 0 : options.dbName, authDb: (options === null || options === void 0 ? void 0 : options.authDb) || db.authDb, url: db.url, host: db.host, port: (0, lodash_1.toString)(db.port), user: db.user, pass: db.pass, }) .then((res) => bkSvc.updateStatus(backup._id, { status: "success", path: res.path })) .catch((e) => { (0, log_1.logError)(e.stack); if (backup) bkSvc.updateStatus(backup._id, { status: "failed" }); }); break; default: throw new Error(`Database type "${db.type}" is not supported backing up at the moment.`); } return backup; } catch (e) { (0, log_1.logError)(`[DB_BACKUP_ERRROR]`, e.stack); return; } } async restoreFromBackupId(backupId, dbId) { const bkSvc = new CloudDatabaseBackupService_1.CloudDatabaseBackupService(this.ownership); const backup = await bkSvc.findOne({ _id: backupId }); const db = await this.findOne({ _id: dbId }); return this.restoreFromBackup(backup, db); } async restoreFromBackup(backup, db) { if (!backup.path) throw new Error(`Backup path is required.`); if (backup.path && !(0, fs_1.existsSync)(path_1.default.resolve(backup.path)) && backup.url) { // download the backup url to server... } else { throw new Error(`Backup path is not existed.`); } const restoreParams = { path: backup.path }; return this.restore({}, db); } async restoreById(options, id) { const { DB } = await Promise.resolve().then(() => __importStar(require("../modules/api/DB"))); const db = await DB.findOne("database", { _id: id }); if (!db) throw new Error(`Cloud database not found.`); return this.restore(options, db); } async restore(options, toDatabase) { // validate destination if (!toDatabase.type) throw new Error(`Destination database type is required, should be one of: ${SystemTypes_1.cloudDatabaseList.join(",")}.`); if (toDatabase.type !== "mongodb" && toDatabase.type !== "mariadb" && toDatabase.type !== "mysql" && toDatabase.type !== "postgresql") throw new Error(`Destination database type "${toDatabase.type}" is not supported at the moment.`); if (!toDatabase.host) throw new Error(`Destination database host is required.`); if (!toDatabase.pass) throw new Error(`Destination password is required.`); // validate backup if (!options.path) throw new Error(`Backup path is required.`); // backup let res; switch (toDatabase.type) { case "mariadb": case "mysql": res = await mysql_1.default.backup({ dbName: options.dbName, host: toDatabase.host, port: (0, lodash_1.toString)(toDatabase.port), user: toDatabase.user, pass: toDatabase.pass, }); break; case "mongodb": const MongoShell = await Promise.resolve().then(() => __importStar(require("../modules/db/mongo"))); res = await MongoShell.backup({ dbName: options.dbName, url: toDatabase.url, host: toDatabase.host, port: (0, lodash_1.toString)(toDatabase.port), user: toDatabase.user, pass: toDatabase.pass, }); break; case "postgresql": res = await pg_1.default.backup({ dbName: options.dbName, url: toDatabase.url, host: toDatabase.host, port: (0, lodash_1.toString)(toDatabase.port), user: toDatabase.user, pass: toDatabase.pass, }); break; default: return (0, interfaces_1.respondFailure)(`Database type "${toDatabase.type}" is not supported backing up at the moment.`); } return res; } async scheduleAutoBackup(id, repeat, condition, ownership) { const { DB } = await Promise.resolve().then(() => __importStar(require("../modules/api/DB"))); // validate if (typeof (repeat === null || repeat === void 0 ? void 0 : repeat.range) === "undefined") throw new Error(`Recurrent range is required.`); if (typeof (repeat === null || repeat === void 0 ? void 0 : repeat.unit) === "undefined") throw new Error(`Recurrent unit is required, one of: ${Cronjob_1.cronjobRepeatUnitList.join(", ")}.`); const db = await this.findOne({ _id: id }); if (!db) throw new Error(`Database not found.`); const apiKeyAccount = await DB.findOne("api_key_user", { workspaces: db.workspace }); // create new cronjob const request = { url: `${app_config_1.Config.BASE_URL}/api/v1/database/backup`, method: "POST", params: { id: mongodb_1.MongoDB.toString(db._id) }, headers: { "X-API-Key": apiKeyAccount.token.access_token, }, }; const cronjob = await (0, schedule_1.createCronjobRepeat)(`[SYSTEM] Backup database "${db.name}"`, request, repeat, condition, ownership); if (!cronjob) throw new Error(`Unable to schedule auto-backup for "${db.name}" database.`); // update cronjob ID to database: const updatedDb = await DB.updateOne("database", { _id: id }, { autoBackup: cronjob._id, owner: ownership === null || ownership === void 0 ? void 0 : ownership.owner, workspace: ownership === null || ownership === void 0 ? void 0 : ownership.workspace }); return updatedDb; } } exports.CloudDatabaseService = CloudDatabaseService;