2018-04-21 02:59:30 -03:00
|
|
|
/*****************************************************************************\
|
|
|
|
| ( )_ _ |
|
2018-11-01 15:11:23 -03:00
|
|
|
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ _ _ _ |
|
|
|
|
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/ \ /`\ /'_`\ |
|
|
|
|
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| |*| |( (_) ) |
|
2018-04-21 02:59:30 -03:00
|
|
|
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
|
|
|
|
| | | ( )_) | |
|
|
|
|
| (_) \___/' |
|
|
|
|
| |
|
|
|
|
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
|
|
|
|
| Licensed under the AGPL-3.0. |
|
|
|
|
| |
|
|
|
|
| According to our dual licensing model, this program can be used either |
|
|
|
|
| under the terms of the GNU Affero General Public License, version 3, |
|
|
|
|
| or under a proprietary license. |
|
|
|
|
| |
|
|
|
|
| The texts of the GNU Affero General Public License with an additional |
|
|
|
|
| permission and of our proprietary license can be found at and |
|
|
|
|
| in the LICENSE file you have received along with this program. |
|
|
|
|
| |
|
|
|
|
| This program is distributed in the hope that it will be useful, |
|
2018-09-11 19:40:53 -03:00
|
|
|
| but WITHOUT ANY WARRANTY, without even the implied warranty of |
|
2018-04-21 02:59:30 -03:00
|
|
|
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
|
|
| GNU Affero General Public License for more details. |
|
|
|
|
| |
|
|
|
|
| "General Bots" is a registered trademark of Pragmatismo.io. |
|
|
|
|
| The licensing of the program under the AGPLv3 does not imply a |
|
|
|
|
| trademark license. Therefore any rights, title and interest in |
|
|
|
|
| our trademarks remain entirely with us. |
|
|
|
|
| |
|
|
|
|
\*****************************************************************************/
|
|
|
|
|
2018-10-14 19:58:54 -03:00
|
|
|
"use strict";
|
2018-04-21 02:59:30 -03:00
|
|
|
|
2018-10-14 19:58:54 -03:00
|
|
|
const logger = require("../../../src/logger");
|
|
|
|
import { Sequelize } from "sequelize-typescript";
|
2018-10-25 18:13:51 -03:00
|
|
|
import * as fs from "fs";
|
2018-10-14 19:58:54 -03:00
|
|
|
import { GBConfigService } from "./GBConfigService";
|
|
|
|
import { IGBInstance, IGBCoreService } from "botlib";
|
|
|
|
import { GuaribasInstance } from "../models/GBModel";
|
2018-09-16 17:00:17 -03:00
|
|
|
import { GBAdminService } from "../../admin.gbapp/services/GBAdminService";
|
2018-10-15 19:05:43 -03:00
|
|
|
const processExists = require("process-exists");
|
2018-10-22 15:33:23 -03:00
|
|
|
const TextDecoder = require("util").TextDecoder;
|
2018-04-21 02:59:30 -03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Core service layer.
|
|
|
|
*/
|
|
|
|
export class GBCoreService implements IGBCoreService {
|
2018-10-15 19:05:43 -03:00
|
|
|
isCloudSetup() {
|
|
|
|
return GBConfigService.tryGet("STORAGE_DIALECT");
|
|
|
|
}
|
2018-10-14 19:58:54 -03:00
|
|
|
|
2018-09-09 14:39:37 -03:00
|
|
|
/**
|
|
|
|
* Data access layer instance.
|
|
|
|
*/
|
2018-10-14 19:58:54 -03:00
|
|
|
public sequelize: Sequelize;
|
2018-04-21 02:59:30 -03:00
|
|
|
|
2018-09-16 17:00:17 -03:00
|
|
|
/**
|
|
|
|
* Administrative services.
|
|
|
|
*/
|
2018-10-14 19:58:54 -03:00
|
|
|
public adminService: GBAdminService;
|
2018-09-16 17:00:17 -03:00
|
|
|
|
2018-09-09 14:39:37 -03:00
|
|
|
/**
|
|
|
|
* Allows filtering on SQL generated before send to the database.
|
|
|
|
*/
|
2018-10-14 19:58:54 -03:00
|
|
|
private queryGenerator: any;
|
2018-09-09 14:39:37 -03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Custom create table query.
|
|
|
|
*/
|
2018-10-14 19:58:54 -03:00
|
|
|
private createTableQuery: (tableName, attributes, options) => string;
|
2018-06-04 08:03:23 -03:00
|
|
|
|
2018-09-09 14:39:37 -03:00
|
|
|
/**
|
|
|
|
* Custom change column query.
|
|
|
|
*/
|
2018-10-14 19:58:54 -03:00
|
|
|
private changeColumnQuery: (tableName, attributes) => string;
|
2018-04-21 02:59:30 -03:00
|
|
|
|
2018-09-11 19:33:58 -03:00
|
|
|
/**
|
|
|
|
* Dialect used. Tested: mssql and sqlite.
|
2018-09-09 14:39:37 -03:00
|
|
|
*/
|
2018-10-14 19:58:54 -03:00
|
|
|
private dialect: string;
|
2018-04-21 02:59:30 -03:00
|
|
|
|
2018-09-09 14:39:37 -03:00
|
|
|
/**
|
|
|
|
* Constructor retrieves default values.
|
|
|
|
*/
|
2018-04-21 02:59:30 -03:00
|
|
|
constructor() {
|
2018-10-14 19:58:54 -03:00
|
|
|
this.adminService = new GBAdminService(this);
|
2018-04-21 02:59:30 -03:00
|
|
|
}
|
|
|
|
|
2018-09-11 19:33:58 -03:00
|
|
|
/**
|
|
|
|
* Gets database config and connect to storage.
|
2018-09-09 14:39:37 -03:00
|
|
|
*/
|
|
|
|
async initDatabase() {
|
2018-09-11 19:33:58 -03:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
try {
|
2018-10-25 18:13:51 -03:00
|
|
|
this.dialect = GBConfigService.get("STORAGE_DIALECT");
|
|
|
|
|
2018-10-14 19:58:54 -03:00
|
|
|
let host: string | undefined;
|
|
|
|
let database: string | undefined;
|
|
|
|
let username: string | undefined;
|
|
|
|
let password: string | undefined;
|
|
|
|
let storage: string | undefined;
|
2018-04-21 02:59:30 -03:00
|
|
|
|
2018-09-11 19:33:58 -03:00
|
|
|
if (this.dialect === "mssql") {
|
2018-10-25 18:13:51 -03:00
|
|
|
host = GBConfigService.get("STORAGE_SERVER");
|
2018-10-14 19:58:54 -03:00
|
|
|
database = GBConfigService.get("STORAGE_NAME");
|
|
|
|
username = GBConfigService.get("STORAGE_USERNAME");
|
|
|
|
password = GBConfigService.get("STORAGE_PASSWORD");
|
2018-09-11 19:33:58 -03:00
|
|
|
} else if (this.dialect === "sqlite") {
|
2018-10-14 19:58:54 -03:00
|
|
|
storage = GBConfigService.get("STORAGE_STORAGE");
|
2018-10-25 18:13:51 -03:00
|
|
|
} else {
|
|
|
|
reject(`Unknown dialect: ${this.dialect}.`);
|
2018-09-11 19:33:58 -03:00
|
|
|
}
|
2018-05-28 06:51:06 -03:00
|
|
|
|
2018-09-11 19:33:58 -03:00
|
|
|
let logging =
|
2018-09-24 15:27:26 -03:00
|
|
|
GBConfigService.get("STORAGE_LOGGING") === "true"
|
2018-09-11 19:33:58 -03:00
|
|
|
? (str: string) => {
|
2018-10-14 19:58:54 -03:00
|
|
|
logger.info(str);
|
|
|
|
}
|
|
|
|
: false;
|
2018-05-28 06:51:06 -03:00
|
|
|
|
2018-10-14 19:58:54 -03:00
|
|
|
let encrypt = GBConfigService.get("STORAGE_ENCRYPT") === "true";
|
2018-06-04 08:03:23 -03:00
|
|
|
|
2018-09-11 19:33:58 -03:00
|
|
|
this.sequelize = new Sequelize({
|
|
|
|
host: host,
|
|
|
|
database: database,
|
|
|
|
username: username,
|
|
|
|
password: password,
|
|
|
|
logging: logging,
|
|
|
|
operatorsAliases: false,
|
|
|
|
dialect: this.dialect,
|
|
|
|
storage: storage,
|
|
|
|
dialectOptions: {
|
|
|
|
encrypt: encrypt
|
|
|
|
},
|
|
|
|
pool: {
|
|
|
|
max: 32,
|
|
|
|
min: 8,
|
|
|
|
idle: 40000,
|
|
|
|
evict: 40000,
|
|
|
|
acquire: 40000
|
2018-09-09 14:39:37 -03:00
|
|
|
}
|
2018-10-14 19:58:54 -03:00
|
|
|
});
|
2018-09-11 19:33:58 -03:00
|
|
|
|
|
|
|
if (this.dialect === "mssql") {
|
2018-10-14 19:58:54 -03:00
|
|
|
this.queryGenerator = this.sequelize.getQueryInterface().QueryGenerator;
|
|
|
|
this.createTableQuery = this.queryGenerator.createTableQuery;
|
2018-09-11 19:33:58 -03:00
|
|
|
this.queryGenerator.createTableQuery = (
|
|
|
|
tableName,
|
|
|
|
attributes,
|
|
|
|
options
|
2018-10-14 19:58:54 -03:00
|
|
|
) => this.createTableQueryOverride(tableName, attributes, options);
|
|
|
|
this.changeColumnQuery = this.queryGenerator.changeColumnQuery;
|
2018-09-11 19:33:58 -03:00
|
|
|
this.queryGenerator.changeColumnQuery = (tableName, attributes) =>
|
2018-10-14 19:58:54 -03:00
|
|
|
this.changeColumnQueryOverride(tableName, attributes);
|
2018-09-09 14:39:37 -03:00
|
|
|
}
|
2018-10-14 19:58:54 -03:00
|
|
|
resolve();
|
2018-09-11 19:33:58 -03:00
|
|
|
} catch (error) {
|
2018-10-14 19:58:54 -03:00
|
|
|
reject(error);
|
2018-09-11 19:33:58 -03:00
|
|
|
}
|
2018-10-14 19:58:54 -03:00
|
|
|
});
|
2018-06-04 08:03:23 -03:00
|
|
|
}
|
|
|
|
|
2018-09-11 19:33:58 -03:00
|
|
|
/**
|
|
|
|
* SQL:
|
2018-10-14 19:58:54 -03:00
|
|
|
*
|
2018-09-11 19:33:58 -03:00
|
|
|
* // let sql: string = '' +
|
|
|
|
* // 'IF OBJECT_ID(\'[UserGroup]\', \'U\') IS NULL\n' +
|
|
|
|
* // 'CREATE TABLE [UserGroup] (\n' +
|
|
|
|
* // ' [id] INTEGER NOT NULL IDENTITY(1,1),\n' +
|
|
|
|
* // ' [userId] INTEGER NULL,\n' +
|
|
|
|
* // ' [groupId] INTEGER NULL,\n' +
|
|
|
|
* // ' [instanceId] INTEGER NULL,\n' +
|
|
|
|
* // ' PRIMARY KEY ([id1], [id2]),\n' +
|
|
|
|
* // ' FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,\n' +
|
|
|
|
* // ' FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId1]) ON DELETE NO ACTION,\n' +
|
2018-09-11 19:40:53 -03:00
|
|
|
* // ' FOREIGN KEY ([instanceId]) REFERENCES [Instance] ([instanceId]) ON DELETE NO ACTION)'
|
2018-09-11 19:33:58 -03:00
|
|
|
*/
|
2018-06-04 08:03:23 -03:00
|
|
|
private createTableQueryOverride(tableName, attributes, options): string {
|
2018-09-11 19:33:58 -03:00
|
|
|
let sql: string = this.createTableQuery.apply(this.queryGenerator, [
|
|
|
|
tableName,
|
|
|
|
attributes,
|
|
|
|
options
|
2018-10-14 19:58:54 -03:00
|
|
|
]);
|
|
|
|
const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/;
|
|
|
|
const matches = re1.exec(sql);
|
2018-06-04 08:03:23 -03:00
|
|
|
if (matches) {
|
2018-10-14 19:58:54 -03:00
|
|
|
const table = matches[1];
|
|
|
|
const re2 = /PRIMARY\s+KEY\s+\(\[[^\]]*\](?:,\s*\[[^\]]*\])*\)/;
|
2018-09-11 19:33:58 -03:00
|
|
|
sql = sql.replace(
|
|
|
|
re2,
|
|
|
|
(match: string, ...args: any[]): string => {
|
2018-10-14 19:58:54 -03:00
|
|
|
return "CONSTRAINT [" + table + "_pk] " + match;
|
2018-09-11 19:33:58 -03:00
|
|
|
}
|
2018-10-14 19:58:54 -03:00
|
|
|
);
|
|
|
|
const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g;
|
|
|
|
const re4 = /\[([^\]]*)\]/g;
|
2018-09-11 19:33:58 -03:00
|
|
|
sql = sql.replace(
|
|
|
|
re3,
|
|
|
|
(match: string, ...args: any[]): string => {
|
2018-10-14 19:58:54 -03:00
|
|
|
const fkcols = args[0];
|
|
|
|
let fkname = table;
|
|
|
|
let matches = re4.exec(fkcols);
|
2018-09-11 19:33:58 -03:00
|
|
|
while (matches != null) {
|
2018-10-14 19:58:54 -03:00
|
|
|
fkname += "_" + matches[1];
|
|
|
|
matches = re4.exec(fkcols);
|
2018-09-11 19:33:58 -03:00
|
|
|
}
|
2018-10-14 19:58:54 -03:00
|
|
|
return "CONSTRAINT [" + fkname + "_fk] FOREIGN KEY (" + fkcols + ")";
|
2018-06-04 08:03:23 -03:00
|
|
|
}
|
2018-10-14 19:58:54 -03:00
|
|
|
);
|
2018-06-04 08:03:23 -03:00
|
|
|
}
|
2018-10-14 19:58:54 -03:00
|
|
|
return sql;
|
2018-06-04 08:03:23 -03:00
|
|
|
}
|
|
|
|
|
2018-09-11 19:33:58 -03:00
|
|
|
/**
|
|
|
|
* SQL:
|
|
|
|
* let sql = '' +
|
|
|
|
* 'ALTER TABLE [UserGroup]\n' +
|
|
|
|
* ' ADD CONSTRAINT [invalid1] FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,\n' +
|
|
|
|
* ' CONSTRAINT [invalid2] FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId2]) ON DELETE NO ACTION, \n' +
|
2018-09-11 19:40:53 -03:00
|
|
|
* ' CONSTRAINT [invalid3] FOREIGN KEY ([instanceId1]) REFERENCES [Instance] ([instanceId1]) ON DELETE NO ACTION\n'
|
2018-09-11 19:33:58 -03:00
|
|
|
*/
|
2018-06-04 08:03:23 -03:00
|
|
|
private changeColumnQueryOverride(tableName, attributes): string {
|
2018-09-11 19:33:58 -03:00
|
|
|
let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [
|
|
|
|
tableName,
|
|
|
|
attributes
|
2018-10-14 19:58:54 -03:00
|
|
|
]);
|
|
|
|
const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/;
|
|
|
|
const matches = re1.exec(sql);
|
2018-06-04 08:03:23 -03:00
|
|
|
if (matches) {
|
2018-10-14 19:58:54 -03:00
|
|
|
const table = matches[1];
|
|
|
|
const re2 = /(ADD\s+)?CONSTRAINT\s+\[([^\]]*)\]\s+FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g;
|
|
|
|
const re3 = /\[([^\]]*)\]/g;
|
2018-09-11 19:33:58 -03:00
|
|
|
sql = sql.replace(
|
|
|
|
re2,
|
|
|
|
(match: string, ...args: any[]): string => {
|
2018-10-14 19:58:54 -03:00
|
|
|
const fkcols = args[2];
|
|
|
|
let fkname = table;
|
|
|
|
let matches = re3.exec(fkcols);
|
2018-09-11 19:33:58 -03:00
|
|
|
while (matches != null) {
|
2018-10-14 19:58:54 -03:00
|
|
|
fkname += "_" + matches[1];
|
|
|
|
matches = re3.exec(fkcols);
|
2018-09-11 19:33:58 -03:00
|
|
|
}
|
|
|
|
return (
|
|
|
|
(args[0] ? args[0] : "") +
|
|
|
|
"CONSTRAINT [" +
|
|
|
|
fkname +
|
|
|
|
"_fk] FOREIGN KEY (" +
|
|
|
|
fkcols +
|
|
|
|
")"
|
2018-10-14 19:58:54 -03:00
|
|
|
);
|
2018-06-04 08:03:23 -03:00
|
|
|
}
|
2018-10-14 19:58:54 -03:00
|
|
|
);
|
2018-06-04 08:03:23 -03:00
|
|
|
}
|
2018-10-14 19:58:54 -03:00
|
|
|
return sql;
|
2018-04-21 02:59:30 -03:00
|
|
|
}
|
2018-05-18 11:39:17 -03:00
|
|
|
|
2018-09-09 14:39:37 -03:00
|
|
|
async syncDatabaseStructure() {
|
2018-09-24 15:27:26 -03:00
|
|
|
if (GBConfigService.get("STORAGE_SYNC") === "true") {
|
2018-10-14 19:58:54 -03:00
|
|
|
const alter = GBConfigService.get("STORAGE_SYNC_ALTER") === "true";
|
|
|
|
const force = GBConfigService.get("STORAGE_SYNC_FORCE") === "true";
|
|
|
|
logger.info("Syncing database...");
|
2018-09-16 17:00:17 -03:00
|
|
|
return this.sequelize.sync({
|
|
|
|
alter: alter,
|
|
|
|
force: force
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
let msg = "Database synchronization is disabled.";
|
2018-10-14 19:58:54 -03:00
|
|
|
logger.info(msg);
|
2018-09-16 17:00:17 -03:00
|
|
|
}
|
2018-04-21 02:59:30 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads all items to start several listeners.
|
|
|
|
*/
|
2018-09-09 14:39:37 -03:00
|
|
|
async loadInstances(): Promise<IGBInstance> {
|
2018-09-16 17:00:17 -03:00
|
|
|
return GuaribasInstance.findAll({});
|
2018-04-21 02:59:30 -03:00
|
|
|
}
|
|
|
|
|
2018-09-24 11:04:36 -03:00
|
|
|
/**
|
|
|
|
* Loads just one Bot instance by its internal Id.
|
|
|
|
*/
|
|
|
|
async loadInstanceById(instanceId: string): Promise<IGBInstance> {
|
2018-10-14 19:58:54 -03:00
|
|
|
let options = { where: { instanceId: instanceId } };
|
2018-09-24 11:04:36 -03:00
|
|
|
return GuaribasInstance.findOne(options);
|
|
|
|
}
|
|
|
|
|
2018-04-21 02:59:30 -03:00
|
|
|
/**
|
|
|
|
* Loads just one Bot instance.
|
|
|
|
*/
|
2018-09-09 14:39:37 -03:00
|
|
|
async loadInstance(botId: string): Promise<IGBInstance> {
|
2018-10-14 19:58:54 -03:00
|
|
|
let options = { where: {} };
|
2018-04-21 02:59:30 -03:00
|
|
|
|
2018-09-16 17:00:17 -03:00
|
|
|
if (botId != "[default]") {
|
2018-10-14 19:58:54 -03:00
|
|
|
options.where = { botId: botId };
|
2018-09-16 17:00:17 -03:00
|
|
|
}
|
2018-04-21 02:59:30 -03:00
|
|
|
|
2018-09-16 17:00:17 -03:00
|
|
|
return GuaribasInstance.findOne(options);
|
2018-04-21 02:59:30 -03:00
|
|
|
}
|
2018-10-15 19:05:43 -03:00
|
|
|
|
2018-10-25 18:13:51 -03:00
|
|
|
public async writeEnv(instance: IGBInstance) {
|
|
|
|
let env =
|
|
|
|
`ADDITIONAL_DEPLOY_PATH=\n` +
|
2018-11-01 15:11:23 -03:00
|
|
|
`ADMIN_PASS=${instance.adminPass}\n` +
|
|
|
|
`CLOUD_SUBSCRIPTIONID=${instance.cloudSubscriptionId}\n` +
|
|
|
|
`CLOUD_LOCATION=${instance.cloudLocation}\n` +
|
|
|
|
`CLOUD_GROUP=${instance.botId}\n` +
|
|
|
|
`CLOUD_USERNAME=${instance.cloudUsername}\n` +
|
|
|
|
`CLOUD_PASSWORD=${instance.cloudPassword}\n` +
|
2018-11-04 17:26:29 -02:00
|
|
|
`MARKETPLACE_ID=${instance.marketplaceId}\n`+
|
|
|
|
`MARKETPLACE_SECRET=${instance.marketplacePassword}\n`+
|
2018-11-01 15:11:23 -03:00
|
|
|
`NLP_AUTHORING_KEY=${instance.nlpAuthoringKey}\n`+
|
2018-10-25 18:13:51 -03:00
|
|
|
`STORAGE_DIALECT=${instance.storageDialect}\n` +
|
|
|
|
`STORAGE_SERVER=${instance.storageServer}.database.windows.net\n` +
|
|
|
|
`STORAGE_NAME=${instance.storageName}\n` +
|
|
|
|
`STORAGE_USERNAME=${instance.storageUsername}\n` +
|
2018-10-28 21:56:51 -03:00
|
|
|
`STORAGE_PASSWORD=${instance.storagePassword}\n` +
|
2018-11-01 15:11:23 -03:00
|
|
|
`STORAGE_SYNC=true\n`;
|
2018-10-25 18:13:51 -03:00
|
|
|
|
|
|
|
fs.writeFileSync(".env", env);
|
|
|
|
}
|
|
|
|
|
2018-10-28 21:56:51 -03:00
|
|
|
public async ensureProxy(port): Promise<string> {
|
2018-10-22 15:33:23 -03:00
|
|
|
let proxyAddress: string;
|
2018-10-25 18:13:51 -03:00
|
|
|
const ngrok = require("ngrok");
|
2018-10-28 21:56:51 -03:00
|
|
|
return await ngrok.connect({port:port});
|
2018-10-15 19:05:43 -03:00
|
|
|
}
|
2018-04-21 02:59:30 -03:00
|
|
|
}
|