fix(core): Bot boot logic being fixed.

This commit is contained in:
Rodrigo Rodriguez (pragmatismo.io) 2018-11-27 22:56:11 -02:00
parent c1db8be0c0
commit 1761e06061
23 changed files with 461 additions and 493 deletions

View file

@ -1,8 +1,9 @@
{ {
"trailingComma": "all", "trailingComma": "none",
"tabWidth": 2, "tabWidth": 2,
"printWidth": 80, "printWidth": 120,
"arrowParens": "avoid", "arrowParens": "avoid",
"semi": true, "semi": true,
"singleQuote": true "singleQuote": true
} }

34
package-lock.json generated
View file

@ -2966,9 +2966,9 @@
} }
}, },
"bluebird": { "bluebird": {
"version": "3.5.2", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz",
"integrity": "sha1-G+CQjgVKdRdUVJwnBInBUF1KsVo=" "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw=="
}, },
"body-parser": { "body-parser": {
"version": "1.18.3", "version": "1.18.3",
@ -7105,13 +7105,11 @@
}, },
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -7124,18 +7122,15 @@
}, },
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -7238,8 +7233,7 @@
}, },
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -7249,7 +7243,6 @@
"is-fullwidth-code-point": { "is-fullwidth-code-point": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -7262,20 +7255,17 @@
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
}, },
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.2.4", "version": "2.2.4",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.1", "safe-buffer": "^5.1.1",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -7292,7 +7282,6 @@
"mkdirp": { "mkdirp": {
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@ -7365,8 +7354,7 @@
}, },
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -7376,7 +7364,6 @@
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -7482,7 +7469,6 @@
"string-width": { "string-width": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",

View file

@ -58,6 +58,7 @@
"azure-arm-search": "^1.3.0-preview", "azure-arm-search": "^1.3.0-preview",
"azure-arm-sql": "5.6.0", "azure-arm-sql": "5.6.0",
"azure-arm-website": "5.7.0", "azure-arm-website": "5.7.0",
"bluebird": "^3.5.3",
"body-parser": "1.18.3", "body-parser": "1.18.3",
"botbuilder": "^4.1.5", "botbuilder": "^4.1.5",
"botbuilder-ai": "^4.1.5", "botbuilder-ai": "^4.1.5",

View file

@ -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 packageName = text.split(' ')[1];
const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH'); const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH');
await deployer.deployPackageFromLocalPath( await deployer.deployPackageFromLocalPath(min,
UrlJoin(additionalPath, packageName) UrlJoin(additionalPath, packageName)
); );
} }
/** /**
@ -119,11 +119,11 @@ export class AdminDialog extends IGBDialog {
await AdminDialog.createFarmCommand(text, deployer); await AdminDialog.createFarmCommand(text, deployer);
await step.replaceDialog('/admin', { firstRun: false }); await step.replaceDialog('/admin', { firstRun: false });
} else if (cmdName === 'deployPackage') { } else if (cmdName === 'deployPackage') {
await AdminDialog.deployPackageCommand(text, deployer); await AdminDialog.deployPackageCommand(min, text, deployer);
await step.replaceDialog('/admin', { firstRun: false }); await step.replaceDialog('/admin', { firstRun: false });
} else if (cmdName === 'redeployPackage') { } else if (cmdName === 'redeployPackage') {
await AdminDialog.undeployPackageCommand(text, min); await AdminDialog.undeployPackageCommand(text, min);
await AdminDialog.deployPackageCommand(text, deployer); await AdminDialog.deployPackageCommand(min, text, deployer);
await step.replaceDialog('/admin', { firstRun: false }); await step.replaceDialog('/admin', { firstRun: false });
} else if (cmdName === 'undeployPackage') { } else if (cmdName === 'undeployPackage') {
await AdminDialog.undeployPackageCommand(text, min); await AdminDialog.undeployPackageCommand(text, min);

View file

@ -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"
}

View file

@ -0,0 +1,3 @@
{
"groups": [{}]
}

View file

@ -0,0 +1,2 @@
{
}

View file

@ -0,0 +1,6 @@
{
"enabledAdmin": "true",
"searchScore": ".15",
"nlpScore": ".15",
"nlpVsSearch": ".4"
}

View file

@ -46,7 +46,7 @@ export class DialogClass {
this.min = min; this.min = min;
} }
public async expectMessage(text: string): Promise<any> { public async hear(text: string): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.min.dialogs.add( this.min.dialogs.add(
new WaterfallDialog('/vmExpect', [ 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( this.min.dialogs.add(
new WaterfallDialog('/vmSend', [ new WaterfallDialog('/vmSend', [
async step => { async step => {

View file

@ -31,18 +31,15 @@
\*****************************************************************************/ \*****************************************************************************/
/** /**
* @fileoverview General Bots server core. * @fileoverview Conversation handling and external service calls.
*/ */
'use strict'; 'use strict';
const logger = require('../../../src/logger'); const logger = require('../../../src/logger');
import { any } from 'bluebird';
import { MessageFactory } from 'botbuilder'; import { MessageFactory } from 'botbuilder';
import { LuisRecognizer } from 'botbuilder-ai'; import { LuisRecognizer } from 'botbuilder-ai';
import { IGBConversationalService } from 'botlib'; import { GBMinInstance, IGBConversationalService } from 'botlib';
import { GBMinInstance } from 'botlib';
import { AzureText } from 'pragmatismo-io-framework'; import { AzureText } from 'pragmatismo-io-framework';
import { Messages } from '../strings'; import { Messages } from '../strings';
import { GBCoreService } from './GBCoreService'; import { GBCoreService } from './GBCoreService';
@ -70,33 +67,27 @@ export class GBConversationalService implements IGBConversationalService {
msg.value = value; msg.value = value;
msg.type = 'event'; msg.type = 'event';
msg.name = name; msg.name = name;
return step.context.sendActivity(msg); return step.context.sendActivity(msg);
} }
} }
public async sendSms( public async sendSms(min: GBMinInstance, mobile: string, text: string): Promise<any> {
min: GBMinInstance, return new Promise(
mobile: string, (resolve: any, reject: any): any => {
text: string const nexmo = new Nexmo({
): Promise<any> { apiKey: min.instance.smsKey,
return new Promise((resolve: any, reject: any): any => { apiSecret: min.instance.smsSecret
const nexmo = new Nexmo({ });
apiKey: min.instance.smsKey, nexmo.message.sendSms(min.instance.smsServiceNumber, mobile, text, (err, data) => {
apiSecret: min.instance.smsSecret
});
nexmo.message.sendSms(
min.instance.smsServiceNumber,
mobile,
text,
(err, data) => {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
resolve(data); resolve(data);
} }
} });
); }
}); );
} }
public async routeNLP(step: any, min: GBMinInstance, text: string): Promise<boolean> { public async routeNLP(step: any, min: GBMinInstance, text: string): Promise<boolean> {
@ -112,15 +103,17 @@ export class GBConversationalService implements IGBConversationalService {
try { try {
nlp = await model.recognize(step.context); nlp = await model.recognize(step.context);
} catch (error) { } catch (error) {
if (error.statusCode == 404) { if (error.statusCode === 404) {
logger.warn ('NLP application still not publish and there are no other options for answering.'); logger.warn('NLP application still not publish and there are no other options for answering.');
return Promise.resolve(false); return Promise.resolve(false);
} else { } else {
const msg = `Error calling NLP server, check if you have a published model and assigned keys on the service. Error: ${ const msg = `Error calling NLP, check if you have a published model and assigned keys. Error: ${
error.statusCode ? error.statusCode : '' error.statusCode ? error.statusCode : ''
} ${error.message}`; } ${error.message}`;
return Promise.reject(new Error(msg)); }
return Promise.reject(new Error(msg));
}
} }
// Resolves intents returned from LUIS. // Resolves intents returned from LUIS.
@ -128,37 +121,31 @@ export class GBConversationalService implements IGBConversationalService {
const topIntent = LuisRecognizer.topIntent(nlp); const topIntent = LuisRecognizer.topIntent(nlp);
if (topIntent) { if (topIntent) {
const intent = topIntent; const intent = topIntent;
const entity = const entity = nlp.entities && nlp.entities.length > 0 ? nlp.entities[0].entity.toUpperCase() : null;
nlp.entities && nlp.entities.length > 0
? nlp.entities[0].entity.toUpperCase()
: null;
if (intent === 'None') { if (intent === 'None') {
return Promise.resolve(false); return Promise.resolve(false);
} }
logger.info('NLP called:' + intent + ', ' + entity); logger.info(`NLP called: ${intent}, ${entity}`);
try { try {
await step.replace('/' + intent, nlp.entities); await step.replace(`/${intent}`, nlp.entities);
return Promise.resolve(true); return Promise.resolve(true);
} catch (error) { } catch (error) {
const msg = `Error finding dialog associated to NLP event: ${intent}: ${ const msg = `Error finding dialog associated to NLP event: ${intent}: ${error.message}`;
error.message
}`;
return Promise.reject(new Error(msg)); return Promise.reject(new Error(msg));
} }
} }
return Promise.resolve(false); return Promise.resolve(false);
} }
public async checkLanguage(step, min, text) { public async checkLanguage(step, min, text) {
const locale = await AzureText.getLocale( const locale = await AzureText.getLocale(min.instance.textAnalyticsKey, min.instance.textAnalyticsEndpoint, text);
min.instance.textAnalyticsKey, if (locale !== step.context.activity.locale.split('-')[0]) {
min.instance.textAnalyticsEndpoint,
text
);
if (locale != step.context.activity.locale.split('-')[0]) {
switch (locale) { switch (locale) {
case 'pt': case 'pt':
step.context.activity.locale = 'pt-BR'; step.context.activity.locale = 'pt-BR';

View file

@ -39,17 +39,18 @@
import { IGBCoreService, IGBInstance, IGBPackage } from 'botlib'; import { IGBCoreService, IGBInstance, IGBPackage } from 'botlib';
import * as fs from 'fs'; import * as fs from 'fs';
import { Sequelize } from 'sequelize-typescript'; import { Sequelize } from 'sequelize-typescript';
import { GBAdminPackage } from '../../admin.gbapp/index';
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService'; 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 { GuaribasInstance } from '../models/GBModel';
import { GBConfigService } from './GBConfigService'; import { GBConfigService } from './GBConfigService';
import { AzureDeployerService } from 'packages/azuredeployer.gbapp/services/AzureDeployerService'; import { GBImporter } from './GBImporterService';
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';
const logger = require('../../../src/logger'); const logger = require('../../../src/logger');
const opn = require('opn'); const opn = require('opn');
@ -76,11 +77,7 @@ export class GBCoreService implements IGBCoreService {
/** /**
* Custom create table query. * Custom create table query.
*/ */
private createTableQuery: ( private createTableQuery: (tableName: string, attributes: any, options: any) => string;
tableName: string,
attributes: any,
options: any,
) => string;
/** /**
* Custom change column query. * Custom change column query.
@ -102,78 +99,79 @@ export class GBCoreService implements IGBCoreService {
/** /**
* Gets database config and connect to storage. * Gets database config and connect to storage.
*/ */
public async initDatabase(): Promise<any> {
return new Promise(
(resolve: any, reject: any): any => {
try {
this.dialect = GBConfigService.get('STORAGE_DIALECT');
let host: string | undefined; public async initStorage(): Promise<any> {
let database: string | undefined; this.dialect = GBConfigService.get('STORAGE_DIALECT');
let username: string | undefined;
let password: string | undefined;
let storage: string | undefined;
if (this.dialect === 'mssql') { let host: string | undefined;
host = GBConfigService.get('STORAGE_SERVER'); let database: string | undefined;
database = GBConfigService.get('STORAGE_NAME'); let username: string | undefined;
username = GBConfigService.get('STORAGE_USERNAME'); let password: string | undefined;
password = GBConfigService.get('STORAGE_PASSWORD'); let storage: string | undefined;
} else if (this.dialect === 'sqlite') {
storage = GBConfigService.get('STORAGE_STORAGE'); if (this.dialect === 'mssql') {
} else { host = GBConfigService.get('STORAGE_SERVER');
reject(`Unknown dialect: ${this.dialect}.`); 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);
} }
: false;
const logging: any = const encrypt: boolean = GBConfigService.get('STORAGE_ENCRYPT') === 'true';
GBConfigService.get('STORAGE_LOGGING') === 'true'
? (str: string): void => {
logger.info(str);
}
: false;
const encrypt: boolean = this.sequelize = new Sequelize({
GBConfigService.get('STORAGE_ENCRYPT') === 'true'; host: host,
database: database,
this.sequelize = new Sequelize({ username: username,
host: host, password: password,
database: database, logging: logging,
username: username, operatorsAliases: false,
password: password, dialect: this.dialect,
logging: logging, storage: storage,
operatorsAliases: false, dialectOptions: {
dialect: this.dialect, encrypt: encrypt
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);
}
resolve();
} catch (error) {
reject(error);
}
}, },
); 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() { public async syncDatabaseStructure() {
@ -181,9 +179,10 @@ export class GBCoreService implements IGBCoreService {
const alter = GBConfigService.get('STORAGE_SYNC_ALTER') === 'true'; const alter = GBConfigService.get('STORAGE_SYNC_ALTER') === 'true';
const force = GBConfigService.get('STORAGE_SYNC_FORCE') === 'true'; const force = GBConfigService.get('STORAGE_SYNC_FORCE') === 'true';
logger.info('Syncing database...'); logger.info('Syncing database...');
return this.sequelize.sync({ return this.sequelize.sync({
alter: alter, alter: alter,
force: force, force: force
}); });
} else { } else {
const msg = 'Database synchronization is disabled.'; const msg = 'Database synchronization is disabled.';
@ -203,6 +202,7 @@ export class GBCoreService implements IGBCoreService {
*/ */
public async loadInstanceById(instanceId: string): Promise<IGBInstance> { public async loadInstanceById(instanceId: string): Promise<IGBInstance> {
const options = { where: { instanceId: instanceId } }; const options = { where: { instanceId: instanceId } };
return GuaribasInstance.findOne(options); return GuaribasInstance.findOne(options);
} }
@ -211,63 +211,182 @@ export class GBCoreService implements IGBCoreService {
*/ */
public async loadInstance(botId: string): Promise<IGBInstance> { public async loadInstance(botId: string): Promise<IGBInstance> {
const options = { where: {} }; const options = { where: {} };
options.where = { botId: botId };
if (botId !== '[default]') { return await GuaribasInstance.findOne(options);
options.where = { botId: botId };
}
return GuaribasInstance.findOne(options);
} }
public async writeEnv(instance: IGBInstance) { public async writeEnv(instance: IGBInstance) {
const env = const env = `ADDITIONAL_DEPLOY_PATH=
`ADDITIONAL_DEPLOY_PATH=\n` + ADMIN_PASS=${instance.adminPass}
`ADMIN_PASS=${instance.adminPass}\n` + CLOUD_SUBSCRIPTIONID=${instance.cloudSubscriptionId}
`CLOUD_SUBSCRIPTIONID=${instance.cloudSubscriptionId}\n` + CLOUD_LOCATION=${instance.cloudLocation}
`CLOUD_LOCATION=${instance.cloudLocation}\n` + CLOUD_GROUP=${instance.botId}
`CLOUD_GROUP=${instance.botId}\n` + CLOUD_USERNAME=${instance.cloudUsername}
`CLOUD_USERNAME=${instance.cloudUsername}\n` + CLOUD_PASSWORD=${instance.cloudPassword}
`CLOUD_PASSWORD=${instance.cloudPassword}\n` + MARKETPLACE_ID=${instance.marketplaceId}
`MARKETPLACE_ID=${instance.marketplaceId}\n` + MARKETPLACE_SECRET=${instance.marketplacePassword}
`MARKETPLACE_SECRET=${instance.marketplacePassword}\n` + NLP_AUTHORING_KEY=${instance.nlpAuthoringKey}
`NLP_AUTHORING_KEY=${instance.nlpAuthoringKey}\n` + STORAGE_DIALECT=${instance.storageDialect}
`STORAGE_DIALECT=${instance.storageDialect}\n` + STORAGE_SERVER=${instance.storageServer}.database.windows.net
`STORAGE_SERVER=${instance.storageServer}.database.windows.net\n` + STORAGE_NAME=${instance.storageName}
`STORAGE_NAME=${instance.storageName}\n` + STORAGE_USERNAME=${instance.storageUsername}
`STORAGE_USERNAME=${instance.storageUsername}\n` + STORAGE_PASSWORD=${instance.storagePassword}
`STORAGE_PASSWORD=${instance.storagePassword}\n` + STORAGE_SYNC=true`;
`STORAGE_SYNC=true\n`;
fs.writeFileSync('.env', env); fs.writeFileSync('.env', env);
} }
public async ensureProxy(port): Promise<string> { public async ensureProxy(port): Promise<string> {
let proxyAddress: string;
const ngrok = require('ngrok'); const ngrok = require('ngrok');
return await ngrok.connect({ port: port }); return await ngrok.connect({ port: port });
} }
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);
return await instance.save();
}
/**
* Loads all bot instances from object storage, if it's formatted.
*
* @param core
* @param azureDeployer
* @param proxyAddress
*/
public async loadAllInstances(core: GBCoreService, azureDeployer: AzureDeployerService, proxyAddress: string) {
logger.info(`Loading instances from storage...`);
let instances: GuaribasInstance[];
try {
instances = await core.loadInstances();
const instance = instances[0];
if (process.env.NODE_ENV === 'development') {
logger.info(`Updating bot endpoint to local reverse proxy (ngrok)...`);
await azureDeployer.updateBotProxy(
instance.botId,
instance.botId,
`${proxyAddress}/api/messages/${instance.botId}`
);
}
} catch (error) {
// 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 {
throw new Error(`Cannot connect to operating storage: ${error.message}.`);
}
}
return instances;
}
/**
* If instances is undefined here it's because storage has been formatted.
* Load all instances from .gbot found on deploy package directory.
* @param instances
* @param bootInstance
* @param core
*/
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;
}
public loadSysPackages(core: GBCoreService) {
// NOTE: if there is any code before this line a semicolon
// will be necessary before this line.
// Loads all system packages.
[
GBAdminPackage,
GBAnalyticsPackage,
GBCorePackage,
GBSecurityPackage,
GBKBPackage,
GBCustomerSatisfactionPackage,
GBWhatsappPackage
].forEach(e => {
logger.info(`Loading sys package: ${e.name}...`);
const p = Object.create(e.prototype) as IGBPackage;
p.loadPackage(core, core.sequelize);
});
}
public ensureAdminIsSecured() {
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.'
);
}
}
public async createBootInstance(core: GBCoreService, azureDeployer: AzureDeployerService, proxyAddress: string) {
let instance: IGBInstance;
try {
await core.initStorage();
} catch (error) {
logger.info(`Deploying cognitive infrastructure (on the cloud / on premises)...`);
try {
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.'
);
throw error;
}
core.writeEnv(instance);
logger.info(`File .env written, starting General Bots...`);
GBConfigService.init();
await core.initStorage();
return instance;
}
}
public openBrowserInDevelopment() {
if (process.env.NODE_ENV === 'development') {
opn('http://localhost:4242');
}
}
/** /**
* SQL: * SQL:
* *
* // let sql: string = '' + * // let sql: string = '' +
* // 'IF OBJECT_ID(\'[UserGroup]\', \'U\') IS NULL\n' + * // 'IF OBJECT_ID(\'[UserGroup]\', \'U\') IS NULL' +
* // 'CREATE TABLE [UserGroup] (\n' + * // 'CREATE TABLE [UserGroup] (' +
* // ' [id] INTEGER NOT NULL IDENTITY(1,1),\n' + * // ' [id] INTEGER NOT NULL IDENTITY(1,1),' +
* // ' [userId] INTEGER NULL,\n' + * // ' [userId] INTEGER NULL,' +
* // ' [groupId] INTEGER NULL,\n' + * // ' [groupId] INTEGER NULL,' +
* // ' [instanceId] INTEGER NULL,\n' + * // ' [instanceId] INTEGER NULL,' +
* // ' PRIMARY KEY ([id1], [id2]),\n' + * // ' PRIMARY KEY ([id1], [id2]),' +
* // ' FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,\n' + * // ' 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,\n' + * // ' FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId1]) ON DELETE NO ACTION,' +
* // ' FOREIGN KEY ([instanceId]) REFERENCES [Instance] ([instanceId]) ON DELETE NO ACTION)' * // ' FOREIGN KEY ([instanceId]) REFERENCES [Instance] ([instanceId]) ON DELETE NO ACTION)'
*/ */
private createTableQueryOverride(tableName, attributes, options): string { private createTableQueryOverride(tableName, attributes, options): string {
let sql: string = this.createTableQuery.apply(this.queryGenerator, [ let sql: string = this.createTableQuery.apply(this.queryGenerator, [tableName, attributes, options]);
tableName,
attributes,
options,
]);
const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/; const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/;
const matches = re1.exec(sql); const matches = re1.exec(sql);
if (matches) { if (matches) {
@ -277,7 +396,7 @@ export class GBCoreService implements IGBCoreService {
re2, re2,
(match: string, ...args: any[]): string => { (match: string, ...args: any[]): string => {
return 'CONSTRAINT [' + table + '_pk] ' + match; return 'CONSTRAINT [' + table + '_pk] ' + match;
}, }
); );
const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g; const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g;
const re4 = /\[([^\]]*)\]/g; const re4 = /\[([^\]]*)\]/g;
@ -292,7 +411,7 @@ export class GBCoreService implements IGBCoreService {
matches = re4.exec(fkcols); matches = re4.exec(fkcols);
} }
return 'CONSTRAINT [' + fkname + '_fk] FOREIGN KEY (' + fkcols + ')'; return 'CONSTRAINT [' + fkname + '_fk] FOREIGN KEY (' + fkcols + ')';
}, }
); );
} }
return sql; return sql;
@ -301,16 +420,13 @@ export class GBCoreService implements IGBCoreService {
/** /**
* SQL: * SQL:
* let sql = '' + * let sql = '' +
* 'ALTER TABLE [UserGroup]\n' + * 'ALTER TABLE [UserGroup]' +
* ' ADD CONSTRAINT [invalid1] FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,\n' + * ' 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, \n' + * ' 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\n' * ' CONSTRAINT [invalid3] FOREIGN KEY ([instanceId1]) REFERENCES [Instance] ([instanceId1]) ON DELETE NO ACTION'
*/ */
private changeColumnQueryOverride(tableName, attributes): string { private changeColumnQueryOverride(tableName, attributes): string {
let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [ let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [tableName, attributes]);
tableName,
attributes,
]);
const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/; const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/;
const matches = re1.exec(sql); const matches = re1.exec(sql);
if (matches) { if (matches) {
@ -327,159 +443,21 @@ export class GBCoreService implements IGBCoreService {
fkname += '_' + matches[1]; fkname += '_' + matches[1];
matches = re3.exec(fkcols); matches = re3.exec(fkcols);
} }
return ( return (args[0] ? args[0] : '') + 'CONSTRAINT [' + fkname + '_fk] FOREIGN KEY (' + fkcols + ')';
(args[0] ? args[0] : '') + }
'CONSTRAINT [' +
fkname +
'_fk] FOREIGN KEY (' +
fkcols +
')'
);
},
); );
} }
return sql; return sql;
} }
/** /**
* Loads all bot instances from object storage, if it's formatted. * Opens storage firewall.
* *
* @param core * @param azureDeployer Infrastructure Deployer instance.
* @param azureDeployer
* @param proxyAddress
*/ */
public async loadAllInstances( private async openStorageFrontier(deployer: AzureDeployerService) {
core: GBCoreService, const group = GBConfigService.get('CLOUD_GROUP');
azureDeployer: AzureDeployerService, const serverName = GBConfigService.get('STORAGE_SERVER').split('.database.windows.net')[0];
proxyAddress: string, await deployer.openStorageFirewall(group, serverName);
) {
logger.info(`Loading instances from storage...`);
let instances: GuaribasInstance[];
try {
instances = await core.loadInstances();
const instance = instances[0];
if (process.env.NODE_ENV === 'development') {
logger.info(`Updating bot endpoint to local reverse proxy (ngrok)...`);
await azureDeployer.updateBotProxy(
instance.botId,
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 {
throw new Error(
`Cannot connect to operating storage: ${error.message}.`,
);
}
}
}
return instances;
} }
/**
* If instances is undefined here it's because storage has been formatted.
* Load all instances from .gbot found on deploy package directory.
* @param instances
* @param bootInstance
* @param core
*/
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;
}
public loadSysPackages(core: GBCoreService) {
// NOTE: if there is any code before this line a semicolon
// will be necessary before this line.
// Loads all system packages.
[
GBAdminPackage,
GBAnalyticsPackage,
GBCorePackage,
GBSecurityPackage,
GBKBPackage,
GBCustomerSatisfactionPackage,
GBWhatsappPackage,
].forEach(e => {
logger.info(`Loading sys package: ${e.name}...`);
const p = Object.create(e.prototype) as IGBPackage;
p.loadPackage(core, core.sequelize);
});
}
public ensureAdminIsSecured() {
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.',
);
}
}
public async createBootInstance(
core: GBCoreService,
azureDeployer: AzureDeployerService,
proxyAddress: string,
) {
let bootInstance: IGBInstance;
try {
await core.initDatabase();
} catch (error) {
logger.info(
`Deploying cognitive infrastructure (on the cloud / on premises)...`,
);
try {
bootInstance = 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.',
);
throw error;
}
core.writeEnv(bootInstance);
logger.info(`File .env written, starting General Bots...`);
GBConfigService.init();
await core.initDatabase();
}
return bootInstance;
}
public openBrowserInDevelopment() {
if (process.env.NODE_ENV === 'development') {
opn('http://localhost:4242');
}
}
} }

