botserver/packages/core.gbapp/services/GBCoreService.ts

929 lines
32 KiB
TypeScript
Raw Normal View History

2018-04-21 02:59:30 -03:00
/*****************************************************************************\
2024-01-09 17:40:48 -03:00
| ® |
| |
| |
| |
| |
2018-04-21 02:59:30 -03:00
| |
| General Bots Copyright (c) pragmatismo.cloud. All rights reserved. |
2018-04-21 02:59:30 -03:00
| 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.cloud. |
2018-04-21 02:59:30 -03:00
| 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. |
| |
\*****************************************************************************/
/**
* @fileoverview General Bots server core.
*/
2018-11-11 19:09:18 -02:00
'use strict';
2018-04-21 02:59:30 -03:00
2024-08-30 14:15:02 -03:00
import { GBLog, GBMinInstance, IGBCoreService, IGBInstallationDeployer, IGBInstance, IGBPackage } from 'botlib';
2024-09-07 18:13:36 -03:00
import fs from 'fs/promises';
import { Sequelize, SequelizeOptions } from 'sequelize-typescript';
import { Op, Dialect } from 'sequelize';
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';
2024-08-30 14:15:02 -03:00
import { v2 as webdav } from 'webdav-server';
import { GBWhatsappPackage } from '../../whatsapp.gblib/index.js';
2023-07-17 14:55:17 -03:00
import { GuaribasApplications, GuaribasInstance, GuaribasLog } from '../models/GBModel.js';
import { GBConfigService } from './GBConfigService.js';
2024-08-19 23:03:58 -03:00
import mkdirp from 'mkdirp';
import { GBAzureDeployerPackage } from '../../azuredeployer.gbapp/index.js';
import { GBSharePointPackage } from '../../sharepoint.gblib/index.js';
import { CollectionUtil } from 'pragmatismo-io-framework';
import { GBBasicPackage } from '../../basic.gblib/index.js';
import { GBGoogleChatPackage } from '../../google-chat.gblib/index.js';
import { GBHubSpotPackage } from '../../hubspot.gblib/index.js';
import open from 'open';
import ngrok from 'ngrok';
2024-09-06 15:30:03 -03:00
import path from 'path';
import { GBUtil } from '../../../src/util.js';
2024-04-21 23:39:39 -03:00
import { GBLogEx } from './GBLogEx.js';
import { GBDeployer } from './GBDeployer.js';
import { SystemKeywords } from '../../basic.gblib/services/SystemKeywords.js';
import { DialogKeywords } from '../../basic.gblib/services/DialogKeywords.js';
2024-08-29 22:10:52 -03:00
import csvdb from 'csv-database';
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.
*/
public sequelize: Sequelize;
2018-04-21 02:59:30 -03:00
/**
* Administrative services.
*/
public adminService: GBAdminService;
2018-09-09 14:39:37 -03:00
/**
* Allows filtering on SQL generated before send to the database.
*/
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-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
/**
* Dialect used. Tested: mssql and sqlite.
2018-09-09 14:39:37 -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() {
this.adminService = new GBAdminService(this);
2018-04-21 02:59:30 -03:00
}
2024-08-19 23:03:58 -03:00
public async ensureInstances(instances: IGBInstance[], bootInstance: any, core: IGBCoreService) {}
/**
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_FILE');
2024-08-20 19:12:57 -03:00
2024-09-07 18:13:36 -03:00
if (!await GBUtil.exists(storage)) {
2024-08-20 19:12:57 -03:00
process.env.STORAGE_SYNC = 'true';
}
2018-11-27 22:56:11 -02:00
} else {
throw new Error(`Unknown dialect: ${this.dialect}.`);
}
const logging: boolean | Function =
2018-11-27 22:56:11 -02:00
GBConfigService.get('STORAGE_LOGGING') === 'true'
? (str: string): void => {
2024-08-19 23:03:58 -03:00
GBLogEx.info(0, str);
}
2018-11-27 22:56:11 -02:00
: false;
const encrypt: boolean = GBConfigService.get('STORAGE_ENCRYPT') === 'true';
const acquire = parseInt(GBConfigService.get('STORAGE_ACQUIRE_TIMEOUT'));
const sequelizeOptions: SequelizeOptions = {
define: {
freezeTableName: true,
timestamps: false
},
2018-11-27 22:56:11 -02:00
host: host,
logging: logging as boolean,
dialect: this.dialect as Dialect,
2018-11-27 22:56:11 -02:00
storage: storage,
quoteIdentifiers: false, // set case-insensitive
2018-11-27 22:56:11 -02:00
dialectOptions: {
options: {
trustServerCertificate: true,
encrypt: encrypt
}
},
pool: {
max: 5,
min: 0,
idle: 10000,
evict: 10000,
acquire: acquire
2018-11-27 22:56:11 -02:00
}
};
this.sequelize = new Sequelize(database, username, password, sequelizeOptions);
2018-11-27 22:56:11 -02:00
}
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) {
2024-04-21 23:39:39 -03:00
GBLogEx.info(0, 'Opening storage firewall on infrastructure...');
// tslint:disable:no-unsafe-any
2018-11-27 22:56:11 -02:00
if (error.parent.code === 'ELOGIN') {
await this.openStorageFrontier(installationDeployer);
2018-11-27 22:56:11 -02:00
} else {
throw error;
}
// 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';
2024-04-21 23:39:39 -03:00
GBLogEx.info(0, 'Syncing database...');
2018-11-27 22:56:11 -02: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 {
const msg = `Database synchronization is disabled.`;
2024-04-21 23:39:39 -03:00
GBLogEx.info(0, 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[]> {
2024-08-21 13:09:50 -03:00
if (process.env.LOAD_ONLY) {
const bots = process.env.LOAD_ONLY.split(`;`);
const and = [];
await CollectionUtil.asyncForEach(bots, async e => {
and.push({ botId: e });
});
const options = {
where: {
[Op.or]: and
}
};
return await GuaribasInstance.findAll(options);
} else {
const options = { where: { state: 'active' } };
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> {
const options = { where: { instanceId: instanceId, state: 'active' } };
2018-11-27 22:56:11 -02:00
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 loadInstanceByActivationCode(code: string): Promise<IGBInstance> {
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: {} };
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) {
const env = `
ADDITIONAL_DEPLOY_PATH=
ADMIN_PASS=${instance.adminPass}
BOT_ID=${instance.botId}
CLOUD_SUBSCRIPTIONID=${instance.cloudSubscriptionId}
CLOUD_LOCATION=${instance.cloudLocation}
CLOUD_USERNAME=${instance.cloudUsername}
CLOUD_PASSWORD=${instance.cloudPassword}
MARKETPLACE_ID=${instance.marketplaceId}
MARKETPLACE_SECRET=${instance.marketplacePassword}
STORAGE_DIALECT=${instance.storageDialect}
STORAGE_SERVER=${instance.storageServer}
STORAGE_NAME=${instance.storageName}
STORAGE_USERNAME=${instance.storageUsername}
STORAGE_PASSWORD=${instance.storagePassword}
STORAGE_SYNC=true
STORAGE_SYNC_ALTER=true
ENDPOINT_UPDATE=true
`;
2018-11-11 19:09:18 -02:00
2024-09-07 18:13:36 -03:00
fs.writeFile('.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 {
2024-09-07 18:13:36 -03:00
if (await GBUtil.exists('node_modules/ngrok/bin/ngrok.exe') || await GBUtil.exists('node_modules/.bin/ngrok')) {
2022-11-19 23:34:58 -03:00
return await ngrok.connect({ port: port });
} else {
2023-07-14 18:45:17 -03:00
GBLog.warn('ngrok executable not found. Check installation or node_modules folder.');
2019-04-30 12:56:31 -03:00
return 'https://localhost';
}
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.
throw new Error(`Error connecting to remote ngrok server, please check network connection. ${error.msg}`);
2018-12-01 14:38:08 -02: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) {
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) {
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) {
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) {
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
if (instance) {
instance = Object.assign(instance, fullInstance);
} else {
instance = Object.assign(new GuaribasInstance(), fullInstance);
}
try {
instance.params = JSON.stringify(JSON.parse(instance.params));
} catch (err) {
instance.params = JSON.stringify(instance.params);
}
return await instance.save();
2018-04-21 02:59:30 -03:00
}
/**
* Loads all bot instances from object storage, if it's formatted.
2023-07-17 14:55:17 -03:00
*/
public async getApplicationsByInstanceId(appPackages, instanceId: number) {
const options = { where: { instanceId: instanceId } };
const apps = await GuaribasApplications.findAll(options);
2023-07-17 14:55:17 -03:00
let matchingAppPackages = [];
await CollectionUtil.asyncForEach(appPackages, async appPackage => {
2024-09-06 15:30:03 -03:00
const filenameOnly = path.basename(appPackage.name);
2023-07-17 15:37:58 -03:00
const matchedApp = apps.find(app => app.name === filenameOnly);
2023-07-18 17:09:04 -03:00
if (matchedApp || filenameOnly.endsWith('.gblib')) {
2023-07-17 14:55:17 -03:00
matchingAppPackages.push(appPackage);
}
});
2023-07-17 14:55:17 -03:00
return matchingAppPackages;
}
2023-07-17 14:55:17 -03:00
/**
* Loads all bot instances from object storage, if it's formatted.
*/
2023-02-12 14:31:21 -03:00
public async loadAllInstances(
core: IGBCoreService,
installationDeployer: IGBInstallationDeployer,
proxyAddress: string
) {
2024-04-21 23:39:39 -03:00
GBLogEx.info(0, `Loading instances from storage...`);
let instances: IGBInstance[];
try {
instances = await core.loadInstances();
if (process.env.ENDPOINT_UPDATE === 'true') {
2024-08-20 15:13:43 -03:00
const group = GBConfigService.get('CLOUD_GROUP') ?? GBConfigService.get('BOT_ID');
await CollectionUtil.asyncForEach(instances, async instance => {
2024-04-21 23:39:39 -03:00
GBLogEx.info(instance.instanceId, `Updating bot endpoint for ${instance.botId}...`);
try {
await installationDeployer.updateBotProxy(
instance.botId,
2024-03-17 20:32:10 -03:00
group,
`${proxyAddress}/api/messages/${instance.botId}`
);
} catch (error) {
if (error.code === 'ResourceNotFound') {
2024-03-12 19:00:27 -03:00
GBLog.warn(`Bot ${instance.botId} not found on resource group ${GBConfigService.get('BOT_ID')}.`);
} else {
throw new Error(`Error updating bot proxy, details: ${error}.`);
}
}
});
}
} catch (error) {
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}.`
);
} else {
2024-08-19 23:03:58 -03:00
GBLogEx.info(
0,
`Storage is empty. After collecting storage structure from all .gbapps it will get synced.`
);
}
2018-11-27 22:56:11 -02:00
} else {
throw new Error(`Cannot connect to operating storage: ${error.message}.`);
}
}
}
2018-11-27 22:56:11 -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[]> {
// NOTE: if there is any code before this line a semicolon
// will be necessary before this line.
// Loads all system packages.
const sysPackages: IGBPackage[] = [];
await CollectionUtil.asyncForEach(
[
GBAdminPackage,
GBCorePackage,
GBSecurityPackage,
GBKBPackage,
GBCustomerSatisfactionPackage,
GBAnalyticsPackage,
GBWhatsappPackage,
GBAzureDeployerPackage,
GBSharePointPackage,
GBGoogleChatPackage,
2021-12-19 16:39:50 -03:00
GBBasicPackage,
GBHubSpotPackage
],
async e => {
2024-04-21 23:39:39 -03:00
GBLogEx.info(0, `Loading sys package: ${e.name}...`);
const p = Object.create(e.prototype) as IGBPackage;
sysPackages.push(p);
await p.loadPackage(core, core.sequelize);
}
);
return sysPackages;
}
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.
*/
2024-08-29 22:10:52 -03:00
public ensureAdminIsSecured() {}
2023-07-14 18:45:17 -03:00
public async createBootInstance(
core: GBCoreService,
installationDeployer: IGBInstallationDeployer,
proxyAddress: string
) {
return await this.createBootInstanceEx(
core,
installationDeployer,
2024-08-19 23:03:58 -03:00
proxyAddress,
null,
GBConfigService.get('FREE_TIER')
);
2023-07-14 18:45:17 -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-07-14 18:45:17 -03:00
public async createBootInstanceEx(
core: GBCoreService,
installationDeployer: IGBInstallationDeployer,
2023-07-14 18:45:17 -03:00
proxyAddress: string,
deployer,
freeTier
) {
2024-04-21 23:39:39 -03:00
GBLogEx.info(0, `Deploying cognitive infrastructure (on the cloud / on premises)...`);
2018-12-01 14:38:08 -02:00
try {
2024-08-19 23:03:58 -03:00
const { instance, credentials, subscriptionId, installationDeployer } = await StartDialog.createBaseInstance(
deployer,
freeTier
);
installationDeployer['core'] = this;
const changedInstance = await installationDeployer['deployFarm2'](
proxyAddress,
instance,
credentials,
subscriptionId
);
await this.writeEnv(changedInstance);
GBConfigService.init();
2022-11-19 23:34:58 -03:00
2024-04-21 23:39:39 -03:00
GBLogEx.info(0, `File .env written. Preparing storage and search for the first time...`);
await this.openStorageFrontier(installationDeployer);
await this.initStorage();
2023-07-14 18:45:17 -03:00
return [changedInstance, installationDeployer];
2018-12-01 14:38:08 -02:00
} catch (error) {
GBLog.warn(
`There is an error being thrown, so please cleanup any infrastructure objects
created during this procedure and .env before running again.`
2018-12-01 14:38:08 -02:00
);
throw error;
}
}
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() {
if (process.env.NODE_ENV === 'development') {
open('http://localhost:4242');
}
}
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);
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
}
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);
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
}
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) {
2024-03-12 19:00:27 -03:00
const group = GBConfigService.get('BOT_ID');
const serverName = GBConfigService.get('STORAGE_SERVER').split('.database.windows.net')[0];
await installationDeployer.openStorageFirewall(group, serverName);
2018-11-27 22:56:11 -02:00
}
2020-07-04 16:32:44 -03:00
public async setConfig(min, name: string, value: any): Promise<any> {
2024-08-29 22:10:52 -03:00
if (GBConfigService.get('STORAGE_NAME')) {
// Handles calls for BASIC persistence on sheet files.
2024-08-29 22:10:52 -03:00
GBLog.info(`Defining Config.xlsx variable ${name}= '${value}'...`);
2024-08-29 22:10:52 -03:00
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
2024-08-29 22:10:52 -03:00
const maxLines = 512;
const file = 'Config.xlsx';
2024-09-07 00:08:23 -03:00
const packagePath = GBUtil.getGBAIPath(min.botId, `gbot`);
2024-09-07 00:08:23 -03:00
let document = await new SystemKeywords().internalGetDocument(client, baseUrl, packagePath, file);
2024-08-29 22:10:52 -03:00
// Creates book session that will be discarded.
2024-08-29 22:10:52 -03:00
let sheets = await client.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets`).get();
2024-08-29 22:10:52 -03:00
let results = await client
.api(
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='A1:A${maxLines}')`
)
.get();
2024-08-29 22:10:52 -03:00
const rows = results.text;
let address = '';
2024-08-29 22:10:52 -03:00
// Fills the row variable.
2024-08-29 22:10:52 -03:00
for (let i = 1; i <= rows.length; i++) {
let result = rows[i - 1][0];
if (result && result.toLowerCase() === name.toLowerCase()) {
address = `B${i}:B${i}`;
break;
}
}
2024-08-29 22:10:52 -03:00
let body = { values: [[]] };
body.values[0][0] = value;
await client
.api(
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${address}')`
)
.patch(body);
} else {
2024-09-07 00:08:23 -03:00
let packagePath = GBUtil.getGBAIPath(min.botId, `gbot`);
const config = path.join(GBConfigService.get('STORAGE_LIBRARY'), packagePath, 'config.csv');
2024-08-29 22:10:52 -03:00
const db = await csvdb(config, ['name', 'value'], ',');
if (await db.get({ name: name })) {
await db.edit({ name: name }, { name, value });
} else {
await db.add({ name, value });
}
}
}
2024-01-09 17:40:48 -03:00
2020-07-04 16:32:44 -03:00
/**
* Get a dynamic param from instance. Dynamic params are defined in Config.xlsx
* and loaded into the work folder from comida command.
*
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.
*/
2024-08-29 22:10:52 -03:00
public getParam<T>(instance: IGBInstance, name: string, defaultValue?: T, platform = false): any {
2020-07-04 16:32:44 -03:00
let value = null;
let params;
name = name.trim();
// Gets .gbot Params from specified bot.
2020-07-04 16:32:44 -03:00
if (instance.params) {
2024-08-19 23:03:58 -03:00
params = typeof instance.params === 'object' ? instance.params : JSON.parse(instance.params);
params = GBUtil.caseInsensitive(params);
2020-07-04 16:32:44 -03:00
value = params ? params[name] : defaultValue;
}
// Gets specified bot instance values.
params = GBUtil.caseInsensitive(instance['dataValues']);
if (params && !value) {
// Retrieves the value from specified bot instance (no params collection).
value = instance['dataValues'][name];
// If still not found, get from boot bot params.
const minBoot = GBServer.globals.minBoot as any;
2024-08-19 23:03:58 -03:00
if (minBoot.instance && !value && instance.botId != minBoot.instance.botId) {
instance = minBoot.instance;
2024-08-19 23:03:58 -03:00
if (instance.params) {
params = typeof instance.params === 'object' ? instance.params : JSON.parse(instance.params);
params = GBUtil.caseInsensitive(params);
value = params ? params[name] : defaultValue;
}
// If still did not found in boot bot params, try instance fields.
2024-08-19 23:03:58 -03:00
if (!value) {
value = instance['dataValues'][name];
}
2024-08-19 23:03:58 -03:00
if (!value) {
2024-05-25 21:53:21 -03:00
value = instance[name];
}
}
}
2024-08-19 23:03:58 -03:00
if (value === undefined) {
value = null;
}
2024-08-29 22:10:52 -03:00
if (!value && platform) {
value = process.env[name.replace(/ /g, '_').toUpperCase()];
2024-08-29 19:53:56 -03:00
}
if (value && typeof defaultValue === 'boolean') {
return new Boolean(value ? value.toString().toLowerCase() === 'true' : defaultValue).valueOf();
}
if (value && typeof defaultValue === 'string') {
return value ? value : defaultValue;
}
if (value && typeof defaultValue === 'number') {
return new Number(value ? value : defaultValue ? defaultValue : 0).valueOf();
}
2024-08-19 23:03:58 -03:00
const ret = value ?? defaultValue;
return ret;
2020-07-04 16:32:44 -03:00
}
/**
* Finds a dynamic param from instance. *
*/
public async findParam<T>(instance: IGBInstance, criteria: string) {
let params = null;
const list = [];
if (instance.params) {
2024-08-19 23:03:58 -03:00
params = typeof instance.params === 'object' ? instance.params : JSON.parse(instance.params);
}
Object.keys(params).forEach(e => {
if (e.toLowerCase().indexOf(criteria.toLowerCase()) !== -1) {
list.push(e);
}
});
return list;
}
2024-08-19 23:03:58 -03:00
public async ensureFolders(instances, deployer: GBDeployer) {
let libraryPath = GBConfigService.get('STORAGE_LIBRARY');
2024-09-07 18:13:36 -03:00
if (!await GBUtil.exists(libraryPath)) {
2024-08-29 22:10:52 -03:00
mkdirp.sync(libraryPath);
2024-08-19 23:03:58 -03:00
}
await this.syncBotStorage(instances, 'default', deployer, libraryPath);
2024-08-29 22:10:52 -03:00
2024-09-07 18:13:36 -03:00
const files = fs.readdir(libraryPath);
2024-08-19 23:03:58 -03:00
await CollectionUtil.asyncForEach(files, async file => {
2024-08-29 22:10:52 -03:00
if (file.trim().toLowerCase() !== 'default.gbai') {
let botId = file.replace(/\.gbai/, '');
2024-08-19 23:03:58 -03:00
2024-08-29 22:10:52 -03:00
await this.syncBotStorage(instances, botId, deployer, libraryPath);
2024-08-19 23:03:58 -03:00
}
});
}
private async syncBotStorage(instances: any, botId: any, deployer: GBDeployer, libraryPath: string) {
let instance = instances.find(p => p.botId.toLowerCase().trim() === botId.toLowerCase().trim());
if (!instance) {
GBLog.info(`Importing package ${botId}...`);
2024-08-29 22:10:52 -03:00
2024-08-19 23:03:58 -03:00
// Creates a bot.
let mobile = null,
email = null;
instance = await deployer.deployBlankBot(botId, mobile, email);
2024-09-06 15:30:03 -03:00
const gbaiPath = path.join(libraryPath, `${botId}.gbai`);
2024-08-19 23:03:58 -03:00
2024-09-07 18:13:36 -03:00
if (!await GBUtil.exists(gbaiPath)) {
fs.mkdir(gbaiPath, { recursive: true });
2024-08-19 23:03:58 -03:00
2024-09-06 15:30:03 -03:00
const base = path.join(process.env.PWD, 'templates', 'default.gbai');
2024-08-19 23:03:58 -03:00
2024-09-07 18:13:36 -03:00
fs.cp(path.join(base, `default.gbkb`), path.join(gbaiPath, `default.gbkb`), {
2024-08-19 23:03:58 -03:00
errorOnExist: false,
force: true,
recursive: true
});
2024-09-07 18:13:36 -03:00
fs.cp(path.join(base, `default.gbot`), path.join(gbaiPath, `default.gbot`), {
2024-08-19 23:03:58 -03:00
errorOnExist: false,
force: true,
recursive: true
});
2024-09-07 18:13:36 -03:00
fs.cp(path.join(base, `default.gbtheme`), path.join(gbaiPath, `default.gbtheme`), {
2024-08-19 23:03:58 -03:00
errorOnExist: false,
force: true,
recursive: true
});
2024-09-07 18:13:36 -03:00
fs.cp(path.join(base, `default.gbdata`), path.join(gbaiPath, `default.gbdata`), {
2024-08-19 23:03:58 -03:00
errorOnExist: false,
force: true,
recursive: true
});
2024-09-07 18:13:36 -03:00
fs.cp(path.join(base, `default.gbdialog`), path.join(gbaiPath, `default.gbdialog`), {
2024-08-19 23:03:58 -03:00
errorOnExist: false,
force: true,
2024-08-29 22:10:52 -03:00
recursive: true
2024-08-19 23:03:58 -03:00
});
2024-09-07 18:13:36 -03:00
fs.cp(path.join(base, `default.gbdrive`), path.join(gbaiPath, `default.gbdrive`), {
2024-08-19 23:03:58 -03:00
errorOnExist: false,
force: true,
recursive: true
});
}
}
}
2024-08-30 14:15:02 -03:00
public static async createWebDavServer(minInstances: GBMinInstance[]) {
const userManager = new webdav.SimpleUserManager();
const privilegeManager = new webdav.SimplePathPrivilegeManager();
// Create the WebDAV server
const server = new webdav.WebDAVServer({
port: 1900,
httpAuthentication: new webdav.HTTPDigestAuthentication(userManager, 'Default realm'),
privilegeManager: privilegeManager
});
GBServer.globals.webDavServer = server;
minInstances.forEach(min => {
const user = min.core.getParam(min.instance, 'WebDav Username', GBConfigService.get('WEBDAV_USERNAME'));
const pass = min.core.getParam(min.instance, 'WebDav Password', GBConfigService.get('WEBDAV_PASSWORD'));
if (user && pass) {
const objUser = userManager.addUser(user, pass);
const virtualPath = '/' + min.botId;
let path = GBUtil.getGBAIPath(min.botId, null);
2024-09-06 15:30:03 -03:00
const gbaiRoot = path.join(GBConfigService.get('STORAGE_LIBRARY'), path);
2024-08-30 14:15:02 -03:00
server.setFileSystem(virtualPath, new webdav.PhysicalFileSystem(gbaiRoot), successed => {
GBLogEx.info(min.instance.instanceId, `WebDav online for ${min.botId}...`);
});
privilegeManager.setRights(objUser, virtualPath, ['all']);
}
});
server.start(1900);
}
2018-04-21 02:59:30 -03:00
}