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. |
|
2018-11-11 19:09:18 -02:00
|
|
|
| |
|
2018-04-21 02:59:30 -03:00
|
|
|
| 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-11-11 17:31:17 -02:00
|
|
|
/**
|
|
|
|
* @fileoverview General Bots server core.
|
|
|
|
*/
|
|
|
|
|
2018-11-11 19:09:18 -02:00
|
|
|
'use strict';
|
2018-04-21 02:59:30 -03:00
|
|
|
|
2021-09-30 16:03:31 -03:00
|
|
|
import { GBLog, IGBCoreService, IGBInstallationDeployer, IGBInstance, IGBPackage } from 'botlib';
|
2022-11-18 22:39:14 -03:00
|
|
|
import * as Fs from 'fs';
|
2020-01-08 17:52:46 -03:00
|
|
|
import { Sequelize, SequelizeOptions } from 'sequelize-typescript';
|
2019-12-31 16:12:06 -03:00
|
|
|
import { Op, Dialect } from 'sequelize';
|
2022-11-18 22:39:14 -03:00
|
|
|
import { GBServer } from '../../../src/app.js';
|
|
|
|
import { GBAdminPackage } from '../../admin.gbapp/index.js';
|
|
|
|
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService.js';
|
|
|
|
import { GBAnalyticsPackage } from '../../analytics.gblib/index.js';
|
|
|
|
import { StartDialog } from '../../azuredeployer.gbapp/dialogs/StartDialog.js';
|
|
|
|
import { GBCorePackage } from '../../core.gbapp/index.js';
|
|
|
|
import { GBCustomerSatisfactionPackage } from '../../customer-satisfaction.gbapp/index.js';
|
|
|
|
import { GBKBPackage } from '../../kb.gbapp/index.js';
|
|
|
|
import { GBSecurityPackage } from '../../security.gbapp/index.js';
|
|
|
|
import { GBWhatsappPackage } from '../../whatsapp.gblib/index.js';
|
2023-02-12 14:31:21 -03:00
|
|
|
import { GuaribasInstance, GuaribasLog} from '../models/GBModel.js';
|
2022-11-18 22:39:14 -03:00
|
|
|
import { GBConfigService } from './GBConfigService.js';
|
|
|
|
import { GBAzureDeployerPackage } from '../../azuredeployer.gbapp/index.js';
|
|
|
|
import { GBSharePointPackage } from '../../sharepoint.gblib/index.js';
|
2020-04-13 19:14:55 -03:00
|
|
|
import { CollectionUtil } from 'pragmatismo-io-framework';
|
2022-11-18 22:39:14 -03:00
|
|
|
import { GBBasicPackage } from '../../basic.gblib/index.js';
|
|
|
|
import { GBGoogleChatPackage } from '../../google-chat.gblib/index.js';
|
|
|
|
import { GBHubSpotPackage } from '../../hubspot.gblib/index.js';
|
2022-11-19 19:50:19 -03:00
|
|
|
import open from 'open';
|
2022-11-18 22:39:14 -03:00
|
|
|
import ngrok from 'ngrok';
|
2018-04-21 02:59:30 -03:00
|
|
|
|
|
|
|
/**
|
2020-12-25 13:36:26 -03:00
|
|
|
* GBCoreService contains main logic for handling storage services related
|
|
|
|
* to instance handling. When the server starts a instance is needed and
|
|
|
|
* if no instance is found a boot instance is created. After that high-level
|
|
|
|
* instance management methods can be created.
|
|
|
|
* Core scheduling, base network services are also handled in this service.
|
2018-04-21 02:59:30 -03:00
|
|
|
*/
|
|
|
|
export class GBCoreService implements IGBCoreService {
|
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-11-27 22:56:11 -02:00
|
|
|
private createTableQuery: (tableName: string, attributes: any, options: any) => string;
|
2018-06-04 08:03:23 -03:00
|
|
|
|
2018-09-09 14:39:37 -03:00
|
|
|
/**
|
|
|
|
* Custom change column query.
|
|
|
|
*/
|
2018-11-11 19:09:18 -02:00
|
|
|
private changeColumnQuery: (tableName: string, attributes: any) => 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
|
|
|
/**
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
2018-09-09 14:39:37 -03:00
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
constructor() {
|
2018-10-14 19:58:54 -03:00
|
|
|
this.adminService = new GBAdminService(this);
|
2018-04-21 02:59:30 -03:00
|
|
|
}
|
2023-02-12 14:31:21 -03:00
|
|
|
public async ensureInstances(instances: IGBInstance[], bootInstance: any, core: IGBCoreService) {}
|
2021-04-17 17:20:44 -03:00
|
|
|
|
2018-09-11 19:33:58 -03:00
|
|
|
/**
|
2020-12-25 13:36:26 -03:00
|
|
|
* Gets database config and connect to storage. Currently two databases
|
|
|
|
* are available: SQL Server and SQLite.
|
2018-09-09 14:39:37 -03:00
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public async initStorage(): Promise<any> {
|
2018-11-27 22:56:11 -02:00
|
|
|
this.dialect = GBConfigService.get('STORAGE_DIALECT');
|
|
|
|
|
|
|
|
let host: string | undefined;
|
|
|
|
let database: string | undefined;
|
|
|
|
let username: string | undefined;
|
|
|
|
let password: string | undefined;
|
|
|
|
let storage: string | undefined;
|
|
|
|
|
|
|
|
if (this.dialect === 'mssql') {
|
|
|
|
host = GBConfigService.get('STORAGE_SERVER');
|
|
|
|
database = GBConfigService.get('STORAGE_NAME');
|
|
|
|
username = GBConfigService.get('STORAGE_USERNAME');
|
|
|
|
password = GBConfigService.get('STORAGE_PASSWORD');
|
|
|
|
} else if (this.dialect === 'sqlite') {
|
|
|
|
storage = GBConfigService.get('STORAGE_STORAGE');
|
|
|
|
} else {
|
|
|
|
throw new Error(`Unknown dialect: ${this.dialect}.`);
|
|
|
|
}
|
|
|
|
|
2019-03-08 17:05:58 -03:00
|
|
|
const logging: boolean | Function =
|
2018-11-27 22:56:11 -02:00
|
|
|
GBConfigService.get('STORAGE_LOGGING') === 'true'
|
|
|
|
? (str: string): void => {
|
2022-11-19 23:34:58 -03:00
|
|
|
GBLog.info(str);
|
|
|
|
}
|
2018-11-27 22:56:11 -02:00
|
|
|
: false;
|
|
|
|
|
|
|
|
const encrypt: boolean = GBConfigService.get('STORAGE_ENCRYPT') === 'true';
|
|
|
|
|
2019-11-10 16:20:15 -03:00
|
|
|
const acquire = parseInt(GBConfigService.get('STORAGE_ACQUIRE_TIMEOUT'));
|
2020-01-08 17:52:46 -03:00
|
|
|
const sequelizeOptions: SequelizeOptions = {
|
|
|
|
define: {
|
|
|
|
freezeTableName: true,
|
|
|
|
timestamps: false
|
|
|
|
},
|
2018-11-27 22:56:11 -02:00
|
|
|
host: host,
|
2019-12-31 16:12:06 -03:00
|
|
|
logging: logging as boolean,
|
|
|
|
dialect: this.dialect as Dialect,
|
2018-11-27 22:56:11 -02:00
|
|
|
storage: storage,
|
|
|
|
dialectOptions: {
|
2019-04-07 12:23:27 -03:00
|
|
|
options: {
|
2020-10-17 17:34:16 -03:00
|
|
|
trustServerCertificate: true,
|
2019-04-07 12:23:27 -03:00
|
|
|
encrypt: encrypt
|
|
|
|
}
|
2020-08-22 18:41:54 -03:00
|
|
|
},
|
|
|
|
pool: {
|
2018-11-27 22:56:11 -02:00
|
|
|
max: 32,
|
|
|
|
min: 8,
|
|
|
|
idle: 40000,
|
|
|
|
evict: 40000,
|
2019-11-10 16:20:15 -03:00
|
|
|
acquire: acquire
|
2018-11-27 22:56:11 -02:00
|
|
|
}
|
2019-12-31 16:12:06 -03:00
|
|
|
};
|
|
|
|
|
2020-08-22 18:41:54 -03:00
|
|
|
this.sequelize = new Sequelize(database, username, password, sequelizeOptions);
|
2018-11-27 22:56:11 -02:00
|
|
|
|
2020-12-27 13:30:56 -03:00
|
|
|
// Specifies custom setup for MSFT...
|
2021-04-17 17:20:44 -03:00
|
|
|
|
2018-11-27 22:56:11 -02:00
|
|
|
if (this.dialect === 'mssql') {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-25 13:36:26 -03:00
|
|
|
/**
|
|
|
|
* Checks wheather storage is acessible or not and opens firewall
|
|
|
|
* in case of any connection block.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public async checkStorage(installationDeployer: IGBInstallationDeployer) {
|
2018-11-27 22:56:11 -02:00
|
|
|
try {
|
|
|
|
await this.sequelize.authenticate();
|
|
|
|
} catch (error) {
|
2019-03-08 17:05:58 -03:00
|
|
|
GBLog.info('Opening storage firewall on infrastructure...');
|
|
|
|
// tslint:disable:no-unsafe-any
|
2018-11-27 22:56:11 -02:00
|
|
|
if (error.parent.code === 'ELOGIN') {
|
2019-03-08 06:37:13 -03:00
|
|
|
await this.openStorageFrontier(installationDeployer);
|
2018-11-27 22:56:11 -02:00
|
|
|
} else {
|
|
|
|
throw error;
|
|
|
|
}
|
2019-03-08 17:05:58 -03:00
|
|
|
// tslint:ensable:no-unsafe-any
|
2018-11-27 22:56:11 -02:00
|
|
|
}
|
2018-11-11 19:09:18 -02:00
|
|
|
}
|
|
|
|
|
2022-11-19 23:34:58 -03:00
|
|
|
/**
|
|
|
|
* Syncronizes structure between model and tables in storage.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public async syncDatabaseStructure() {
|
2018-11-11 19:09:18 -02:00
|
|
|
if (GBConfigService.get('STORAGE_SYNC') === 'true') {
|
|
|
|
const alter = GBConfigService.get('STORAGE_SYNC_ALTER') === 'true';
|
2019-03-08 17:05:58 -03:00
|
|
|
GBLog.info('Syncing database...');
|
2018-11-27 22:56:11 -02:00
|
|
|
|
2021-04-17 17:20:44 -03:00
|
|
|
return await this.sequelize.sync({
|
2018-11-11 19:09:18 -02:00
|
|
|
alter: alter,
|
2022-01-03 13:11:21 -03:00
|
|
|
force: false // Keep it false due to data loss danger.
|
2018-11-11 19:09:18 -02:00
|
|
|
});
|
|
|
|
} else {
|
2018-11-28 17:08:06 -02:00
|
|
|
const msg = `Database synchronization is disabled.`;
|
2019-03-08 17:05:58 -03:00
|
|
|
GBLog.info(msg);
|
2018-11-11 19:09:18 -02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads all items to start several listeners.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public async getLatestLogs(instanceId: number): Promise<string> {
|
|
|
|
const options = {
|
|
|
|
where: {
|
|
|
|
instanceId: instanceId,
|
|
|
|
state: 'active',
|
|
|
|
created: {
|
|
|
|
[Op.gt]: new Date(Date.now() - 60 * 60 * 1000 * 48) // Latest 48 hours.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
const list = await GuaribasLog.findAll(options);
|
|
|
|
let out = 'General Bots Log\n';
|
|
|
|
await CollectionUtil.asyncForEach(list, async e => {
|
|
|
|
out = `${out}\n${e.createdAt} - ${e.message}`;
|
|
|
|
});
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads all items to start several listeners.
|
|
|
|
*/
|
|
|
|
public async loadInstances(): Promise<IGBInstance[]> {
|
2020-05-23 17:59:01 -03:00
|
|
|
if (process.env.LOAD_ONLY !== undefined) {
|
2020-06-04 13:44:02 -03:00
|
|
|
const bots = process.env.LOAD_ONLY.split(`;`);
|
|
|
|
const and = [];
|
|
|
|
await CollectionUtil.asyncForEach(bots, async e => {
|
2020-07-26 16:46:37 -03:00
|
|
|
and.push({ botId: e });
|
2020-06-04 13:44:02 -03:00
|
|
|
});
|
2020-07-26 16:46:37 -03:00
|
|
|
|
|
|
|
const options = {
|
|
|
|
where: {
|
|
|
|
[Op.or]: and
|
|
|
|
}
|
|
|
|
};
|
2020-11-09 19:04:01 -03:00
|
|
|
return await GuaribasInstance.findAll(options);
|
2020-08-22 18:41:54 -03:00
|
|
|
} else {
|
2020-05-24 17:06:05 -03:00
|
|
|
const options = { where: { state: 'active' } };
|
2020-11-09 19:04:01 -03:00
|
|
|
return await GuaribasInstance.findAll(options);
|
2020-04-30 09:14:32 -03:00
|
|
|
}
|
2018-11-11 19:09:18 -02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads just one Bot instance by its internal Id.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public async loadInstanceById(instanceId: number): Promise<IGBInstance> {
|
2020-05-24 17:06:05 -03:00
|
|
|
const options = { where: { instanceId: instanceId, state: 'active' } };
|
2018-11-27 22:56:11 -02:00
|
|
|
|
2020-11-09 19:04:01 -03:00
|
|
|
return await GuaribasInstance.findOne(options);
|
2018-11-11 19:09:18 -02:00
|
|
|
}
|
2020-05-24 17:06:05 -03:00
|
|
|
/**
|
|
|
|
* Loads just one Bot instance.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public async loadInstanceByActivationCode(code: string): Promise<IGBInstance> {
|
2020-05-24 17:06:05 -03:00
|
|
|
let options = { where: { activationCode: code, state: 'active' } };
|
|
|
|
|
|
|
|
return await GuaribasInstance.findOne(options);
|
|
|
|
}
|
2018-11-11 19:09:18 -02:00
|
|
|
/**
|
|
|
|
* Loads just one Bot instance.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public async loadInstanceByBotId(botId: string): Promise<IGBInstance> {
|
2018-11-11 19:09:18 -02:00
|
|
|
const options = { where: {} };
|
2020-05-24 17:06:05 -03:00
|
|
|
options.where = { botId: botId, state: 'active' };
|
2018-11-11 19:09:18 -02:00
|
|
|
|
2018-11-27 22:56:11 -02:00
|
|
|
return await GuaribasInstance.findOne(options);
|
2018-11-11 19:09:18 -02:00
|
|
|
}
|
|
|
|
|
2020-12-25 13:36:26 -03:00
|
|
|
/**
|
2022-11-19 23:34:58 -03:00
|
|
|
* Writes .env required to start the full server. Used during
|
|
|
|
* first startup, when user is asked some questions to create the
|
2020-12-25 13:36:26 -03:00
|
|
|
* full base environment.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public async writeEnv(instance: IGBInstance) {
|
2021-04-17 17:20:44 -03:00
|
|
|
const env = `
|
|
|
|
ADDITIONAL_DEPLOY_PATH=
|
2018-12-18 13:50:35 -02:00
|
|
|
ADMIN_PASS=${instance.adminPass}
|
2019-05-27 12:16:30 -03:00
|
|
|
BOT_ID=${instance.botId}
|
2018-12-18 13:50:35 -02:00
|
|
|
CLOUD_SUBSCRIPTIONID=${instance.cloudSubscriptionId}
|
|
|
|
CLOUD_LOCATION=${instance.cloudLocation}
|
|
|
|
CLOUD_GROUP=${instance.botId}
|
|
|
|
CLOUD_USERNAME=${instance.cloudUsername}
|
|
|
|
CLOUD_PASSWORD=${instance.cloudPassword}
|
|
|
|
MARKETPLACE_ID=${instance.marketplaceId}
|
|
|
|
MARKETPLACE_SECRET=${instance.marketplacePassword}
|
|
|
|
STORAGE_DIALECT=${instance.storageDialect}
|
2021-04-14 16:01:53 -03:00
|
|
|
STORAGE_SERVER=${instance.storageServer}
|
2018-12-18 13:50:35 -02:00
|
|
|
STORAGE_NAME=${instance.storageName}
|
|
|
|
STORAGE_USERNAME=${instance.storageUsername}
|
|
|
|
STORAGE_PASSWORD=${instance.storagePassword}
|
|
|
|
STORAGE_SYNC=true
|
2021-11-29 15:15:55 -03:00
|
|
|
STORAGE_SYNC_ALTER=true
|
2021-04-17 17:20:44 -03:00
|
|
|
ENDPOINT_UPDATE=true
|
2018-12-18 13:50:35 -02:00
|
|
|
`;
|
2018-11-11 19:09:18 -02:00
|
|
|
|
2022-11-18 22:39:14 -03:00
|
|
|
Fs.writeFileSync('.env', env);
|
2018-11-11 19:09:18 -02:00
|
|
|
}
|
|
|
|
|
2022-11-19 23:34:58 -03:00
|
|
|
/**
|
2020-12-25 13:36:26 -03:00
|
|
|
* Certifies that network servers will reach back the development machine
|
|
|
|
* when calling back from web services. This ensures that reverse proxy is
|
|
|
|
* established.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public async ensureProxy(port): Promise<string> {
|
2018-12-01 14:38:08 -02:00
|
|
|
try {
|
2022-11-18 22:39:14 -03:00
|
|
|
if (Fs.existsSync('node_modules/ngrok/bin/ngrok.exe') || Fs.existsSync('node_modules/ngrok/bin/ngrok')) {
|
2022-11-19 23:34:58 -03:00
|
|
|
return await ngrok.connect({ port: port });
|
2019-03-08 06:37:13 -03:00
|
|
|
} else {
|
2019-03-11 19:32:47 -03:00
|
|
|
GBLog.warn('ngrok executable not found (only tested on Windows). Check installation or node_modules folder.');
|
2019-03-08 06:37:13 -03:00
|
|
|
|
2019-04-30 12:56:31 -03:00
|
|
|
return 'https://localhost';
|
2019-03-08 06:37:13 -03:00
|
|
|
}
|
2018-12-01 14:38:08 -02:00
|
|
|
} catch (error) {
|
|
|
|
// There are false positive from ngrok regarding to no memory, but it's just
|
|
|
|
// lack of connection.
|
2020-01-08 17:52:46 -03:00
|
|
|
|
2019-07-19 13:35:11 -03:00
|
|
|
throw new Error(`Error connecting to remote ngrok server, please check network connection. ${error.msg}`);
|
2018-12-01 14:38:08 -02:00
|
|
|
}
|
2018-06-04 08:03:23 -03:00
|
|
|
}
|
|
|
|
|
2020-12-25 13:36:26 -03:00
|
|
|
/**
|
|
|
|
* Setup generic web hooks so .gbapps can expose application logic
|
|
|
|
* and get called on demand.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public installWebHook(isGet: boolean, url: string, callback: any) {
|
2019-06-05 18:23:31 -03:00
|
|
|
if (isGet) {
|
|
|
|
GBServer.globals.server.get(url, (req, res) => {
|
|
|
|
callback(req, res);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
GBServer.globals.server.post(url, (req, res) => {
|
|
|
|
callback(req, res);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-25 13:36:26 -03:00
|
|
|
/**
|
2022-11-19 23:34:58 -03:00
|
|
|
* Defines the entry point dialog to be called whenever a user
|
2020-12-25 13:36:26 -03:00
|
|
|
* starts talking to the bot.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public setEntryPointDialog(dialogName: string) {
|
2020-02-25 12:37:10 -03:00
|
|
|
GBServer.globals.entryPointDialog = dialogName;
|
|
|
|
}
|
|
|
|
|
2020-12-25 13:36:26 -03:00
|
|
|
/**
|
|
|
|
* Replaces the default web application root path used to start the GB
|
|
|
|
* with a custom home page.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public setWWWRoot(localPath: string) {
|
2020-02-25 10:13:38 -03:00
|
|
|
GBServer.globals.wwwroot = localPath;
|
|
|
|
}
|
|
|
|
|
2020-12-25 13:36:26 -03:00
|
|
|
/**
|
|
|
|
* Removes a bot instance from storage.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public async deleteInstance(botId: string) {
|
2019-08-22 01:54:30 +00:00
|
|
|
const options = { where: {} };
|
|
|
|
options.where = { botId: botId };
|
|
|
|
await GuaribasInstance.destroy(options);
|
|
|
|
}
|
|
|
|
|
2020-12-25 13:36:26 -03:00
|
|
|
/**
|
|
|
|
* Saves a bot instance object to the storage handling
|
|
|
|
* multi-column JSON based store 'params' field.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public async saveInstance(fullInstance: any) {
|
2018-11-27 22:56:11 -02:00
|
|
|
const options = { where: {} };
|
|
|
|
options.where = { botId: fullInstance.botId };
|
|
|
|
let instance = await GuaribasInstance.findOne(options);
|
|
|
|
// tslint:disable-next-line:prefer-object-spread
|
2020-09-19 21:57:00 -03:00
|
|
|
if (instance) {
|
|
|
|
instance = Object.assign(instance, fullInstance);
|
|
|
|
} else {
|
|
|
|
instance = Object.assign(new GuaribasInstance(), fullInstance);
|
|
|
|
}
|
2020-05-23 17:59:01 -03:00
|
|
|
try {
|
|
|
|
instance.params = JSON.stringify(JSON.parse(instance.params));
|
|
|
|
} catch (err) {
|
|
|
|
instance.params = JSON.stringify(instance.params);
|
|
|
|
}
|
2020-03-08 09:24:28 -03:00
|
|
|
return await instance.save();
|
2018-04-21 02:59:30 -03:00
|
|
|
}
|
2018-11-26 15:54:34 -02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads all bot instances from object storage, if it's formatted.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public async loadAllInstances(
|
2019-03-08 17:05:58 -03:00
|
|
|
core: IGBCoreService,
|
|
|
|
installationDeployer: IGBInstallationDeployer,
|
|
|
|
proxyAddress: string
|
|
|
|
) {
|
|
|
|
GBLog.info(`Loading instances from storage...`);
|
2019-03-08 06:37:13 -03:00
|
|
|
let instances: IGBInstance[];
|
2018-11-26 15:54:34 -02:00
|
|
|
try {
|
|
|
|
instances = await core.loadInstances();
|
2020-08-22 18:41:54 -03:00
|
|
|
if (process.env.ENDPOINT_UPDATE === 'true') {
|
2020-06-04 13:44:02 -03:00
|
|
|
await CollectionUtil.asyncForEach(instances, async instance => {
|
|
|
|
GBLog.info(`Updating bot endpoint for ${instance.botId}...`);
|
|
|
|
try {
|
|
|
|
await installationDeployer.updateBotProxy(
|
|
|
|
instance.botId,
|
2020-08-22 18:41:54 -03:00
|
|
|
GBConfigService.get('CLOUD_GROUP'),
|
2020-06-04 13:44:02 -03:00
|
|
|
`${proxyAddress}/api/messages/${instance.botId}`
|
|
|
|
);
|
|
|
|
} catch (error) {
|
2020-08-22 18:41:54 -03:00
|
|
|
if (error.code === 'ResourceNotFound') {
|
|
|
|
GBLog.warn(`Bot ${instance.botId} not found on resource group ${GBConfigService.get('CLOUD_GROUP')}.`);
|
|
|
|
} else {
|
2020-06-04 13:44:02 -03:00
|
|
|
throw new Error(`Error updating bot proxy, details: ${error}.`);
|
|
|
|
}
|
2020-04-15 01:42:54 +00:00
|
|
|
}
|
2020-06-04 13:44:02 -03:00
|
|
|
});
|
|
|
|
}
|
2018-11-26 15:54:34 -02:00
|
|
|
} catch (error) {
|
2019-03-11 19:32:47 -03:00
|
|
|
if (error.parent === undefined) {
|
|
|
|
throw new Error(`Cannot connect to operating storage: ${error.message}.`);
|
|
|
|
} else {
|
|
|
|
// Check if storage is empty and needs formatting.
|
|
|
|
const isInvalidObject = error.parent.number === 208 || error.parent.errno === 1; // MSSQL or SQLITE.
|
|
|
|
if (isInvalidObject) {
|
|
|
|
if (GBConfigService.get('STORAGE_SYNC') !== 'true') {
|
|
|
|
throw new Error(
|
|
|
|
`Operating storage is out of sync or there is a storage connection error.
|
2018-11-27 22:56:11 -02:00
|
|
|
Try setting STORAGE_SYNC to true in .env file. Error: ${error.message}.`
|
2019-03-11 19:32:47 -03:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
GBLog.info(`Storage is empty. After collecting storage structure from all .gbapps it will get synced.`);
|
|
|
|
}
|
2018-11-27 22:56:11 -02:00
|
|
|
} else {
|
2019-03-11 19:32:47 -03:00
|
|
|
throw new Error(`Cannot connect to operating storage: ${error.message}.`);
|
2018-11-26 15:54:34 -02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-11-27 22:56:11 -02:00
|
|
|
|
2018-11-26 15:54:34 -02:00
|
|
|
return instances;
|
|
|
|
}
|
|
|
|
|
2020-12-25 13:36:26 -03:00
|
|
|
/**
|
2022-11-19 23:34:58 -03:00
|
|
|
* Loads all system packages from 'packages' folder.
|
2020-12-25 13:36:26 -03:00
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public async loadSysPackages(core: GBCoreService): Promise<IGBPackage[]> {
|
2018-11-26 15:54:34 -02:00
|
|
|
// NOTE: if there is any code before this line a semicolon
|
|
|
|
// will be necessary before this line.
|
|
|
|
// Loads all system packages.
|
2019-06-17 21:41:41 -03:00
|
|
|
const sysPackages: IGBPackage[] = [];
|
2020-04-15 01:42:54 +00:00
|
|
|
|
2020-08-22 18:41:54 -03:00
|
|
|
await CollectionUtil.asyncForEach(
|
|
|
|
[
|
|
|
|
GBAdminPackage,
|
|
|
|
GBCorePackage,
|
|
|
|
GBSecurityPackage,
|
|
|
|
GBKBPackage,
|
|
|
|
GBCustomerSatisfactionPackage,
|
|
|
|
GBAnalyticsPackage,
|
|
|
|
GBWhatsappPackage,
|
|
|
|
GBAzureDeployerPackage,
|
2020-12-27 13:30:56 -03:00
|
|
|
GBSharePointPackage,
|
2021-06-11 09:50:40 -03:00
|
|
|
GBGoogleChatPackage,
|
2021-12-19 16:39:50 -03:00
|
|
|
GBBasicPackage,
|
|
|
|
GBHubSpotPackage
|
2020-08-22 18:41:54 -03:00
|
|
|
],
|
|
|
|
async e => {
|
|
|
|
GBLog.info(`Loading sys package: ${e.name}...`);
|
|
|
|
|
|
|
|
const p = Object.create(e.prototype) as IGBPackage;
|
|
|
|
sysPackages.push(p);
|
|
|
|
|
|
|
|
await p.loadPackage(core, core.sequelize);
|
|
|
|
}
|
|
|
|
);
|
2019-06-17 21:41:41 -03:00
|
|
|
|
|
|
|
return sysPackages;
|
2018-11-26 15:54:34 -02:00
|
|
|
}
|
|
|
|
|
2020-12-25 13:36:26 -03:00
|
|
|
/**
|
2022-11-19 23:34:58 -03:00
|
|
|
* Verifies that an complex global password has been specified
|
2020-12-25 13:36:26 -03:00
|
|
|
* before starting the server.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public ensureAdminIsSecured() {
|
2018-11-26 15:54:34 -02:00
|
|
|
const password = GBConfigService.get('ADMIN_PASS');
|
|
|
|
if (!GBAdminService.StrongRegex.test(password)) {
|
|
|
|
throw new Error(
|
2018-11-27 22:56:11 -02:00
|
|
|
'Please, define a really strong password in ADMIN_PASS environment variable before running the server.'
|
2018-11-26 15:54:34 -02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2020-05-23 17:59:01 -03:00
|
|
|
|
2020-12-25 13:36:26 -03:00
|
|
|
/**
|
|
|
|
* Creates the first bot instance (boot instance) used to "boot" the server.
|
|
|
|
* At least one bot is required to perform conversational administrative tasks.
|
2022-11-19 23:34:58 -03:00
|
|
|
* So a base main bot is always deployed and will act as root bot for
|
|
|
|
* configuration tree with three levels: .env > root bot > all other bots.
|
2020-12-25 13:36:26 -03:00
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public async createBootInstance(
|
2019-03-08 17:05:58 -03:00
|
|
|
core: GBCoreService,
|
|
|
|
installationDeployer: IGBInstallationDeployer,
|
|
|
|
proxyAddress: string
|
|
|
|
) {
|
|
|
|
GBLog.info(`Deploying cognitive infrastructure (on the cloud / on premises)...`);
|
2018-12-01 14:38:08 -02:00
|
|
|
try {
|
2019-03-08 17:05:58 -03:00
|
|
|
const { instance, credentials, subscriptionId } = await StartDialog.createBaseInstance(installationDeployer);
|
2021-04-17 17:20:44 -03:00
|
|
|
installationDeployer['core'] = this;
|
2019-03-08 17:05:58 -03:00
|
|
|
const changedInstance = await installationDeployer.deployFarm(
|
|
|
|
proxyAddress,
|
|
|
|
instance,
|
|
|
|
credentials,
|
|
|
|
subscriptionId
|
|
|
|
);
|
2021-04-17 17:20:44 -03:00
|
|
|
await this.writeEnv(changedInstance);
|
2019-02-25 08:36:43 -03:00
|
|
|
GBConfigService.init();
|
2022-11-19 23:34:58 -03:00
|
|
|
|
2021-04-17 17:20:44 -03:00
|
|
|
GBLog.info(`File .env written. Preparing storage and search for the first time...`);
|
|
|
|
await this.openStorageFrontier(installationDeployer);
|
|
|
|
await this.initStorage();
|
2019-02-25 08:36:43 -03:00
|
|
|
|
2019-03-08 17:05:58 -03:00
|
|
|
return changedInstance;
|
2018-12-01 14:38:08 -02:00
|
|
|
} catch (error) {
|
2019-03-08 17:05:58 -03:00
|
|
|
GBLog.warn(
|
2021-04-14 16:01:53 -03:00
|
|
|
`There is an error being thrown, so please cleanup any infrastructure objects
|
2018-11-28 17:08:06 -02:00
|
|
|
created during this procedure and .env before running again.`
|
2018-12-01 14:38:08 -02:00
|
|
|
);
|
|
|
|
throw error;
|
|
|
|
}
|
2018-11-26 15:54:34 -02:00
|
|
|
}
|
|
|
|
|
2020-12-25 13:36:26 -03:00
|
|
|
/**
|
|
|
|
* Helper to get the web browser onpened in UI interfaces.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public openBrowserInDevelopment() {
|
2018-11-26 15:54:34 -02:00
|
|
|
if (process.env.NODE_ENV === 'development') {
|
2022-11-19 19:50:19 -03:00
|
|
|
open('http://localhost:4242');
|
2018-11-26 15:54:34 -02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-27 22:56:11 -02:00
|
|
|
/**
|
|
|
|
* SQL:
|
|
|
|
*
|
|
|
|
* // let sql: string = '' +
|
|
|
|
* // 'IF OBJECT_ID(\'[UserGroup]\', \'U\') IS NULL' +
|
|
|
|
* // 'CREATE TABLE [UserGroup] (' +
|
|
|
|
* // ' [id] INTEGER NOT NULL IDENTITY(1,1),' +
|
|
|
|
* // ' [userId] INTEGER NULL,' +
|
|
|
|
* // ' [groupId] INTEGER NULL,' +
|
|
|
|
* // ' [instanceId] INTEGER NULL,' +
|
|
|
|
* // ' PRIMARY KEY ([id1], [id2]),' +
|
|
|
|
* // ' FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,' +
|
|
|
|
* // ' FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId1]) ON DELETE NO ACTION,' +
|
|
|
|
* // ' FOREIGN KEY ([instanceId]) REFERENCES [Instance] ([instanceId]) ON DELETE NO ACTION)'
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
private createTableQueryOverride(tableName, attributes, options): string {
|
2018-11-27 22:56:11 -02:00
|
|
|
let sql: string = this.createTableQuery.apply(this.queryGenerator, [tableName, attributes, options]);
|
|
|
|
const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/;
|
|
|
|
const matches = re1.exec(sql);
|
2019-03-08 17:05:58 -03:00
|
|
|
if (matches !== null) {
|
2018-11-27 22:56:11 -02:00
|
|
|
const table = matches[1];
|
|
|
|
const re2 = /PRIMARY\s+KEY\s+\(\[[^\]]*\](?:,\s*\[[^\]]*\])*\)/;
|
2023-02-12 14:31:21 -03:00
|
|
|
sql = sql.replace(re2, (match: string, ...args: any[]): string => {
|
|
|
|
return `CONSTRAINT [${table}_pk] ${match}`;
|
|
|
|
});
|
2018-11-27 22:56:11 -02:00
|
|
|
const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g;
|
|
|
|
const re4 = /\[([^\]]*)\]/g;
|
2023-02-12 14:31:21 -03:00
|
|
|
sql = sql.replace(re3, (match: string, ...args: any[]): string => {
|
|
|
|
const fkcols = args[0];
|
|
|
|
let fkname = table;
|
|
|
|
let matches2 = re4.exec(fkcols);
|
|
|
|
while (matches2 !== null) {
|
|
|
|
fkname += `_${matches2[1]}`;
|
|
|
|
matches2 = re4.exec(fkcols);
|
2022-11-19 23:34:58 -03:00
|
|
|
}
|
2023-02-12 14:31:21 -03:00
|
|
|
|
|
|
|
return `CONSTRAINT [${fkname}_fk] FOREIGN KEY (${fkcols})`;
|
|
|
|
});
|
2018-11-27 22:56:11 -02:00
|
|
|
}
|
2019-03-08 06:49:22 -03:00
|
|
|
|
2018-11-27 22:56:11 -02:00
|
|
|
return sql;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* SQL:
|
|
|
|
* let sql = '' +
|
|
|
|
* 'ALTER TABLE [UserGroup]' +
|
|
|
|
* ' ADD CONSTRAINT [invalid1] FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,' +
|
|
|
|
* ' CONSTRAINT [invalid2] FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId2]) ON DELETE NO ACTION, ' +
|
|
|
|
* ' CONSTRAINT [invalid3] FOREIGN KEY ([instanceId1]) REFERENCES [Instance] ([instanceId1]) ON DELETE NO ACTION'
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
private changeColumnQueryOverride(tableName, attributes): string {
|
2018-11-27 22:56:11 -02:00
|
|
|
let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [tableName, attributes]);
|
|
|
|
const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/;
|
|
|
|
const matches = re1.exec(sql);
|
2019-03-08 17:05:58 -03:00
|
|
|
if (matches !== null) {
|
2018-11-27 22:56:11 -02:00
|
|
|
const table = matches[1];
|
|
|
|
const re2 = /(ADD\s+)?CONSTRAINT\s+\[([^\]]*)\]\s+FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g;
|
|
|
|
const re3 = /\[([^\]]*)\]/g;
|
2023-02-12 14:31:21 -03:00
|
|
|
sql = sql.replace(re2, (match: string, ...args: any[]): string => {
|
|
|
|
const fkcols = args[2];
|
|
|
|
let fkname = table;
|
|
|
|
let matches2 = re3.exec(fkcols);
|
|
|
|
while (matches2 !== null) {
|
|
|
|
fkname += `_${matches2[1]}`;
|
|
|
|
matches2 = re3.exec(fkcols);
|
2022-11-19 23:34:58 -03:00
|
|
|
}
|
2023-02-12 14:31:21 -03:00
|
|
|
|
|
|
|
return `${args[0] ? args[0] : ''}CONSTRAINT [${fkname}_fk] FOREIGN KEY (${fkcols})`;
|
|
|
|
});
|
2018-11-27 22:56:11 -02:00
|
|
|
}
|
2019-03-08 06:49:22 -03:00
|
|
|
|
2018-11-27 22:56:11 -02:00
|
|
|
return sql;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-12-25 13:36:26 -03:00
|
|
|
* Opens storage firewall used by the server when starting to get root bot instance.
|
2018-11-27 22:56:11 -02:00
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
private async openStorageFrontier(installationDeployer: IGBInstallationDeployer) {
|
2018-11-27 22:56:11 -02:00
|
|
|
const group = GBConfigService.get('CLOUD_GROUP');
|
2019-03-11 19:32:47 -03:00
|
|
|
const serverName = GBConfigService.get('STORAGE_SERVER').split('.database.windows.net')[0];
|
2019-03-08 06:37:13 -03:00
|
|
|
await installationDeployer.openStorageFirewall(group, serverName);
|
2018-11-27 22:56:11 -02:00
|
|
|
}
|
2020-07-04 16:32:44 -03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a dynamic param from instance. Dynamic params are defined in Config.xlsx
|
2023-02-27 14:34:38 -03:00
|
|
|
* and loaded into the work folder from comida command.
|
2020-08-22 18:41:54 -03:00
|
|
|
*
|
2020-07-04 16:32:44 -03:00
|
|
|
* @param name Name of param to get from instance.
|
|
|
|
* @param defaultValue Value returned when no param is defined in Config.xlsx.
|
|
|
|
*/
|
2023-02-12 14:31:21 -03:00
|
|
|
public getParam<T>(instance: IGBInstance, name: string, defaultValue?: T): any {
|
2020-07-04 16:32:44 -03:00
|
|
|
let value = null;
|
|
|
|
if (instance.params) {
|
|
|
|
const params = JSON.parse(instance.params);
|
|
|
|
value = params ? params[name] : defaultValue;
|
|
|
|
}
|
2020-08-22 18:41:54 -03:00
|
|
|
if (typeof defaultValue === 'boolean') {
|
2022-02-04 18:50:19 -03:00
|
|
|
return new Boolean(value ? value.toString().toLowerCase() === 'true' : defaultValue);
|
2020-07-04 16:32:44 -03:00
|
|
|
}
|
2020-08-22 18:41:54 -03:00
|
|
|
if (typeof defaultValue === 'string') {
|
2020-07-04 16:32:44 -03:00
|
|
|
return value ? value : defaultValue;
|
|
|
|
}
|
2020-08-22 18:41:54 -03:00
|
|
|
if (typeof defaultValue === 'number') {
|
2020-12-06 16:22:34 -03:00
|
|
|
return new Number(value ? value : defaultValue ? defaultValue : 0);
|
2020-07-04 16:32:44 -03:00
|
|
|
}
|
2020-08-22 18:41:54 -03:00
|
|
|
|
2020-11-26 13:48:21 -03:00
|
|
|
if (instance['dataValues'] && !value) {
|
2020-08-22 18:41:54 -03:00
|
|
|
value = instance['dataValues'][name];
|
|
|
|
if (value === null) {
|
|
|
|
const minBoot = GBServer.globals.minBoot as any;
|
2021-04-17 17:20:44 -03:00
|
|
|
if (minBoot.instance && minBoot.instance.datavalues) {
|
2020-11-26 11:25:04 -03:00
|
|
|
value = minBoot.instance.datavalues[name];
|
2021-04-17 17:20:44 -03:00
|
|
|
}
|
2020-08-22 18:41:54 -03:00
|
|
|
}
|
2020-07-26 16:46:37 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
2020-07-04 16:32:44 -03:00
|
|
|
}
|
2018-04-21 02:59:30 -03:00
|
|
|
}
|