@topgroup/diginext
Version:
A BUILD SERVER & CLI to deploy apps to any Kubernetes clusters.
391 lines (390 loc) • 19.1 kB
JavaScript
;
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;