View file

@ -84,7 +84,7 @@ export class GBDeployer {
public deployPackages( public deployPackages(
core: IGBCoreService, core: IGBCoreService,
server: any, server: any,
appPackages: IGBPackage[], appPackages: IGBPackage[]
) { ) {
const _this = this; const _this = this;
return new Promise( return new Promise(
@ -93,7 +93,6 @@ export class GBDeployer {
const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH'); const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH');
let paths = [GBDeployer.deployFolder]; let paths = [GBDeployer.deployFolder];
if (additionalPath) { if (additionalPath) {
paths = paths.concat(additionalPath.toLowerCase().split(';')); paths = paths.concat(additionalPath.toLowerCase().split(';'));
} }
const botPackages = new Array<string>(); const botPackages = new Array<string>();
@ -124,7 +123,7 @@ export class GBDeployer {
} }
logger.info( logger.info(
`Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...`, `Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...`
); );
paths.forEach(e => { paths.forEach(e => {
logger.info(`Looking in: ${e}...`); logger.info(`Looking in: ${e}...`);
@ -199,20 +198,22 @@ export class GBDeployer {
server.use('/themes/' + filenameOnly, express.static(filename)); server.use('/themes/' + filenameOnly, express.static(filename));
logger.info( logger.info(
`Theme (.gbtheme) assets accessible at: ${'/themes/' + `Theme (.gbtheme) assets accessible at: ${'/themes/' +
filenameOnly}.`, filenameOnly}.`
); );
/** Knowledge base for bots. */ /** Knowledge base for bots. */
} else if (Path.extname(filename) === '.gbkb') { } else if (Path.extname(filename) === '.gbkb') {
server.use( server.use(
'/kb/' + filenameOnly + '/subjects', '/kb/' + filenameOnly + '/subjects',
express.static(UrlJoin(filename, 'subjects')), express.static(UrlJoin(filename, 'subjects'))
); );
logger.info( logger.info(
`KB (.gbkb) assets accessible at: ${'/kb/' + filenameOnly}.`, `KB (.gbkb) assets accessible at: ${'/kb/' + filenameOnly}.`
); );
} else if (Path.extname(filename) === '.gbui') { } else if (Path.extname(filename) === '.gbui') {
// Already Handled // Already Handled
} else if (Path.extname(filename) === '.gbdialog') {
// Already Handled
} else { } else {
/** Unknown package format. */ /** Unknown package format. */
const err = new Error(`Package type not handled: ${filename}.`); const err = new Error(`Package type not handled: ${filename}.`);
@ -231,7 +232,7 @@ export class GBDeployer {
.done(function(result) { .done(function(result) {
if (botPackages.length === 0) { if (botPackages.length === 0) {
logger.info( 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 { } else {
logger.info(`Package deployment done.`); logger.info(`Package deployment done.`);
@ -239,7 +240,7 @@ export class GBDeployer {
resolve(); resolve();
}); });
}); });
}, }
); );
} }
@ -252,24 +253,22 @@ export class GBDeployer {
const packageName = Path.basename(localPath); const packageName = Path.basename(localPath);
const instance = await this.importer.importIfNotExistsBotPackage( const instance = await this.importer.importIfNotExistsBotPackage(
packageName, packageName,
localPath, localPath
); );
return instance; return instance;
} }
public async deployPackageToStorage( public async deployPackageToStorage(
instanceId: number, instanceId: number,
packageName: string, packageName: string
): Promise<GuaribasPackage> { ): Promise<GuaribasPackage> {
return GuaribasPackage.create({ return GuaribasPackage.create({
packageName: packageName, packageName: packageName,
instanceId: instanceId, instanceId: instanceId
}); });
} }
public deployScriptToStorage(instanceId: number, localPath: string) { public deployScriptToStorage(instanceId: number, localPath: string) {}
}
public deployTheme(localPath: string) { public deployTheme(localPath: string) {
// DISABLED: Until completed, "/ui/public". // 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); const packageType = Path.extname(localPath);
switch (packageType) { switch (packageType) {
@ -303,7 +302,7 @@ export class GBDeployer {
case '.gbdialog': case '.gbdialog':
const vm = new GBVMService(); const vm = new GBVMService();
return service.deployKb(this.core, this, localPath); return vm.loadJS(localPath, min, this.core, this, localPath);
default: default:
const err = GBError.create( const err = GBError.create(
@ -316,7 +315,7 @@ export class GBDeployer {
public async undeployPackageFromLocalPath( public async undeployPackageFromLocalPath(
instance: IGBInstance, instance: IGBInstance,
localPath: string, localPath: string
) { ) {
const packageType = Path.extname(localPath); const packageType = Path.extname(localPath);
const packageName = Path.basename(localPath); const packageName = Path.basename(localPath);
@ -339,9 +338,12 @@ export class GBDeployer {
case '.gbui': case '.gbui':
break; break;
case '.gbdialog':
break;
default: default:
const err = GBError.create( const err = GBError.create(
`GuaribasBusinessError: Unknown package type: ${packageType}.`, `GuaribasBusinessError: Unknown package type: ${packageType}.`
); );
Promise.reject(err); Promise.reject(err);
break; break;
@ -353,11 +355,11 @@ export class GBDeployer {
instance.searchKey, instance.searchKey,
instance.searchHost, instance.searchHost,
instance.searchIndex, instance.searchIndex,
instance.searchIndexer, instance.searchIndexer
); );
const connectionString = GBDeployer.getConnectionStringFromInstance( const connectionString = GBDeployer.getConnectionStringFromInstance(
instance, instance
); );
const dsName = 'gb'; const dsName = 'gb';
@ -375,7 +377,7 @@ export class GBDeployer {
dsName, dsName,
'GuaribasQuestion', 'GuaribasQuestion',
'azuresql', 'azuresql',
connectionString, connectionString
); );
try { try {
@ -388,35 +390,17 @@ export class GBDeployer {
} }
await search.createIndex( await search.createIndex(
AzureDeployerService.getKBSearchSchema(instance.searchIndex), AzureDeployerService.getKBSearchSchema(instance.searchIndex),
dsName, dsName
); );
} }
public async getPackageByName( public async getPackageByName(
instanceId: number, instanceId: number,
packageName: string, packageName: string
): Promise<GuaribasPackage> { ): Promise<GuaribasPackage> {
const where = { packageName: packageName, instanceId: instanceId }; const where = { packageName: packageName, instanceId: instanceId };
return GuaribasPackage.findOne({ 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),
);
}
}
} }

View file

@ -57,14 +57,12 @@ export class GBImporter {
const packageJson = JSON.parse( const packageJson = JSON.parse(
fs.readFileSync(UrlJoin(localPath, 'package.json'), 'utf8') fs.readFileSync(UrlJoin(localPath, 'package.json'), 'utf8')
); );
const botId = packageJson.botId; const botId = packageJson.botId;
const instance = await this.core.loadInstance(botId); const instance = await this.core.loadInstance(botId);
if (instance) { if (instance) {
return Promise.resolve(instance); return instance;
} else { } 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}; packageJson = {...packageJson, ...settings, ...servicesJson};
GuaribasInstance.create(packageJson).then((instance: IGBInstance) => { GuaribasInstance.create(packageJson).then((instance: IGBInstance) => {
const service = new SecService(); const service = new SecService();
// TODO: service.importSecurityFile(localPath, instance) // TODO: service.importSecurityFile(localPath, instance)

View file

@ -88,7 +88,7 @@ export class GBMinService {
core: IGBCoreService, core: IGBCoreService,
conversationalService: IGBConversationalService, conversationalService: IGBConversationalService,
adminService: IGBAdminService, adminService: IGBAdminService,
deployer: GBDeployer, deployer: GBDeployer
) { ) {
this.core = core; this.core = core;
this.conversationalService = conversationalService; this.conversationalService = conversationalService;
@ -110,14 +110,14 @@ export class GBMinService {
public async buildMin( public async buildMin(
server: any, server: any,
appPackages: IGBPackage[], appPackages: IGBPackage[],
instances: GuaribasInstance[], instances: GuaribasInstance[]
): Promise<GBMinInstance> { ): Promise<GBMinInstance> {
// Serves default UI on root address '/'. // Serves default UI on root address '/'.
const uiPackage = 'default.gbui'; const uiPackage = 'default.gbui';
server.use( server.use(
'/', '/',
express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')), express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build'))
); );
Promise.all( Promise.all(
@ -152,8 +152,8 @@ export class GBMinService {
speechToken: speechToken, speechToken: speechToken,
conversationId: webchatToken.conversationId, conversationId: webchatToken.conversationId,
authenticatorTenant: instance.authenticatorTenant, authenticatorTenant: instance.authenticatorTenant,
authenticatorClientId: instance.authenticatorClientId, authenticatorClientId: instance.authenticatorClientId
}), })
); );
} else { } else {
const error = `Instance not found: ${botId}.`; const error = `Instance not found: ${botId}.`;
@ -166,7 +166,7 @@ export class GBMinService {
// Build bot adapter. // Build bot adapter.
const { min, adapter, conversationState } = await this.buildBotAdapter( const { min, adapter, conversationState } = await this.buildBotAdapter(
instance, instance
); );
// Call the loadBot context.activity for all packages. // Call the loadBot context.activity for all packages.
@ -184,11 +184,11 @@ export class GBMinService {
conversationState, conversationState,
min, min,
instance, instance,
appPackages, appPackages
); );
}); });
logger.info( logger.info(
`GeneralBots(${instance.engineName}) listening on: ${url}.`, `GeneralBots(${instance.engineName}) listening on: ${url}.`
); );
// Serves individual URL for each bot user interface. // Serves individual URL for each bot user interface.
@ -196,12 +196,12 @@ export class GBMinService {
const uiUrl = `/${instance.botId}`; const uiUrl = `/${instance.botId}`;
server.use( server.use(
uiUrl, uiUrl,
express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')), express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build'))
); );
logger.info(`Bot UI ${uiPackage} accessible at: ${uiUrl}.`); logger.info(`Bot UI ${uiPackage} accessible at: ${uiUrl}.`);
const state = `${instance.instanceId}${Math.floor( 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. // 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( let authorizationUrl = UrlJoin(
min.instance.authenticatorAuthorityHostUrl, min.instance.authenticatorAuthorityHostUrl,
min.instance.authenticatorTenant, min.instance.authenticatorTenant,
'/oauth2/authorize', '/oauth2/authorize'
); );
authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${ authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${
min.instance.authenticatorClientId min.instance.authenticatorClientId
@ -229,7 +229,7 @@ export class GBMinService {
server.get(`/${min.instance.botId}/token`, async (req, res) => { server.get(`/${min.instance.botId}/token`, async (req, res) => {
const state = await min.adminService.getValue( const state = await min.adminService.getValue(
min.instance.instanceId, min.instance.instanceId,
'AntiCSRFAttackState', 'AntiCSRFAttackState'
); );
if (req.query.state !== state) { if (req.query.state !== state) {
@ -242,8 +242,8 @@ export class GBMinService {
const authenticationContext = new AuthenticationContext( const authenticationContext = new AuthenticationContext(
UrlJoin( UrlJoin(
min.instance.authenticatorAuthorityHostUrl, min.instance.authenticatorAuthorityHostUrl,
min.instance.authenticatorTenant, min.instance.authenticatorTenant
), )
); );
const resource = 'https://graph.microsoft.com'; const resource = 'https://graph.microsoft.com';
@ -263,27 +263,27 @@ export class GBMinService {
await this.adminService.setValue( await this.adminService.setValue(
instance.instanceId, instance.instanceId,
'refreshToken', 'refreshToken',
token.refreshToken, token.refreshToken
); );
await this.adminService.setValue( await this.adminService.setValue(
instance.instanceId, instance.instanceId,
'accessToken', 'accessToken',
token.accessToken, token.accessToken
); );
await this.adminService.setValue( await this.adminService.setValue(
instance.instanceId, instance.instanceId,
'expiresOn', 'expiresOn',
token.expiresOn.toString(), token.expiresOn.toString()
); );
await this.adminService.setValue( await this.adminService.setValue(
instance.instanceId, instance.instanceId,
'AntiCSRFAttackState', 'AntiCSRFAttackState',
null, null
); );
res.redirect(min.instance.botEndpoint); res.redirect(min.instance.botEndpoint);
} }
}, }
); );
}); });
@ -301,7 +301,7 @@ export class GBMinService {
// } // }
// ) // )
// next() // next()
}), })
); );
} }
@ -316,8 +316,8 @@ export class GBMinService {
url: 'https://directline.botframework.com/v3/directline/tokens/generate', url: 'https://directline.botframework.com/v3/directline/tokens/generate',
method: 'POST', method: 'POST',
headers: { headers: {
Authorization: `Bearer ${instance.webchatKey}`, Authorization: `Bearer ${instance.webchatKey}`
}, }
}; };
try { try {
@ -344,8 +344,8 @@ export class GBMinService {
url: 'https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken', url: 'https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken',
method: 'POST', method: 'POST',
headers: { headers: {
'Ocp-Apim-Subscription-Key': instance.speechKey, 'Ocp-Apim-Subscription-Key': instance.speechKey
}, }
}; };
try { try {
@ -359,7 +359,7 @@ export class GBMinService {
private async buildBotAdapter(instance: any) { private async buildBotAdapter(instance: any) {
const adapter = new BotFrameworkAdapter({ const adapter = new BotFrameworkAdapter({
appId: instance.marketplaceId, appId: instance.marketplaceId,
appPassword: instance.marketplacePassword, appPassword: instance.marketplacePassword
}); });
const storage = new MemoryStorage(); const storage = new MemoryStorage();
@ -395,7 +395,7 @@ export class GBMinService {
GBKBPackage, GBKBPackage,
GBAnalyticsPackage, GBAnalyticsPackage,
GBCustomerSatisfactionPackage, GBCustomerSatisfactionPackage,
GBWhatsappPackage, GBWhatsappPackage
].forEach(sysPackage => { ].forEach(sysPackage => {
const p = Object.create(sysPackage.prototype) as IGBPackage; const p = Object.create(sysPackage.prototype) as IGBPackage;
p.loadBot(min); p.loadBot(min);
@ -406,12 +406,12 @@ export class GBMinService {
p.channel.received(req, res); p.channel.received(req, res);
}); });
} }
}, this); }, this);
appPackages.forEach(e => { appPackages.forEach(e => {
e.sysPackages = sysPackages; e.sysPackages = sysPackages;
e.loadBot(min); e.loadBot(min);
}, this); }, this);
} }
/** /**
@ -424,7 +424,7 @@ export class GBMinService {
conversationState: ConversationState, conversationState: ConversationState,
min: any, min: any,
instance: any, instance: any,
appPackages: any[], appPackages: any[]
) { ) {
return adapter.processActivity(req, res, async context => { return adapter.processActivity(req, res, async context => {
const state = conversationState.get(context); const state = conversationState.get(context);
@ -439,7 +439,7 @@ export class GBMinService {
instanceId: instance.instanceId, instanceId: instance.instanceId,
botId: instance.botId, botId: instance.botId,
theme: instance.theme ? instance.theme : 'default.gbtheme', theme: instance.theme ? instance.theme : 'default.gbtheme',
secret: instance.webchatKey, secret: instance.webchatKey
}); });
user.loaded = true; user.loaded = true;
user.subjects = []; user.subjects = [];
@ -449,7 +449,7 @@ export class GBMinService {
logger.info( logger.info(
`User>: ${context.activity.text} (${context.activity.type}, ${ `User>: ${context.activity.text} (${context.activity.type}, ${
context.activity.name context.activity.name
}, ${context.activity.channelId}, {context.activity.value})`, }, ${context.activity.channelId}, {context.activity.value})`
); );
if ( if (
context.activity.type === 'conversationUpdate' && context.activity.type === 'conversationUpdate' &&
@ -478,7 +478,7 @@ export class GBMinService {
// Checks for /menu JSON signature. // Checks for /menu JSON signature.
} else if (context.activity.text.startsWith('{"title"')) { } else if (context.activity.text.startsWith('{"title"')) {
await step.beginDialog('/menu', { 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. // Otherwise, continue to the active dialog in the stack.
@ -487,7 +487,7 @@ export class GBMinService {
await step.continueDialog(); await step.continueDialog();
} else { } else {
await step.beginDialog('/answer', { await step.beginDialog('/answer', {
query: context.activity.text, query: context.activity.text
}); });
} }
} }
@ -504,18 +504,18 @@ export class GBMinService {
await step.beginDialog('/menu'); await step.beginDialog('/menu');
} else if (context.activity.name === 'giveFeedback') { } else if (context.activity.name === 'giveFeedback') {
await step.beginDialog('/feedback', { await step.beginDialog('/feedback', {
fromMenu: true, fromMenu: true
}); });
} else if (context.activity.name === 'showFAQ') { } else if (context.activity.name === 'showFAQ') {
await step.beginDialog('/faq'); await step.beginDialog('/faq');
} else if (context.activity.name === 'answerEvent') { } else if (context.activity.name === 'answerEvent') {
await step.beginDialog('/answerEvent', { await step.beginDialog('/answerEvent', {
questionId: (context.activity as any).data, questionId: (context.activity as any).data,
fromFaq: true, fromFaq: true
}); });
} else if (context.activity.name === 'quality') { } else if (context.activity.name === 'quality') {
await step.beginDialog('/quality', { await step.beginDialog('/quality', {
score: (context.activity as any).data, score: (context.activity as any).data
}); });
} else if (context.activity.name === 'updateToken') { } else if (context.activity.name === 'updateToken') {
const token = (context.activity as any).data; const token = (context.activity as any).data;
@ -529,7 +529,7 @@ export class GBMinService {
logger.error(msg); logger.error(msg);
await step.context.sendActivity( 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 }); await step.beginDialog('/ask', { isReturning: true });
} }

View file

@ -53,7 +53,6 @@ const UrlJoin = require('url-join');
*/ */
export class GBVMService implements IGBCoreService { export class GBVMService implements IGBCoreService {
private script = new vm.Script(); private script = new vm.Script();
public async loadJS( public async loadJS(
@ -63,18 +62,15 @@ export class GBVMService implements IGBCoreService {
deployer: GBDeployer, deployer: GBDeployer,
localPath: string localPath: string
): Promise<void> { ): Promise<void> {
localPath = UrlJoin(localPath, 'chat.dialog.js');
const code = fs.readFileSync(UrlJoin(localPath, filename), 'utf8'); const code: string = fs.readFileSync(UrlJoin(localPath, filename), 'utf8');
const sandbox = new DialogClass(min); const sandbox: DialogClass = new DialogClass(min);
const context = vm.createContext(sandbox); const context = vm.createContext(sandbox);
this.script.runInContext(context);
this.script.runInContext(code, context);
console.log(util.inspect(sandbox)); console.log(util.inspect(sandbox));
await deployer.deployScriptToStorage( await deployer.deployScriptToStorage(min.instanceId, filename);
min.instanceId,
filename
);
logger.info(`[GBVMService] Finished loading of ${filename}`); logger.info(`[GBVMService] Finished loading of ${filename}`);
} }
} }

View file

@ -36,7 +36,7 @@ function ICanSendEmails()
bot.say ("Please, what's your e-mail address?") bot.say ("Please, what's your e-mail address?")
email = bot.expectEmail() 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 end function

View file

@ -59,10 +59,10 @@ export class AskDialog extends IGBDialog {
min.dialogs.add( min.dialogs.add(
new WaterfallDialog('/answerEvent', [ new WaterfallDialog('/answerEvent', [
async step => { async step => {
if (step.options && step.options['questionId']) { if (step.options && step.options.questionId) {
const question = await service.getQuestionById( const question = await service.getQuestionById(
min.instance.instanceId, min.instance.instanceId,
step.options['questionId'] step.options.questionId
); );
const answer = await service.getAnswerById( const answer = await service.getAnswerById(
min.instance.instanceId, min.instance.instanceId,
@ -84,7 +84,7 @@ export class AskDialog extends IGBDialog {
new WaterfallDialog('/answer', [ new WaterfallDialog('/answer', [
async step => { async step => {
const user = await min.userProfile.get(step.context, {}); const user = await min.userProfile.get(step.context, {});
let text = step.options['query']; let text = step.options.query;
if (!text) { if (!text) {
throw new Error(`/answer being called with no args query 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. // Handle extra text from FAQ.
if (step.options && step.options['query']) { if (step.options && step.options.query) {
text = step.options['query']; text = step.options.query;
} else if (step.options && step.options['fromFaq']) { } else if (step.options && step.options.fromFaq) {
await step.context.sendActivity(Messages[locale].going_answer); await step.context.sendActivity(Messages[locale].going_answer);
} }
@ -212,9 +212,9 @@ export class AskDialog extends IGBDialog {
// Three forms of asking. // Three forms of asking.
if (step.options && step.options['firstTime'] ) { if (step.options && step.options.firstTime) {
text = Messages[locale].ask_first_time; 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; text = Messages[locale].anything_else;
} else if (user.subjects.length > 0) { } else if (user.subjects.length > 0) {
text = Messages[locale].which_question; text = Messages[locale].which_question;

View file

@ -63,7 +63,7 @@ export class MenuDialog extends IGBDialog {
const locale = step.context.activity.locale; const locale = step.context.activity.locale;
let rootSubjectId = null; let rootSubjectId = null;
if (step.options && step.options['data']) { if (step.options && step.options.data) {
const subject = step.result.data; const subject = step.result.data;
// If there is a shortcut specified as subject destination, go there. // If there is a shortcut specified as subject destination, go there.

View file

@ -30,11 +30,15 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
/**
* @fileoverview Knowledge base services and logic.
*/
const logger = require('../../../src/logger'); const logger = require('../../../src/logger');
const Path = require('path'); const Path = require('path');
const Fs = require('fs'); 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 UrlJoin = require('url-join');
const marked = require('marked'); const marked = require('marked');
const path = require('path'); const path = require('path');
@ -69,6 +73,7 @@ export class KBService {
subjects.forEach(subject => { subjects.forEach(subject => {
out.push(subject.title); out.push(subject.title);
}); });
return out.join(', '); return out.join(', ');
} }
@ -77,6 +82,7 @@ export class KBService {
subjects.forEach(subject => { subjects.forEach(subject => {
out.push(subject.internalId); out.push(subject.internalId);
}); });
return out.join(' '); return out.join(' ');
} }
@ -125,8 +131,10 @@ export class KBService {
answerId: question.answerId answerId: question.answerId
} }
}); });
return Promise.resolve({ question: question, answer: answer }); return Promise.resolve({ question: question, answer: answer });
} }
return Promise.resolve(null); return Promise.resolve(null);
} }
@ -163,9 +171,9 @@ export class KBService {
query = `${query} ${text}`; query = `${query} ${text}`;
} }
} }
// TODO: Filter by instance. what = `${what}&$filter=instanceId eq ${instanceId}` query = `${query}&$filter=instanceId eq ${instance.instanceId}`;
try { try {
if (instance.searchKey && GBConfigService.get('STORAGE_DIALECT') == 'mssql') { if (instance.searchKey && GBConfigService.get('STORAGE_DIALECT') === 'mssql') {
const service = new AzureSearch( const service = new AzureSearch(
instance.searchKey, instance.searchKey,
instance.searchHost, instance.searchHost,
@ -203,6 +211,7 @@ export class KBService {
parentId: number parentId: number
): Promise<GuaribasSubject[]> { ): Promise<GuaribasSubject[]> {
const where = { parentSubjectId: parentId, instanceId: instanceId }; const where = { parentSubjectId: parentId, instanceId: instanceId };
return GuaribasSubject.findAll({ return GuaribasSubject.findAll({
where: where where: where
}); });
@ -210,7 +219,7 @@ export class KBService {
public async getFaqBySubjectArray(from: string, subjects: any): Promise<GuaribasQuestion[]> { public async getFaqBySubjectArray(from: string, subjects: any): Promise<GuaribasQuestion[]> {
const where = { const where = {
from: from, subject1: null, subject2: null, subject3: null, subject4:null from: from, subject1: null, subject2: null, subject3: null, subject4: null
}; };
if (subjects) { if (subjects) {
@ -230,6 +239,7 @@ export class KBService {
where.subject4 = subjects[3].internalId; where.subject4 = subjects[3].internalId;
} }
} }
return await GuaribasQuestion.findAll({ return await GuaribasQuestion.findAll({
where: where where: where
}); });
@ -250,6 +260,7 @@ export class KBService {
let lastAnswer: GuaribasAnswer; let lastAnswer: GuaribasAnswer;
const data = await parse(file, opts); const data = await parse(file, opts);
return asyncPromise.eachSeries(data, async line => { return asyncPromise.eachSeries(data, async line => {
// Extracts values from columns in the current line. // Extracts values from columns in the current line.
@ -262,7 +273,7 @@ export class KBService {
// Skips the first line. // Skips the first line.
if (!(subjectsText === 'subjects' && from == 'from')) { if (!(subjectsText === 'subjects' && from === 'from')) {
let format = '.txt'; let format = '.txt';
// Extracts answer from external media if any. // Extracts answer from external media if any.
@ -281,8 +292,10 @@ export class KBService {
// Processes subjects hierarchy splitting by dots. // Processes subjects hierarchy splitting by dots.
const subjectArray = subjectsText.split('.'); const subjectArray = subjectsText.split('.');
let subject1: string, subject2: string, subject3: string, let subject1: string;
subject4: string; let subject2: string;
let subject3: string;
let subject4: string;
let indexer = 0; let indexer = 0;
subjectArray.forEach(element => { subjectArray.forEach(element => {

View file

@ -75,8 +75,8 @@ export class GBServer {
server.use( server.use(
bodyParser.urlencoded({ bodyParser.urlencoded({
// to support URL-encoded bodies // to support URL-encoded bodies
extended: true, extended: true
}), })
); );
let bootInstance: IGBInstance; let bootInstance: IGBInstance;
@ -93,40 +93,33 @@ export class GBServer {
// Ensures cloud / on-premises infrastructure is setup. // Ensures cloud / on-premises infrastructure is setup.
logger.info(`Establishing a development local proxy (ngrok)...`); 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...`); logger.info(`Deploying packages...`);
const deployer = new GBDeployer(core, new GBImporter(core)); const importer: GBImporter = new GBImporter(core);
const azureDeployer = new AzureDeployerService(deployer); const deployer: GBDeployer = new GBDeployer(core, importer);
const adminService = new GBAdminService(core); const azureDeployer: AzureDeployerService = new AzureDeployerService(deployer);
const conversationalService = new GBConversationalService(core); const adminService: GBAdminService = new GBAdminService(core);
bootInstance = await core.createBootInstance( const conversationalService: GBConversationalService = new GBConversationalService(core);
core,
azureDeployer,
proxyAddress,
);
core.ensureAdminIsSecured(); 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); await deployer.deployPackages(core, server, appPackages);
logger.info(`Publishing instances...`); logger.info(`Publishing instances...`);
let instances: GuaribasInstance[] = await core.loadAllInstances( const packageInstance = await importer.importIfNotExistsBotPackage('boot.gbot', 'packages/boot.gbot');
core, const fullInstance = Object.assign(packageInstance, bootInstance);
azureDeployer, await core.saveInstance(fullInstance);
proxyAddress, let instances: GuaribasInstance[] = await core.loadAllInstances(core, azureDeployer, proxyAddress);
); instances = await core.ensureInstances(instances, bootInstance, core);
instances = await core.ensureInstances(
instances,
bootInstance,
core
);
const minService = new GBMinService( // Install default VBA module.
core,
conversationalService, deployer.deployPackageFromLocalPath(instances[0], 'packages/default.gbdialog');
adminService,
deployer, const minService: GBMinService = new GBMinService(core, conversationalService, adminService, deployer);
);
await minService.buildMin(server, appPackages, instances); await minService.buildMin(server, appPackages, instances);
logger.info(`The Bot Server is in RUNNING mode...`); logger.info(`The Bot Server is in RUNNING mode...`);
@ -141,7 +134,6 @@ export class GBServer {
}); });
} }
} }
// First line to run. // First line to run.

View file

@ -30,6 +30,10 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
/**
* @fileoverview Logging support.
*/
const { createLogger, format, transports } = require('winston'); const { createLogger, format, transports } = require('winston');
const config = { const config = {

View file

@ -15,6 +15,8 @@
], ],
"jsRules": {}, "jsRules": {},
"rules": { "rules": {
"no-var-requires":false,
"typedef":false,
"variable-name": false, "variable-name": false,
"no-parameter-properties": false, "no-parameter-properties": false,
"no-reserved-keywords": false, "no-reserved-keywords": false,
@ -34,6 +36,6 @@
"export-name":false, "export-name":false,
"no-relative-imports": false, "no-relative-imports": false,
"no-backbone-get-set-outside-model": 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+\\*"}]
} }
} }