diff --git a/.prettierrc b/.prettierrc index 847c9dbaa..af8db0fa7 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,8 +1,9 @@ { - "trailingComma": "all", + "trailingComma": "none", "tabWidth": 2, - "printWidth": 80, + "printWidth": 120, "arrowParens": "avoid", "semi": true, "singleQuote": true + } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2beefb6b2..17f168a04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2966,9 +2966,9 @@ } }, "bluebird": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", - "integrity": "sha1-G+CQjgVKdRdUVJwnBInBUF1KsVo=" + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" }, "body-parser": { "version": "1.18.3", @@ -7105,13 +7105,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7124,18 +7122,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -7238,8 +7233,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -7249,7 +7243,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7262,20 +7255,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.2.4", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -7292,7 +7282,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -7365,8 +7354,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -7376,7 +7364,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -7482,7 +7469,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/package.json b/package.json index 93cfb7aed..30542ebc1 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "azure-arm-search": "^1.3.0-preview", "azure-arm-sql": "5.6.0", "azure-arm-website": "5.7.0", + "bluebird": "^3.5.3", "body-parser": "1.18.3", "botbuilder": "^4.1.5", "botbuilder-ai": "^4.1.5", diff --git a/packages/admin.gbapp/dialogs/AdminDialog.ts b/packages/admin.gbapp/dialogs/AdminDialog.ts index 2d0b220c5..a12f19100 100644 --- a/packages/admin.gbapp/dialogs/AdminDialog.ts +++ b/packages/admin.gbapp/dialogs/AdminDialog.ts @@ -63,11 +63,11 @@ export class AdminDialog extends IGBDialog { ); } - public static async deployPackageCommand(text: string, deployer: GBDeployer) { + public static async deployPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) { const packageName = text.split(' ')[1]; const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH'); - await deployer.deployPackageFromLocalPath( - UrlJoin(additionalPath, packageName) + await deployer.deployPackageFromLocalPath(min, + UrlJoin(additionalPath, packageName) ); } /** @@ -119,11 +119,11 @@ export class AdminDialog extends IGBDialog { await AdminDialog.createFarmCommand(text, deployer); await step.replaceDialog('/admin', { firstRun: false }); } else if (cmdName === 'deployPackage') { - await AdminDialog.deployPackageCommand(text, deployer); + await AdminDialog.deployPackageCommand(min, text, deployer); await step.replaceDialog('/admin', { firstRun: false }); } else if (cmdName === 'redeployPackage') { await AdminDialog.undeployPackageCommand(text, min); - await AdminDialog.deployPackageCommand(text, deployer); + await AdminDialog.deployPackageCommand(min, text, deployer); await step.replaceDialog('/admin', { firstRun: false }); } else if (cmdName === 'undeployPackage') { await AdminDialog.undeployPackageCommand(text, min); diff --git a/packages/boot.gbot/package.json b/packages/boot.gbot/package.json new file mode 100644 index 000000000..ef064f8a6 --- /dev/null +++ b/packages/boot.gbot/package.json @@ -0,0 +1,12 @@ +{ + "version": "1.0.0", + "theme": "default.gbtheme", + "ui": "default.gbui", + "kb": "default.gbkb", + "title": "Default General Bot", + "description": "Default General Bot", + "whoAmIVideo": "TODO.mp4", + "author": "pragmatismo.io", + "license": "AGPL", + "engineName": "guaribas-1.0.0" +} \ No newline at end of file diff --git a/packages/boot.gbot/security.json b/packages/boot.gbot/security.json new file mode 100644 index 000000000..41ce2ba00 --- /dev/null +++ b/packages/boot.gbot/security.json @@ -0,0 +1,3 @@ +{ + "groups": [{}] +} diff --git a/packages/boot.gbot/services.json b/packages/boot.gbot/services.json new file mode 100644 index 000000000..2c63c0851 --- /dev/null +++ b/packages/boot.gbot/services.json @@ -0,0 +1,2 @@ +{ +} diff --git a/packages/boot.gbot/settings.json b/packages/boot.gbot/settings.json new file mode 100644 index 000000000..493a65bf7 --- /dev/null +++ b/packages/boot.gbot/settings.json @@ -0,0 +1,6 @@ +{ + "enabledAdmin": "true", + "searchScore": ".15", + "nlpScore": ".15", + "nlpVsSearch": ".4" +} diff --git a/packages/core.gbapp/services/GBAPIService.ts b/packages/core.gbapp/services/GBAPIService.ts index e79025c70..70acdf7d9 100644 --- a/packages/core.gbapp/services/GBAPIService.ts +++ b/packages/core.gbapp/services/GBAPIService.ts @@ -46,7 +46,7 @@ export class DialogClass { this.min = min; } - public async expectMessage(text: string): Promise { + public async hear(text: string): Promise { return new Promise((resolve, reject) => { this.min.dialogs.add( new WaterfallDialog('/vmExpect', [ @@ -63,7 +63,11 @@ export class DialogClass { }); } - public sendMessage(text: string) { + public post(url: string, data) { + + } + + public talk(text: string) { this.min.dialogs.add( new WaterfallDialog('/vmSend', [ async step => { diff --git a/packages/core.gbapp/services/GBConversationalService.ts b/packages/core.gbapp/services/GBConversationalService.ts index 0eda28154..d062353a8 100644 --- a/packages/core.gbapp/services/GBConversationalService.ts +++ b/packages/core.gbapp/services/GBConversationalService.ts @@ -31,18 +31,15 @@ \*****************************************************************************/ /** - * @fileoverview General Bots server core. + * @fileoverview Conversation handling and external service calls. */ 'use strict'; const logger = require('../../../src/logger'); - -import { any } from 'bluebird'; import { MessageFactory } from 'botbuilder'; import { LuisRecognizer } from 'botbuilder-ai'; -import { IGBConversationalService } from 'botlib'; -import { GBMinInstance } from 'botlib'; +import { GBMinInstance, IGBConversationalService } from 'botlib'; import { AzureText } from 'pragmatismo-io-framework'; import { Messages } from '../strings'; import { GBCoreService } from './GBCoreService'; @@ -70,33 +67,27 @@ export class GBConversationalService implements IGBConversationalService { msg.value = value; msg.type = 'event'; msg.name = name; + return step.context.sendActivity(msg); } } - public async sendSms( - min: GBMinInstance, - mobile: string, - text: string - ): Promise { - return new Promise((resolve: any, reject: any): any => { - const nexmo = new Nexmo({ - apiKey: min.instance.smsKey, - apiSecret: min.instance.smsSecret - }); - nexmo.message.sendSms( - min.instance.smsServiceNumber, - mobile, - text, - (err, data) => { + public async sendSms(min: GBMinInstance, mobile: string, text: string): Promise { + return new Promise( + (resolve: any, reject: any): any => { + const nexmo = new Nexmo({ + apiKey: min.instance.smsKey, + apiSecret: min.instance.smsSecret + }); + nexmo.message.sendSms(min.instance.smsServiceNumber, mobile, text, (err, data) => { if (err) { reject(err); } else { resolve(data); } - } - ); - }); + }); + } + ); } public async routeNLP(step: any, min: GBMinInstance, text: string): Promise { @@ -112,15 +103,17 @@ export class GBConversationalService implements IGBConversationalService { try { nlp = await model.recognize(step.context); } catch (error) { - if (error.statusCode == 404) { - logger.warn ('NLP application still not publish and there are no other options for answering.'); + if (error.statusCode === 404) { + logger.warn('NLP application still not publish and there are no other options for answering.'); + return Promise.resolve(false); } else { - const msg = `Error calling NLP server, check if you have a published model and assigned keys on the service. Error: ${ - error.statusCode ? error.statusCode : '' - } ${error.message}`; - return Promise.reject(new Error(msg)); } + const msg = `Error calling NLP, check if you have a published model and assigned keys. Error: ${ + error.statusCode ? error.statusCode : '' + } ${error.message}`; + return Promise.reject(new Error(msg)); + } } // Resolves intents returned from LUIS. @@ -128,37 +121,31 @@ export class GBConversationalService implements IGBConversationalService { const topIntent = LuisRecognizer.topIntent(nlp); if (topIntent) { const intent = topIntent; - const entity = - nlp.entities && nlp.entities.length > 0 - ? nlp.entities[0].entity.toUpperCase() - : null; + const entity = nlp.entities && nlp.entities.length > 0 ? nlp.entities[0].entity.toUpperCase() : null; if (intent === 'None') { return Promise.resolve(false); } - logger.info('NLP called:' + intent + ', ' + entity); + logger.info(`NLP called: ${intent}, ${entity}`); try { - await step.replace('/' + intent, nlp.entities); + await step.replace(`/${intent}`, nlp.entities); + return Promise.resolve(true); } catch (error) { - const msg = `Error finding dialog associated to NLP event: ${intent}: ${ - error.message - }`; + const msg = `Error finding dialog associated to NLP event: ${intent}: ${error.message}`; + return Promise.reject(new Error(msg)); } } + return Promise.resolve(false); } public async checkLanguage(step, min, text) { - const locale = await AzureText.getLocale( - min.instance.textAnalyticsKey, - min.instance.textAnalyticsEndpoint, - text - ); - if (locale != step.context.activity.locale.split('-')[0]) { + const locale = await AzureText.getLocale(min.instance.textAnalyticsKey, min.instance.textAnalyticsEndpoint, text); + if (locale !== step.context.activity.locale.split('-')[0]) { switch (locale) { case 'pt': step.context.activity.locale = 'pt-BR'; diff --git a/packages/core.gbapp/services/GBCoreService.ts b/packages/core.gbapp/services/GBCoreService.ts index 88f6690f3..a7f6256d4 100644 --- a/packages/core.gbapp/services/GBCoreService.ts +++ b/packages/core.gbapp/services/GBCoreService.ts @@ -39,17 +39,18 @@ import { IGBCoreService, IGBInstance, IGBPackage } from 'botlib'; import * as fs from 'fs'; import { Sequelize } from 'sequelize-typescript'; +import { GBAdminPackage } from '../../admin.gbapp/index'; import { GBAdminService } from '../../admin.gbapp/services/GBAdminService'; +import { GBAnalyticsPackage } from '../../analytics.gblib'; +import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService'; +import { GBCorePackage } from '../../core.gbapp'; +import { GBCustomerSatisfactionPackage } from '../../customer-satisfaction.gbapp'; +import { GBKBPackage } from '../../kb.gbapp'; +import { GBSecurityPackage } from '../../security.gblib'; +import { GBWhatsappPackage } from '../../whatsapp.gblib/index'; import { GuaribasInstance } from '../models/GBModel'; import { GBConfigService } from './GBConfigService'; -import { AzureDeployerService } from 'packages/azuredeployer.gbapp/services/AzureDeployerService'; -import { GBAnalyticsPackage } from 'packages/analytics.gblib'; -import { GBAdminPackage } from 'packages/admin.gbapp/index'; -import { GBCorePackage } from 'packages/core.gbapp'; -import { GBCustomerSatisfactionPackage } from 'packages/customer-satisfaction.gbapp'; -import { GBKBPackage } from 'packages/kb.gbapp'; -import { GBSecurityPackage } from 'packages/security.gblib'; -import { GBWhatsappPackage } from 'packages/whatsapp.gblib/index'; +import { GBImporter } from './GBImporterService'; const logger = require('../../../src/logger'); const opn = require('opn'); @@ -76,11 +77,7 @@ export class GBCoreService implements IGBCoreService { /** * Custom create table query. */ - private createTableQuery: ( - tableName: string, - attributes: any, - options: any, - ) => string; + private createTableQuery: (tableName: string, attributes: any, options: any) => string; /** * Custom change column query. @@ -102,78 +99,79 @@ export class GBCoreService implements IGBCoreService { /** * Gets database config and connect to storage. */ - public async initDatabase(): Promise { - return new Promise( - (resolve: any, reject: any): any => { - try { - 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 { - reject(`Unknown dialect: ${this.dialect}.`); - } - const logging: any = - GBConfigService.get('STORAGE_LOGGING') === 'true' - ? (str: string): void => { - logger.info(str); - } - : false; - - const encrypt: boolean = - GBConfigService.get('STORAGE_ENCRYPT') === 'true'; - - 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, - }, - }); - - if (this.dialect === 'mssql') { - this.queryGenerator = this.sequelize.getQueryInterface().QueryGenerator; - this.createTableQuery = this.queryGenerator.createTableQuery; - this.queryGenerator.createTableQuery = ( - tableName, - attributes, - options, - ) => this.createTableQueryOverride(tableName, attributes, options); - this.changeColumnQuery = this.queryGenerator.changeColumnQuery; - this.queryGenerator.changeColumnQuery = (tableName, attributes) => - this.changeColumnQueryOverride(tableName, attributes); + public async initStorage(): Promise { + 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}.`); + } + + const logging: any = + GBConfigService.get('STORAGE_LOGGING') === 'true' + ? (str: string): void => { + logger.info(str); } - resolve(); - } catch (error) { - reject(error); - } + : false; + + const encrypt: boolean = GBConfigService.get('STORAGE_ENCRYPT') === 'true'; + + 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 + } + }); + + if (this.dialect === 'mssql') { + this.queryGenerator = this.sequelize.getQueryInterface().QueryGenerator; + this.createTableQuery = this.queryGenerator.createTableQuery; + this.queryGenerator.createTableQuery = (tableName, attributes, options) => + this.createTableQueryOverride(tableName, attributes, options); + this.changeColumnQuery = this.queryGenerator.changeColumnQuery; + this.queryGenerator.changeColumnQuery = (tableName, attributes) => + this.changeColumnQueryOverride(tableName, attributes); + } + } + + public async checkStorage(azureDeployer: AzureDeployerService) { + try { + await this.sequelize.authenticate(); + } catch (error) { + logger.info('Opening storage firewall on infrastructure...'); + if (error.parent.code === 'ELOGIN') { + await this.openStorageFrontier(azureDeployer); + } else { + throw error; + } + } } public async syncDatabaseStructure() { @@ -181,9 +179,10 @@ export class GBCoreService implements IGBCoreService { const alter = GBConfigService.get('STORAGE_SYNC_ALTER') === 'true'; const force = GBConfigService.get('STORAGE_SYNC_FORCE') === 'true'; logger.info('Syncing database...'); + return this.sequelize.sync({ alter: alter, - force: force, + force: force }); } else { const msg = 'Database synchronization is disabled.'; @@ -203,6 +202,7 @@ export class GBCoreService implements IGBCoreService { */ public async loadInstanceById(instanceId: string): Promise { const options = { where: { instanceId: instanceId } }; + return GuaribasInstance.findOne(options); } @@ -211,134 +211,46 @@ export class GBCoreService implements IGBCoreService { */ public async loadInstance(botId: string): Promise { const options = { where: {} }; + options.where = { botId: botId }; - if (botId !== '[default]') { - options.where = { botId: botId }; - } - - return GuaribasInstance.findOne(options); + return await GuaribasInstance.findOne(options); } public async writeEnv(instance: IGBInstance) { - const env = - `ADDITIONAL_DEPLOY_PATH=\n` + - `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` + - `MARKETPLACE_ID=${instance.marketplaceId}\n` + - `MARKETPLACE_SECRET=${instance.marketplacePassword}\n` + - `NLP_AUTHORING_KEY=${instance.nlpAuthoringKey}\n` + - `STORAGE_DIALECT=${instance.storageDialect}\n` + - `STORAGE_SERVER=${instance.storageServer}.database.windows.net\n` + - `STORAGE_NAME=${instance.storageName}\n` + - `STORAGE_USERNAME=${instance.storageUsername}\n` + - `STORAGE_PASSWORD=${instance.storagePassword}\n` + - `STORAGE_SYNC=true\n`; + const env = `ADDITIONAL_DEPLOY_PATH= + ADMIN_PASS=${instance.adminPass} + 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} + NLP_AUTHORING_KEY=${instance.nlpAuthoringKey} + STORAGE_DIALECT=${instance.storageDialect} + STORAGE_SERVER=${instance.storageServer}.database.windows.net + STORAGE_NAME=${instance.storageName} + STORAGE_USERNAME=${instance.storageUsername} + STORAGE_PASSWORD=${instance.storagePassword} + STORAGE_SYNC=true`; fs.writeFileSync('.env', env); } public async ensureProxy(port): Promise { - let proxyAddress: string; const ngrok = require('ngrok'); + return await ngrok.connect({ port: port }); } - /** - * SQL: - * - * // 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' + - * // ' FOREIGN KEY ([instanceId]) REFERENCES [Instance] ([instanceId]) ON DELETE NO ACTION)' - */ - private createTableQueryOverride(tableName, attributes, options): string { - let sql: string = this.createTableQuery.apply(this.queryGenerator, [ - tableName, - attributes, - options, - ]); - const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/; - const matches = re1.exec(sql); - if (matches) { - const table = matches[1]; - const re2 = /PRIMARY\s+KEY\s+\(\[[^\]]*\](?:,\s*\[[^\]]*\])*\)/; - sql = sql.replace( - re2, - (match: string, ...args: any[]): string => { - return 'CONSTRAINT [' + table + '_pk] ' + match; - }, - ); - const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g; - const re4 = /\[([^\]]*)\]/g; - sql = sql.replace( - re3, - (match: string, ...args: any[]): string => { - const fkcols = args[0]; - let fkname = table; - let matches = re4.exec(fkcols); - while (matches != null) { - fkname += '_' + matches[1]; - matches = re4.exec(fkcols); - } - return 'CONSTRAINT [' + fkname + '_fk] FOREIGN KEY (' + fkcols + ')'; - }, - ); - } - return sql; - } + public async saveInstance(fullInstance: any) { + const options = { where: {} }; + options.where = { botId: fullInstance.botId }; + let instance = await GuaribasInstance.findOne(options); + // tslint:disable-next-line:prefer-object-spread + instance = Object.assign(instance, fullInstance); - /** - * 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' + - * ' CONSTRAINT [invalid3] FOREIGN KEY ([instanceId1]) REFERENCES [Instance] ([instanceId1]) ON DELETE NO ACTION\n' - */ - private changeColumnQueryOverride(tableName, attributes): string { - let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [ - tableName, - attributes, - ]); - const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/; - const matches = re1.exec(sql); - if (matches) { - const table = matches[1]; - const re2 = /(ADD\s+)?CONSTRAINT\s+\[([^\]]*)\]\s+FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g; - const re3 = /\[([^\]]*)\]/g; - sql = sql.replace( - re2, - (match: string, ...args: any[]): string => { - const fkcols = args[2]; - let fkname = table; - let matches = re3.exec(fkcols); - while (matches != null) { - fkname += '_' + matches[1]; - matches = re3.exec(fkcols); - } - return ( - (args[0] ? args[0] : '') + - 'CONSTRAINT [' + - fkname + - '_fk] FOREIGN KEY (' + - fkcols + - ')' - ); - }, - ); - } - return sql; + return await instance.save(); } /** @@ -348,11 +260,7 @@ export class GBCoreService implements IGBCoreService { * @param azureDeployer * @param proxyAddress */ - public async loadAllInstances( - core: GBCoreService, - azureDeployer: AzureDeployerService, - proxyAddress: string, - ) { + public async loadAllInstances(core: GBCoreService, azureDeployer: AzureDeployerService, proxyAddress: string) { logger.info(`Loading instances from storage...`); let instances: GuaribasInstance[]; try { @@ -363,39 +271,26 @@ export class GBCoreService implements IGBCoreService { await azureDeployer.updateBotProxy( instance.botId, instance.botId, - `${proxyAddress}/api/messages/${instance.botId}`, + `${proxyAddress}/api/messages/${instance.botId}` ); } } catch (error) { - if (error.parent.code === 'ELOGIN') { - const group = GBConfigService.get('CLOUD_GROUP'); - const serverName = GBConfigService.get('STORAGE_SERVER').split( - '.database.windows.net', - )[0]; - await azureDeployer.openStorageFirewall(group, serverName); - } 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. Try setting STORAGE_SYNC to true in .env file. Error: ${ - error.message - }.`, - ); - } else { - logger.info( - `Storage is empty. After collecting storage structure from all .gbapps it will get synced.`, - ); - } - } 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( - `Cannot connect to operating storage: ${error.message}.`, + `Operating storage is out of sync or there is a storage connection error. + Try setting STORAGE_SYNC to true in .env file. Error: ${error.message}.` ); + } else { + logger.info(`Storage is empty. After collecting storage structure from all .gbapps it will get synced.`); } + } else { + throw new Error(`Cannot connect to operating storage: ${error.message}.`); } } + return instances; } @@ -406,16 +301,13 @@ export class GBCoreService implements IGBCoreService { * @param bootInstance * @param core */ - public async ensureInstances( - instances: GuaribasInstance[], - bootInstance: any, - core: GBCoreService, - ) { + public async ensureInstances(instances: GuaribasInstance[], bootInstance: any, core: GBCoreService) { if (!instances) { const saveInstance = new GuaribasInstance(bootInstance); await saveInstance.save(); instances = await core.loadInstances(); } + return instances; } @@ -431,7 +323,7 @@ export class GBCoreService implements IGBCoreService { GBSecurityPackage, GBKBPackage, GBCustomerSatisfactionPackage, - GBWhatsappPackage, + GBWhatsappPackage ].forEach(e => { logger.info(`Loading sys package: ${e.name}...`); const p = Object.create(e.prototype) as IGBPackage; @@ -443,37 +335,33 @@ export class GBCoreService implements IGBCoreService { const password = GBConfigService.get('ADMIN_PASS'); if (!GBAdminService.StrongRegex.test(password)) { throw new Error( - 'Please, define a really strong password in ADMIN_PASS environment variable before running the server.', + 'Please, define a really strong password in ADMIN_PASS environment variable before running the server.' ); } } - public async createBootInstance( - core: GBCoreService, - azureDeployer: AzureDeployerService, - proxyAddress: string, - ) { - let bootInstance: IGBInstance; + public async createBootInstance(core: GBCoreService, azureDeployer: AzureDeployerService, proxyAddress: string) { + let instance: IGBInstance; try { - await core.initDatabase(); + await core.initStorage(); } catch (error) { - logger.info( - `Deploying cognitive infrastructure (on the cloud / on premises)...`, - ); + logger.info(`Deploying cognitive infrastructure (on the cloud / on premises)...`); try { - bootInstance = await azureDeployer.deployFarm(proxyAddress); + instance = await azureDeployer.deployFarm(proxyAddress); } catch (error) { logger.warn( - 'In case of error, please cleanup any infrastructure objects created during this procedure and .env before running again.', + 'In case of error, please cleanup any infrastructure objects ' + + 'created during this procedure and .env before running again.' ); throw error; } - core.writeEnv(bootInstance); + core.writeEnv(instance); logger.info(`File .env written, starting General Bots...`); GBConfigService.init(); - await core.initDatabase(); + await core.initStorage(); + + return instance; } - return bootInstance; } public openBrowserInDevelopment() { @@ -482,4 +370,94 @@ export class GBCoreService implements IGBCoreService { } } + /** + * 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)' + */ + private createTableQueryOverride(tableName, attributes, options): string { + let sql: string = this.createTableQuery.apply(this.queryGenerator, [tableName, attributes, options]); + const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/; + const matches = re1.exec(sql); + if (matches) { + const table = matches[1]; + const re2 = /PRIMARY\s+KEY\s+\(\[[^\]]*\](?:,\s*\[[^\]]*\])*\)/; + sql = sql.replace( + re2, + (match: string, ...args: any[]): string => { + return 'CONSTRAINT [' + table + '_pk] ' + match; + } + ); + const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g; + const re4 = /\[([^\]]*)\]/g; + sql = sql.replace( + re3, + (match: string, ...args: any[]): string => { + const fkcols = args[0]; + let fkname = table; + let matches = re4.exec(fkcols); + while (matches != null) { + fkname += '_' + matches[1]; + matches = re4.exec(fkcols); + } + return 'CONSTRAINT [' + fkname + '_fk] FOREIGN KEY (' + fkcols + ')'; + } + ); + } + 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' + */ + private changeColumnQueryOverride(tableName, attributes): string { + let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [tableName, attributes]); + const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/; + const matches = re1.exec(sql); + if (matches) { + const table = matches[1]; + const re2 = /(ADD\s+)?CONSTRAINT\s+\[([^\]]*)\]\s+FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g; + const re3 = /\[([^\]]*)\]/g; + sql = sql.replace( + re2, + (match: string, ...args: any[]): string => { + const fkcols = args[2]; + let fkname = table; + let matches = re3.exec(fkcols); + while (matches != null) { + fkname += '_' + matches[1]; + matches = re3.exec(fkcols); + } + return (args[0] ? args[0] : '') + 'CONSTRAINT [' + fkname + '_fk] FOREIGN KEY (' + fkcols + ')'; + } + ); + } + return sql; + } + + /** + * Opens storage firewall. + * + * @param azureDeployer Infrastructure Deployer instance. + */ + private async openStorageFrontier(deployer: AzureDeployerService) { + const group = GBConfigService.get('CLOUD_GROUP'); + const serverName = GBConfigService.get('STORAGE_SERVER').split('.database.windows.net')[0]; + await deployer.openStorageFirewall(group, serverName); + } } diff --git a/packages/core.gbapp/services/GBDeployer.ts b/packages/core.gbapp/services/GBDeployer.ts index 3eca1b852..1d0b15de6 100644 --- a/packages/core.gbapp/services/GBDeployer.ts +++ b/packages/core.gbapp/services/GBDeployer.ts @@ -84,7 +84,7 @@ export class GBDeployer { public deployPackages( core: IGBCoreService, server: any, - appPackages: IGBPackage[], + appPackages: IGBPackage[] ) { const _this = this; return new Promise( @@ -93,7 +93,6 @@ export class GBDeployer { const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH'); let paths = [GBDeployer.deployFolder]; if (additionalPath) { - paths = paths.concat(additionalPath.toLowerCase().split(';')); } const botPackages = new Array(); @@ -124,7 +123,7 @@ export class GBDeployer { } logger.info( - `Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...`, + `Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...` ); paths.forEach(e => { logger.info(`Looking in: ${e}...`); @@ -199,20 +198,22 @@ export class GBDeployer { server.use('/themes/' + filenameOnly, express.static(filename)); logger.info( `Theme (.gbtheme) assets accessible at: ${'/themes/' + - filenameOnly}.`, + filenameOnly}.` ); /** Knowledge base for bots. */ } else if (Path.extname(filename) === '.gbkb') { server.use( '/kb/' + filenameOnly + '/subjects', - express.static(UrlJoin(filename, 'subjects')), + express.static(UrlJoin(filename, 'subjects')) ); logger.info( - `KB (.gbkb) assets accessible at: ${'/kb/' + filenameOnly}.`, + `KB (.gbkb) assets accessible at: ${'/kb/' + filenameOnly}.` ); } else if (Path.extname(filename) === '.gbui') { // Already Handled + } else if (Path.extname(filename) === '.gbdialog') { + // Already Handled } else { /** Unknown package format. */ const err = new Error(`Package type not handled: ${filename}.`); @@ -231,7 +232,7 @@ export class GBDeployer { .done(function(result) { if (botPackages.length === 0) { logger.info( - 'No external packages to load, please use ADDITIONAL_DEPLOY_PATH to point to a .gbai package folder.', + 'No external packages to load, please use ADDITIONAL_DEPLOY_PATH to point to a .gbai package folder.' ); } else { logger.info(`Package deployment done.`); @@ -239,7 +240,7 @@ export class GBDeployer { resolve(); }); }); - }, + } ); } @@ -252,24 +253,22 @@ export class GBDeployer { const packageName = Path.basename(localPath); const instance = await this.importer.importIfNotExistsBotPackage( packageName, - localPath, + localPath ); return instance; } public async deployPackageToStorage( instanceId: number, - packageName: string, + packageName: string ): Promise { return GuaribasPackage.create({ packageName: packageName, - instanceId: instanceId, + instanceId: instanceId }); } - public deployScriptToStorage(instanceId: number, localPath: string) { - - } + public deployScriptToStorage(instanceId: number, localPath: string) {} public deployTheme(localPath: string) { // DISABLED: Until completed, "/ui/public". @@ -283,7 +282,7 @@ export class GBDeployer { // }) } - public async deployPackageFromLocalPath(localPath: string) { + public async deployPackageFromLocalPath(min: IGBInstance, localPath: string) { const packageType = Path.extname(localPath); switch (packageType) { @@ -303,7 +302,7 @@ export class GBDeployer { case '.gbdialog': const vm = new GBVMService(); - return service.deployKb(this.core, this, localPath); + return vm.loadJS(localPath, min, this.core, this, localPath); default: const err = GBError.create( @@ -316,7 +315,7 @@ export class GBDeployer { public async undeployPackageFromLocalPath( instance: IGBInstance, - localPath: string, + localPath: string ) { const packageType = Path.extname(localPath); const packageName = Path.basename(localPath); @@ -339,9 +338,12 @@ export class GBDeployer { case '.gbui': break; + case '.gbdialog': + break; + default: const err = GBError.create( - `GuaribasBusinessError: Unknown package type: ${packageType}.`, + `GuaribasBusinessError: Unknown package type: ${packageType}.` ); Promise.reject(err); break; @@ -353,11 +355,11 @@ export class GBDeployer { instance.searchKey, instance.searchHost, instance.searchIndex, - instance.searchIndexer, + instance.searchIndexer ); const connectionString = GBDeployer.getConnectionStringFromInstance( - instance, + instance ); const dsName = 'gb'; @@ -375,7 +377,7 @@ export class GBDeployer { dsName, 'GuaribasQuestion', 'azuresql', - connectionString, + connectionString ); try { @@ -388,35 +390,17 @@ export class GBDeployer { } await search.createIndex( AzureDeployerService.getKBSearchSchema(instance.searchIndex), - dsName, + dsName ); } public async getPackageByName( instanceId: number, - packageName: string, + packageName: string ): Promise { const where = { packageName: packageName, instanceId: instanceId }; return GuaribasPackage.findOne({ - where: where, + where: where }); } - - /** - * - * Hot deploy processing. - * - */ - public async scanBootPackage() { - const deployFolder = 'packages'; - const bootPackage = GBConfigService.get('BOOT_PACKAGE'); - - if (bootPackage === 'none') { - return Promise.resolve(true); - } else { - return this.deployPackageFromLocalPath( - UrlJoin(deployFolder, bootPackage), - ); - } - } } diff --git a/packages/core.gbapp/services/GBImporterService.ts b/packages/core.gbapp/services/GBImporterService.ts index 1ad9b0e60..04bf1208a 100644 --- a/packages/core.gbapp/services/GBImporterService.ts +++ b/packages/core.gbapp/services/GBImporterService.ts @@ -57,14 +57,12 @@ export class GBImporter { const packageJson = JSON.parse( fs.readFileSync(UrlJoin(localPath, 'package.json'), 'utf8') ); - const botId = packageJson.botId; - const instance = await this.core.loadInstance(botId); if (instance) { - return Promise.resolve(instance); + return instance; } else { - return this.createInstanceInternal(packageName, localPath, packageJson); + return await this.createInstanceInternal(packageName, localPath, packageJson); } } @@ -83,7 +81,6 @@ export class GBImporter { packageJson = {...packageJson, ...settings, ...servicesJson}; GuaribasInstance.create(packageJson).then((instance: IGBInstance) => { - const service = new SecService(); // TODO: service.importSecurityFile(localPath, instance) diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index d99361af8..fded27abe 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -88,7 +88,7 @@ export class GBMinService { core: IGBCoreService, conversationalService: IGBConversationalService, adminService: IGBAdminService, - deployer: GBDeployer, + deployer: GBDeployer ) { this.core = core; this.conversationalService = conversationalService; @@ -110,14 +110,14 @@ export class GBMinService { public async buildMin( server: any, appPackages: IGBPackage[], - instances: GuaribasInstance[], + instances: GuaribasInstance[] ): Promise { // Serves default UI on root address '/'. const uiPackage = 'default.gbui'; server.use( '/', - express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')), + express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')) ); Promise.all( @@ -152,8 +152,8 @@ export class GBMinService { speechToken: speechToken, conversationId: webchatToken.conversationId, authenticatorTenant: instance.authenticatorTenant, - authenticatorClientId: instance.authenticatorClientId, - }), + authenticatorClientId: instance.authenticatorClientId + }) ); } else { const error = `Instance not found: ${botId}.`; @@ -166,7 +166,7 @@ export class GBMinService { // Build bot adapter. const { min, adapter, conversationState } = await this.buildBotAdapter( - instance, + instance ); // Call the loadBot context.activity for all packages. @@ -184,11 +184,11 @@ export class GBMinService { conversationState, min, instance, - appPackages, + appPackages ); }); logger.info( - `GeneralBots(${instance.engineName}) listening on: ${url}.`, + `GeneralBots(${instance.engineName}) listening on: ${url}.` ); // Serves individual URL for each bot user interface. @@ -196,12 +196,12 @@ export class GBMinService { const uiUrl = `/${instance.botId}`; server.use( uiUrl, - express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')), + express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')) ); logger.info(`Bot UI ${uiPackage} accessible at: ${uiUrl}.`); const state = `${instance.instanceId}${Math.floor( - Math.random() * 1000000000, + Math.random() * 1000000000 )}`; // Clients get redirected here in order to create an OAuth authorize url and redirect them to AAD. @@ -211,7 +211,7 @@ export class GBMinService { let authorizationUrl = UrlJoin( min.instance.authenticatorAuthorityHostUrl, min.instance.authenticatorTenant, - '/oauth2/authorize', + '/oauth2/authorize' ); authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${ min.instance.authenticatorClientId @@ -229,7 +229,7 @@ export class GBMinService { server.get(`/${min.instance.botId}/token`, async (req, res) => { const state = await min.adminService.getValue( min.instance.instanceId, - 'AntiCSRFAttackState', + 'AntiCSRFAttackState' ); if (req.query.state !== state) { @@ -242,8 +242,8 @@ export class GBMinService { const authenticationContext = new AuthenticationContext( UrlJoin( min.instance.authenticatorAuthorityHostUrl, - min.instance.authenticatorTenant, - ), + min.instance.authenticatorTenant + ) ); const resource = 'https://graph.microsoft.com'; @@ -263,27 +263,27 @@ export class GBMinService { await this.adminService.setValue( instance.instanceId, 'refreshToken', - token.refreshToken, + token.refreshToken ); await this.adminService.setValue( instance.instanceId, 'accessToken', - token.accessToken, + token.accessToken ); await this.adminService.setValue( instance.instanceId, 'expiresOn', - token.expiresOn.toString(), + token.expiresOn.toString() ); await this.adminService.setValue( instance.instanceId, 'AntiCSRFAttackState', - null, + null ); res.redirect(min.instance.botEndpoint); } - }, + } ); }); @@ -301,7 +301,7 @@ export class GBMinService { // } // ) // next() - }), + }) ); } @@ -316,8 +316,8 @@ export class GBMinService { url: 'https://directline.botframework.com/v3/directline/tokens/generate', method: 'POST', headers: { - Authorization: `Bearer ${instance.webchatKey}`, - }, + Authorization: `Bearer ${instance.webchatKey}` + } }; try { @@ -344,8 +344,8 @@ export class GBMinService { url: 'https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken', method: 'POST', headers: { - 'Ocp-Apim-Subscription-Key': instance.speechKey, - }, + 'Ocp-Apim-Subscription-Key': instance.speechKey + } }; try { @@ -359,7 +359,7 @@ export class GBMinService { private async buildBotAdapter(instance: any) { const adapter = new BotFrameworkAdapter({ appId: instance.marketplaceId, - appPassword: instance.marketplacePassword, + appPassword: instance.marketplacePassword }); const storage = new MemoryStorage(); @@ -395,7 +395,7 @@ export class GBMinService { GBKBPackage, GBAnalyticsPackage, GBCustomerSatisfactionPackage, - GBWhatsappPackage, + GBWhatsappPackage ].forEach(sysPackage => { const p = Object.create(sysPackage.prototype) as IGBPackage; p.loadBot(min); @@ -406,12 +406,12 @@ export class GBMinService { p.channel.received(req, res); }); } - }, this); + }, this); appPackages.forEach(e => { e.sysPackages = sysPackages; e.loadBot(min); - }, this); + }, this); } /** @@ -424,7 +424,7 @@ export class GBMinService { conversationState: ConversationState, min: any, instance: any, - appPackages: any[], + appPackages: any[] ) { return adapter.processActivity(req, res, async context => { const state = conversationState.get(context); @@ -439,7 +439,7 @@ export class GBMinService { instanceId: instance.instanceId, botId: instance.botId, theme: instance.theme ? instance.theme : 'default.gbtheme', - secret: instance.webchatKey, + secret: instance.webchatKey }); user.loaded = true; user.subjects = []; @@ -449,7 +449,7 @@ export class GBMinService { logger.info( `User>: ${context.activity.text} (${context.activity.type}, ${ context.activity.name - }, ${context.activity.channelId}, {context.activity.value})`, + }, ${context.activity.channelId}, {context.activity.value})` ); if ( context.activity.type === 'conversationUpdate' && @@ -478,7 +478,7 @@ export class GBMinService { // Checks for /menu JSON signature. } else if (context.activity.text.startsWith('{"title"')) { await step.beginDialog('/menu', { - data: JSON.parse(context.activity.text), + data: JSON.parse(context.activity.text) }); // Otherwise, continue to the active dialog in the stack. @@ -487,7 +487,7 @@ export class GBMinService { await step.continueDialog(); } else { await step.beginDialog('/answer', { - query: context.activity.text, + query: context.activity.text }); } } @@ -504,18 +504,18 @@ export class GBMinService { await step.beginDialog('/menu'); } else if (context.activity.name === 'giveFeedback') { await step.beginDialog('/feedback', { - fromMenu: true, + fromMenu: true }); } else if (context.activity.name === 'showFAQ') { await step.beginDialog('/faq'); } else if (context.activity.name === 'answerEvent') { await step.beginDialog('/answerEvent', { questionId: (context.activity as any).data, - fromFaq: true, + fromFaq: true }); } else if (context.activity.name === 'quality') { await step.beginDialog('/quality', { - score: (context.activity as any).data, + score: (context.activity as any).data }); } else if (context.activity.name === 'updateToken') { const token = (context.activity as any).data; @@ -529,7 +529,7 @@ export class GBMinService { logger.error(msg); await step.context.sendActivity( - Messages[step.context.activity.locale].very_sorry_about_error, + Messages[step.context.activity.locale].very_sorry_about_error ); await step.beginDialog('/ask', { isReturning: true }); } diff --git a/packages/core.gbapp/services/GBVMService.ts b/packages/core.gbapp/services/GBVMService.ts index 42da3fc22..9e81174f9 100644 --- a/packages/core.gbapp/services/GBVMService.ts +++ b/packages/core.gbapp/services/GBVMService.ts @@ -53,7 +53,6 @@ const UrlJoin = require('url-join'); */ export class GBVMService implements IGBCoreService { - private script = new vm.Script(); public async loadJS( @@ -63,18 +62,15 @@ export class GBVMService implements IGBCoreService { deployer: GBDeployer, localPath: string ): Promise { - - const code = fs.readFileSync(UrlJoin(localPath, filename), 'utf8'); - const sandbox = new DialogClass(min); - + localPath = UrlJoin(localPath, 'chat.dialog.js'); + const code: string = fs.readFileSync(UrlJoin(localPath, filename), 'utf8'); + const sandbox: DialogClass = new DialogClass(min); const context = vm.createContext(sandbox); - this.script.runInContext(context); + + this.script.runInContext(code, context); console.log(util.inspect(sandbox)); - await deployer.deployScriptToStorage( - min.instanceId, - filename - ); + await deployer.deployScriptToStorage(min.instanceId, filename); logger.info(`[GBVMService] Finished loading of ${filename}`); } } diff --git a/packages/default.gbdialog/chat.dialog.vbs b/packages/default.gbdialog/bot.vbs similarity index 97% rename from packages/default.gbdialog/chat.dialog.vbs rename to packages/default.gbdialog/bot.vbs index 62979cb39..226e1358b 100644 --- a/packages/default.gbdialog/chat.dialog.vbs +++ b/packages/default.gbdialog/bot.vbs @@ -36,7 +36,7 @@ function ICanSendEmails() bot.say ("Please, what's your e-mail address?") email = bot.expectEmail() - bot.sendMail (email, "Olá", "I'm sending a General Bots VBA e-mail.") + bot.sendMail (email, "Hello", "I'm sending a General Bots VBA e-mail.") end function diff --git a/packages/default.gbdialog/chat.dialog.js b/packages/default.gbdialog/bot.vbs.js similarity index 100% rename from packages/default.gbdialog/chat.dialog.js rename to packages/default.gbdialog/bot.vbs.js diff --git a/packages/kb.gbapp/dialogs/AskDialog.ts b/packages/kb.gbapp/dialogs/AskDialog.ts index 3b2f17741..6a804bbf3 100644 --- a/packages/kb.gbapp/dialogs/AskDialog.ts +++ b/packages/kb.gbapp/dialogs/AskDialog.ts @@ -59,10 +59,10 @@ export class AskDialog extends IGBDialog { min.dialogs.add( new WaterfallDialog('/answerEvent', [ async step => { - if (step.options && step.options['questionId']) { + if (step.options && step.options.questionId) { const question = await service.getQuestionById( min.instance.instanceId, - step.options['questionId'] + step.options.questionId ); const answer = await service.getAnswerById( min.instance.instanceId, @@ -84,7 +84,7 @@ export class AskDialog extends IGBDialog { new WaterfallDialog('/answer', [ async step => { const user = await min.userProfile.get(step.context, {}); - let text = step.options['query']; + let text = step.options.query; if (!text) { throw new Error(`/answer being called with no args query text.`); } @@ -97,9 +97,9 @@ export class AskDialog extends IGBDialog { // Handle extra text from FAQ. - if (step.options && step.options['query']) { - text = step.options['query']; - } else if (step.options && step.options['fromFaq']) { + if (step.options && step.options.query) { + text = step.options.query; + } else if (step.options && step.options.fromFaq) { await step.context.sendActivity(Messages[locale].going_answer); } @@ -212,9 +212,9 @@ export class AskDialog extends IGBDialog { // Three forms of asking. - if (step.options && step.options['firstTime'] ) { + if (step.options && step.options.firstTime) { text = Messages[locale].ask_first_time; - } else if (step.options && step.options['isReturning'] ) { + } else if (step.options && step.options.isReturning) { text = Messages[locale].anything_else; } else if (user.subjects.length > 0) { text = Messages[locale].which_question; diff --git a/packages/kb.gbapp/dialogs/MenuDialog.ts b/packages/kb.gbapp/dialogs/MenuDialog.ts index fece9fc0f..5ae6441fe 100644 --- a/packages/kb.gbapp/dialogs/MenuDialog.ts +++ b/packages/kb.gbapp/dialogs/MenuDialog.ts @@ -63,7 +63,7 @@ export class MenuDialog extends IGBDialog { const locale = step.context.activity.locale; let rootSubjectId = null; - if (step.options && step.options['data']) { + if (step.options && step.options.data) { const subject = step.result.data; // If there is a shortcut specified as subject destination, go there. diff --git a/packages/kb.gbapp/services/KBService.ts b/packages/kb.gbapp/services/KBService.ts index e94b96341..614d1876f 100644 --- a/packages/kb.gbapp/services/KBService.ts +++ b/packages/kb.gbapp/services/KBService.ts @@ -30,11 +30,15 @@ | | \*****************************************************************************/ +/** + * @fileoverview Knowledge base services and logic. + */ + const logger = require('../../../src/logger'); const Path = require('path'); const Fs = require('fs'); -const promise = require('bluebird'); -const parse = promise.promisify(require('csv-parse')); + +const parse = require('bluebird').promisify(require('csv-parse')); const UrlJoin = require('url-join'); const marked = require('marked'); const path = require('path'); @@ -69,6 +73,7 @@ export class KBService { subjects.forEach(subject => { out.push(subject.title); }); + return out.join(', '); } @@ -77,6 +82,7 @@ export class KBService { subjects.forEach(subject => { out.push(subject.internalId); }); + return out.join(' '); } @@ -125,8 +131,10 @@ export class KBService { answerId: question.answerId } }); + return Promise.resolve({ question: question, answer: answer }); } + return Promise.resolve(null); } @@ -163,9 +171,9 @@ export class KBService { query = `${query} ${text}`; } } - // TODO: Filter by instance. what = `${what}&$filter=instanceId eq ${instanceId}` + query = `${query}&$filter=instanceId eq ${instance.instanceId}`; try { - if (instance.searchKey && GBConfigService.get('STORAGE_DIALECT') == 'mssql') { + if (instance.searchKey && GBConfigService.get('STORAGE_DIALECT') === 'mssql') { const service = new AzureSearch( instance.searchKey, instance.searchHost, @@ -203,6 +211,7 @@ export class KBService { parentId: number ): Promise { const where = { parentSubjectId: parentId, instanceId: instanceId }; + return GuaribasSubject.findAll({ where: where }); @@ -210,7 +219,7 @@ export class KBService { public async getFaqBySubjectArray(from: string, subjects: any): Promise { const where = { - from: from, subject1: null, subject2: null, subject3: null, subject4:null + from: from, subject1: null, subject2: null, subject3: null, subject4: null }; if (subjects) { @@ -230,6 +239,7 @@ export class KBService { where.subject4 = subjects[3].internalId; } } + return await GuaribasQuestion.findAll({ where: where }); @@ -250,6 +260,7 @@ export class KBService { let lastAnswer: GuaribasAnswer; const data = await parse(file, opts); + return asyncPromise.eachSeries(data, async line => { // Extracts values from columns in the current line. @@ -262,7 +273,7 @@ export class KBService { // Skips the first line. - if (!(subjectsText === 'subjects' && from == 'from')) { + if (!(subjectsText === 'subjects' && from === 'from')) { let format = '.txt'; // Extracts answer from external media if any. @@ -281,8 +292,10 @@ export class KBService { // Processes subjects hierarchy splitting by dots. const subjectArray = subjectsText.split('.'); - let subject1: string, subject2: string, subject3: string, - subject4: string; + let subject1: string; + let subject2: string; + let subject3: string; + let subject4: string; let indexer = 0; subjectArray.forEach(element => { diff --git a/src/app.ts b/src/app.ts index c0d174e01..1f87ab6ad 100644 --- a/src/app.ts +++ b/src/app.ts @@ -75,8 +75,8 @@ export class GBServer { server.use( bodyParser.urlencoded({ // to support URL-encoded bodies - extended: true, - }), + extended: true + }) ); let bootInstance: IGBInstance; @@ -93,40 +93,33 @@ export class GBServer { // Ensures cloud / on-premises infrastructure is setup. logger.info(`Establishing a development local proxy (ngrok)...`); - const proxyAddress = await core.ensureProxy(port); + const proxyAddress: string = await core.ensureProxy(port); logger.info(`Deploying packages...`); - const deployer = new GBDeployer(core, new GBImporter(core)); - const azureDeployer = new AzureDeployerService(deployer); - const adminService = new GBAdminService(core); - const conversationalService = new GBConversationalService(core); - bootInstance = await core.createBootInstance( - core, - azureDeployer, - proxyAddress, - ); + const importer: GBImporter = new GBImporter(core); + const deployer: GBDeployer = new GBDeployer(core, importer); + const azureDeployer: AzureDeployerService = new AzureDeployerService(deployer); + const adminService: GBAdminService = new GBAdminService(core); + const conversationalService: GBConversationalService = new GBConversationalService(core); core.ensureAdminIsSecured(); - core.loadSysPackages(core); + + const bootInstance = await core.createBootInstance(core, azureDeployer, proxyAddress); + await core.checkStorage(azureDeployer); + await core.loadSysPackages(core); await deployer.deployPackages(core, server, appPackages); logger.info(`Publishing instances...`); - let instances: GuaribasInstance[] = await core.loadAllInstances( - core, - azureDeployer, - proxyAddress, - ); - instances = await core.ensureInstances( - instances, - bootInstance, - core - ); - - const minService = new GBMinService( - core, - conversationalService, - adminService, - deployer, - ); + const packageInstance = await importer.importIfNotExistsBotPackage('boot.gbot', 'packages/boot.gbot'); + const fullInstance = Object.assign(packageInstance, bootInstance); + await core.saveInstance(fullInstance); + let instances: GuaribasInstance[] = await core.loadAllInstances(core, azureDeployer, proxyAddress); + instances = await core.ensureInstances(instances, bootInstance, core); + + // Install default VBA module. + + deployer.deployPackageFromLocalPath(instances[0], 'packages/default.gbdialog'); + + const minService: GBMinService = new GBMinService(core, conversationalService, adminService, deployer); await minService.buildMin(server, appPackages, instances); logger.info(`The Bot Server is in RUNNING mode...`); @@ -141,7 +134,6 @@ export class GBServer { }); } } - // First line to run. diff --git a/src/logger.ts b/src/logger.ts index 6a77c89a8..ee201a788 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -30,6 +30,10 @@ | | \*****************************************************************************/ +/** + * @fileoverview Logging support. + */ + const { createLogger, format, transports } = require('winston'); const config = { diff --git a/tslint.json b/tslint.json index 99e6879ad..1b26d002b 100644 --- a/tslint.json +++ b/tslint.json @@ -15,6 +15,8 @@ ], "jsRules": {}, "rules": { + "no-var-requires":false, + "typedef":false, "variable-name": false, "no-parameter-properties": false, "no-reserved-keywords": false, @@ -34,6 +36,6 @@ "export-name":false, "no-relative-imports": false, "no-backbone-get-set-outside-model": false, - "max-line-length": [true,{"limit":80,"ignore-pattern":"^\\s+\\*"}] + "max-line-length": [true,{"limit":120,"ignore-pattern":"^\\s+\\*"}] } }