From 0ce8d48f09bc175501ba9c8ae075eb82a7dc6882 Mon Sep 17 00:00:00 2001 From: Rodrigo Rodriguez Date: Sun, 9 Sep 2018 14:39:37 -0300 Subject: [PATCH] New promises and compiling. --- deploy/admin.gbapp/dialogs/AdminDialog.ts | 243 +++--- .../services/AnalyticsService.ts | 48 +- .../services/ConsoleDirectLine.ts | 7 +- deploy/core.gbapp/dialogs/WelcomeDialog.ts | 6 + deploy/core.gbapp/dialogs/WhoAmIDialog.ts | 8 + .../services/GBConversationalService.ts | 79 +- deploy/core.gbapp/services/GBCoreService.ts | 249 +++--- deploy/core.gbapp/services/GBDeployer.ts | 152 ++-- deploy/core.gbapp/services/GBImporter.ts | 43 +- deploy/core.gbapp/services/GBMinService.ts | 418 +++++----- .../dialogs/FeedbackDialog.ts | 13 +- .../dialogs/QualityDialog.ts | 19 +- .../services/CSService.ts | 74 +- deploy/kb.gbapp/dialogs/AskDialog.ts | 6 + deploy/kb.gbapp/dialogs/FaqDialog.ts | 36 +- deploy/kb.gbapp/dialogs/MenuDialog.ts | 109 +-- deploy/kb.gbapp/services/KBService.ts | 711 +++++++++--------- deploy/security.gblib/services/SecService.ts | 65 +- .../services/WhatsappDirectLine.ts | 1 - package.json | 1 + src/app.ts | 39 +- 21 files changed, 1136 insertions(+), 1191 deletions(-) diff --git a/deploy/admin.gbapp/dialogs/AdminDialog.ts b/deploy/admin.gbapp/dialogs/AdminDialog.ts index 0eda361e..70d8ca53 100644 --- a/deploy/admin.gbapp/dialogs/AdminDialog.ts +++ b/deploy/admin.gbapp/dialogs/AdminDialog.ts @@ -19,7 +19,7 @@ | in the LICENSE file you have received along with this program. | | | | This program is distributed in the hope that it will be useful, | -| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| but WITHOUT ANY WARRANTY without even the implied warranty of | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | GNU Affero General Public License for more details. | | | @@ -30,157 +30,140 @@ | | \*****************************************************************************/ -"use strict"; +"use strict" -const UrlJoin = require("url-join"); - -import { AzureSearch } from "pragmatismo-io-framework"; -const { DialogSet, TextPrompt, NumberPrompt } = require('botbuilder-dialogs'); -const { createTextPrompt, createNumberPrompt } = require('botbuilder-prompts'); -import { GBMinInstance } from "botlib"; -import { IGBDialog } from "botlib"; -import { GBDeployer } from '../../core.gbapp/services/GBDeployer'; -import { GBImporter } from '../../core.gbapp/services/GBImporter'; -import { GBConfigService } from '../../core.gbapp/services/GBConfigService'; -import { KBService } from './../../kb.gbapp/services/KBService'; -import { BotAdapter } from "botbuilder"; +const UrlJoin = require("url-join") +import { AzureSearch } from "pragmatismo-io-framework" +import { GBMinInstance } from "botlib" +import { IGBDialog } from "botlib" +import { GBDeployer } from '../../core.gbapp/services/GBDeployer' +import { GBImporter } from '../../core.gbapp/services/GBImporter' +import { GBConfigService } from '../../core.gbapp/services/GBConfigService' +import { KBService } from './../../kb.gbapp/services/KBService' +import { BotAdapter } from "botbuilder" +import { reject } from "async" +/** + * Dialogs for administration tasks. + */ export class AdminDialog extends IGBDialog { - static setup(bot: BotAdapter, min: GBMinInstance) { - let importer = new GBImporter(min.core); - let deployer = new GBDeployer(min.core, importer); - - min.dialogs.add("/admin", [ - async (dc, args) => { - const prompt = "Please, authenticate:"; - await dc.prompt('textPrompt', prompt); - }, - async (dc, value) => { - let text = value; - const user = min.userState.get(dc.context); - - if ( - !user.authenticated || - text === GBConfigService.get("ADMIN_PASS") - ) { - user.authenticated = true; - dc.context.sendActivity( - "Welcome to Pragmatismo.io GeneralBots Administration." - ); - await dc.prompt('textPrompt', "Which task do you wanna run now?"); - } else { - dc.endAll(); - } - }, - async (dc, value) => { - var text = value; - const user = min.userState.get(dc.context); - - if (text === "quit") { - user.authenticated = false; - dc.replace("/"); - } else if (text === "sync") { - min.core.syncDatabaseStructure(() => { }); - dc.context.sendActivity("Sync started..."); - dc.replace("/admin", { - firstRun: false - }); - } else if (text.split(" ")[0] === "rebuildIndex") { - AdminDialog.rebuildIndexCommand(min, dc, () => - dc.replace("/admin", { - firstRun: false - }) - ); - } else if (text.split(" ")[0] === "deployPackage") { - AdminDialog.deployPackageCommand(text, dc, deployer, min, () => - dc.replace("/admin", { - firstRun: false - }) - ); - } else if (text.split(" ")[0] === "redeployPackage") { - AdminDialog.undeployPackageCommand(text, min, dc, () => { - AdminDialog.deployPackageCommand(text, dc, deployer, min, () => { - dc.context.sendActivity("Redeploy done."); - dc.replace("/admin", { - firstRun: false - }); - }); - }); - } else if (text.split(" ")[0] === "undeployPackage") { - AdminDialog.undeployPackageCommand(text, min, dc, () => - dc.replace("/admin", { - firstRun: false - }) - ); - } else if (text.split(" ")[0] === "applyPackage") { - dc.context.sendActivity("Applying in progress..."); - min.core.loadInstance(text.split(" ")[1], (item, err) => { - dc.context.sendActivity("Applying done..."); - dc.replace("/"); - }); - dc.replace("/admin", { - firstRun: false - }); - } else if (text.split(" ")[0] === "rat") { - min.conversationalService.sendEvent(dc, "play", { playerType: "login", data: null }); - dc.context.sendActivity("Realize login clicando no botão de login, por favor..."); - } - } - ]) - } - - static undeployPackageCommand(text: any, min: GBMinInstance, dc, cb) { - let packageName = text.split(" ")[1]; - let importer = new GBImporter(min.core); - let deployer = new GBDeployer(min.core, importer); - dc.context.sendActivity(`Undeploying package ${packageName}...`); - deployer.undeployPackageFromLocalPath( + static async undeployPackageCommand(text: any, min: GBMinInstance, dc) { + let packageName = text.split(" ")[1] + let importer = new GBImporter(min.core) + let deployer = new GBDeployer(min.core, importer) + dc.context.sendActivity(`Undeploying package ${packageName}...`) + let data = await deployer.undeployPackageFromLocalPath( min.instance, - UrlJoin("deploy", packageName), - (data, err) => { - dc.context.sendActivity(`Package ${packageName} undeployed...`); - cb(); - } - ); + UrlJoin("deploy", packageName)) + dc.context.sendActivity(`Package ${packageName} undeployed...`) } - static deployPackageCommand( + static async deployPackageCommand( text: string, dc, deployer: GBDeployer, min: GBMinInstance, - cb ) { - let packageName = text.split(" ")[1]; - dc.context.sendActivity(`Deploying package ${packageName}... (It may take a few seconds)`); + let packageName = text.split(" ")[1] + dc.context.sendActivity(`Deploying package ${packageName}... (It may take a few seconds)`) // TODO: Find packages in all possible locations. - let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH"); + let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH") - deployer.deployPackageFromLocalPath( - UrlJoin(additionalPath, packageName), - (data, err) => { - dc.context.sendActivity(`Package ${packageName} deployed... Please run rebuildIndex command.`); - - } - ); + let data = deployer.deployPackageFromLocalPath( + UrlJoin(additionalPath, packageName)) + dc.context.sendActivity(`Package ${packageName} deployed... Please run rebuildIndex command.`) } - static rebuildIndexCommand(min: GBMinInstance, dc, cb) { + static async rebuildIndexCommand(min: GBMinInstance, dc) { let search = new AzureSearch( min.instance.searchKey, min.instance.searchHost, min.instance.searchIndex, min.instance.searchIndexer - ); - dc.context.sendActivity("Rebuilding index..."); - search.deleteIndex((data, err) => { - let kbService = new KBService(); - search.createIndex(kbService.getSearchSchema(min.instance.searchIndex), "gb", (data, err) => { - dc.context.sendActivity("Index rebuilt."); - }); - }); + ) + dc.context.sendActivity("Rebuilding index...") + await search.deleteIndex() + let kbService = new KBService() + await search.createIndex(kbService.getSearchSchema(min.instance.searchIndex), "gb") + await dc.context.sendActivity("Index rebuilt.") + } + + + + /** + * Setup dialogs flows and define services call. + * + * @param bot The bot adapter. + * @param min The minimal bot instance data. + */ + static setup(bot: BotAdapter, min: GBMinInstance) { + + // Setup services. + + let importer = new GBImporter(min.core) + let deployer = new GBDeployer(min.core, importer) + + min.dialogs.add("/admin", [ + + async (dc, args) => { + const prompt = "Please, authenticate:" + await dc.prompt('textPrompt', prompt) + }, + async (dc, value) => { + let text = value + const user = min.userState.get(dc.context) + + if ( + !user.authenticated || + text === GBConfigService.get("ADMIN_PASS") + ) { + user.authenticated = true + await dc.context.sendActivity( + "Welcome to Pragmatismo.io GeneralBots Administration." + ) + await dc.prompt('textPrompt', "Which task do you wanna run now?") + } else { + await dc.endAll() + } + }, + async (dc, value) => { + var text = value + const user = min.userState.get(dc.context) + + if (text === "quit") { + user.authenticated = false + await dc.replace("/") + } else if (text === "sync") { + await min.core.syncDatabaseStructure(); + await dc.context.sendActivity("Sync started...") + await dc.replace("/admin", { firstRun: false }) + } else if (text.split(" ")[0] === "rebuildIndex") { + await AdminDialog.rebuildIndexCommand(min, dc) + await dc.replace("/admin", { firstRun: false }) + } else if (text.split(" ")[0] === "deployPackage") { + await AdminDialog.deployPackageCommand(text, dc, deployer, min) + await dc.replace("/admin", { firstRun: false }) + } else if (text.split(" ")[0] === "redeployPackage") { + await AdminDialog.undeployPackageCommand(text, min, dc) + await AdminDialog.deployPackageCommand(text, dc, deployer, min) + await dc.context.sendActivity("Redeploy done.") + await dc.replace("/admin", { firstRun: false }) + } else if (text.split(" ")[0] === "undeployPackage") { + await AdminDialog.undeployPackageCommand(text, min, dc) + await dc.replace("/admin", { firstRun: false }) + } else if (text.split(" ")[0] === "applyPackage") { + await dc.context.sendActivity("Applying in progress...") + await min.core.loadInstance(text.split(" ")[1]); + await dc.context.sendActivity("Applying done...") + await dc.replace("/admin", { firstRun: false }) + } else if (text.split(" ")[0] === "rat") { + await min.conversationalService.sendEvent(dc, "play", { playerType: "login", data: null }) + await dc.context.sendActivity("Realize login clicando no botão de login, por favor...") + } + } + ]) } } diff --git a/deploy/analytics.gblib/services/AnalyticsService.ts b/deploy/analytics.gblib/services/AnalyticsService.ts index edd3b9e8..22782524 100644 --- a/deploy/analytics.gblib/services/AnalyticsService.ts +++ b/deploy/analytics.gblib/services/AnalyticsService.ts @@ -30,38 +30,38 @@ | | \*****************************************************************************/ -import { GBServiceCallback } from "botlib"; import { GuaribasUser } from "../../security.gblib/models"; import { GuaribasConversation, GuaribasConversationMessage } from "../models"; export class AnalyticsService { - - - createConversation( - user: GuaribasUser, - cb: GBServiceCallback - ) { - let conversation = new GuaribasConversation(); - conversation.startedBy = user; - conversation.startedByUserId = user.userId; - conversation.save().then((value: GuaribasConversation) => { - cb(conversation, null); - }); + async createConversation( + user: GuaribasUser + ): Promise { + return new Promise( + (resolve, reject) => { + let conversation = new GuaribasConversation(); + conversation.startedBy = user; + conversation.startedByUserId = user.userId; + conversation.save().then((value: GuaribasConversation) => { + resolve(value); + }); + }); } createMessage( conversation: GuaribasConversation, user: GuaribasUser, - content: string, - cb: GBServiceCallback - ) { - let message = GuaribasConversationMessage.build(); - message.conversation = conversation; - message.user = user; - message.content = content; - message.save().then((value: GuaribasConversationMessage) => { - cb(value, null); - }); + content: string + ): Promise { + return new Promise( + (resolve, reject) => { + let message = GuaribasConversationMessage.build(); + message.conversation = conversation; + message.user = user; + message.content = content; + message.save().then((value: GuaribasConversationMessage) => { + resolve(value); + }); + }); } - } diff --git a/deploy/console.gblib/services/ConsoleDirectLine.ts b/deploy/console.gblib/services/ConsoleDirectLine.ts index ca113186..b7b3c325 100644 --- a/deploy/console.gblib/services/ConsoleDirectLine.ts +++ b/deploy/console.gblib/services/ConsoleDirectLine.ts @@ -40,9 +40,7 @@ const Walk = require("fs-walk"); const logger = require("../../../src/logger"); const Swagger = require('swagger-client'); const rp = require('request-promise'); -import * as request from "request-promise-native"; - -import { GBServiceCallback, GBService, IGBInstance } from "botlib"; +import { GBService } from "botlib"; export class ConsoleDirectLine extends GBService { @@ -193,7 +191,4 @@ export class ConsoleDirectLine extends GBService { console.log('*' + contentLine(attachment.content.text) + '*'); console.log('*'.repeat(width + 1) + '/'); } - - - } \ No newline at end of file diff --git a/deploy/core.gbapp/dialogs/WelcomeDialog.ts b/deploy/core.gbapp/dialogs/WelcomeDialog.ts index 55392c61..851bc9d8 100644 --- a/deploy/core.gbapp/dialogs/WelcomeDialog.ts +++ b/deploy/core.gbapp/dialogs/WelcomeDialog.ts @@ -40,6 +40,12 @@ import { GBMinInstance } from "botlib"; import { BotAdapter } from "botbuilder"; export class WelcomeDialog extends IGBDialog { + /** + * Setup dialogs flows and define services call. + * + * @param bot The bot adapter. + * @param min The minimal bot instance data. + */ static setup(bot: BotAdapter, min: GBMinInstance) { min.dialogs.add("/", [ diff --git a/deploy/core.gbapp/dialogs/WhoAmIDialog.ts b/deploy/core.gbapp/dialogs/WhoAmIDialog.ts index 4e90cb86..1ec5c7a0 100644 --- a/deploy/core.gbapp/dialogs/WhoAmIDialog.ts +++ b/deploy/core.gbapp/dialogs/WhoAmIDialog.ts @@ -40,7 +40,15 @@ import { BotAdapter } from "botbuilder"; export class WhoAmIDialog extends IGBDialog { + + /** + * Setup dialogs flows and define services call. + * + * @param bot The bot adapter. + * @param min The minimal bot instance data. + */ static setup(bot: BotAdapter, min: GBMinInstance) { + min.dialogs.add("/whoAmI", [ async (dc, args) => { dc.context.sendActivity(`${min.instance.description}`); diff --git a/deploy/core.gbapp/services/GBConversationalService.ts b/deploy/core.gbapp/services/GBConversationalService.ts index 61903cda..11c7f7fc 100644 --- a/deploy/core.gbapp/services/GBConversationalService.ts +++ b/deploy/core.gbapp/services/GBConversationalService.ts @@ -1,49 +1,47 @@ /*****************************************************************************\ -| ( )_ _ | -| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | +| ( )_ _ | +| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | | ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ | | | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) | | | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | -| | | ( )_) | | -| (_) \___/' | -| | -| General Bots Copyright (c) Pragmatismo.io. All rights reserved. | -| Licensed under the AGPL-3.0. | -| | -| According to our dual licensing model, this program can be used either | -| under the terms of the GNU Affero General Public License, version 3, | -| or under a proprietary license. | -| | -| The texts of the GNU Affero General Public License with an additional | -| permission and of our proprietary license can be found at and | -| in the LICENSE file you have received along with this program. | -| | -| This program is distributed in the hope that it will be useful, | -| but WITHOUT ANY WARRANTY; without even the implied warranty of | -| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | -| GNU Affero General Public License for more details. | -| | -| "General Bots" is a registered trademark of Pragmatismo.io. | -| The licensing of the program under the AGPLv3 does not imply a | -| trademark license. Therefore any rights, title and interest in | -| our trademarks remain entirely with us. | -| | +| | | ( )_) | | +| (_) \___/' | +| | +| General Bots Copyright (c) Pragmatismo.io. All rights reserved. | +| Licensed under the AGPL-3.0. | +| | +| According to our dual licensing model, this program can be used either | +| under the terms of the GNU Affero General Public License, version 3, | +| or under a proprietary license. | +| | +| The texts of the GNU Affero General Public License with an additional | +| permission and of our proprietary license can be found at and | +| in the LICENSE file you have received along with this program. | +| | +| This program is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| GNU Affero General Public License for more details. | +| | +| "General Bots" is a registered trademark of Pragmatismo.io. | +| The licensing of the program under the AGPLv3 does not imply a | +| trademark license. Therefore any rights, title and interest in | +| our trademarks remain entirely with us. | +| | \*****************************************************************************/ "use strict"; -const UrlJoin = require("url-join"); -const gBuilder = require("botbuilder"); const logger = require("../../../src/logger"); -import { GBConfigService } from "./GBConfigService"; import { GBCoreService } from "./GBCoreService"; -import { GBService, GBServiceCallback, IGBConversationalService } from "botlib"; +import { IGBConversationalService } from "botlib"; import { GBError } from "botlib"; import { GBERROR_TYPE } from "botlib"; import { GBMinInstance } from "botlib"; import { LuisRecognizer } from "botbuilder-ai"; -import {MessageFactory} from "botbuilder"; +import { MessageFactory } from "botbuilder"; +import { resolve } from "bluebird"; export class GBConversationalService implements IGBConversationalService { @@ -64,9 +62,8 @@ export class GBConversationalService implements IGBConversationalService { async runNLP( dc: any, min: GBMinInstance, - text: string, - cb: GBServiceCallback - ) { + text: string + ): Promise { const model = new LuisRecognizer({ appId: min.instance.nlpAppId, @@ -74,12 +71,12 @@ export class GBConversationalService implements IGBConversationalService { serviceEndpoint: min.instance.nlpServerUrl }); - await model.recognize(dc.context).then(res => { + return await model.recognize(dc.context).then(res => { // Resolve intents returned from LUIS let topIntent = LuisRecognizer.topIntent(res); - if (topIntent) { + if (topIntent) { var intent = topIntent; var entity = res.entities && res.entities.length > 0 @@ -93,21 +90,21 @@ export class GBConversationalService implements IGBConversationalService { dc.replace("/" + intent); } catch (error) { logger.info("error: intent: [" + intent + "] error: [" + error + "]"); - + dc.context.sendActivity("Desculpe-me, não encontrei nada a respeito..."); dc.replace("/ask", { isReturning: true }); } - cb({ intent, entities: res.entities }, null); + return Promise.resolve({ intent, entities: res.entities }); } else { - + dc.context.sendActivity("Lamento, não achei nada a respeito..."); dc.replace("/ask", { isReturning: true }); - cb(null, null); + resolve(null); } } ).catch(err => { - cb(null, new GBError(err, GBERROR_TYPE.nlpGeneralError)); + return Promise.reject(new GBError(err, GBERROR_TYPE.nlpGeneralError)); }); } } diff --git a/deploy/core.gbapp/services/GBCoreService.ts b/deploy/core.gbapp/services/GBCoreService.ts index 6ce94220..f2bd1662 100644 --- a/deploy/core.gbapp/services/GBCoreService.ts +++ b/deploy/core.gbapp/services/GBCoreService.ts @@ -32,21 +32,10 @@ "use strict"; -const Path = require("path"); -const Fs = require("fs"); -const _ = require("lodash"); -const Parse = require("csv-parse"); -const Async = require("async"); -const UrlJoin = require("url-join"); -const Walk = require("fs-walk"); const logger = require("../../../src/logger"); - import { Sequelize } from 'sequelize-typescript'; -import { Promise } from "bluebird"; import { GBConfigService } from "./GBConfigService"; -import { DataTypeUUIDv1 } from "sequelize"; - -import { GBServiceCallback, IGBInstance, IGBCoreService } from 'botlib'; +import { IGBInstance, IGBCoreService } from 'botlib'; import { GuaribasInstance } from "../models/GBModel"; /** @@ -54,76 +43,103 @@ import { GuaribasInstance } from "../models/GBModel"; */ export class GBCoreService implements IGBCoreService { + /** + * Data access layer instance. + */ public sequelize: Sequelize; + /** + * Allows filtering on SQL generated before send to the database. + */ private queryGenerator: any; + + /** + * Custom create table query. + */ private createTableQuery: (tableName, attributes, options) => string; + + /** + * Custom change column query. + */ private changeColumnQuery: (tableName, attributes) => string; - /** Dialect used. Tested: mssql and sqlite. */ - + /** + * Dialect used. Tested: mssql and sqlite. + */ private dialect: string; + /** + * Constructor retrieves default values. + */ constructor() { this.dialect = GBConfigService.get("DATABASE_DIALECT"); } - /** Get config and connect to storage. */ - initDatabase(cb) { + /** + * Gets database config and connect to storage. + */ + async initDatabase() { + return new Promise( + (resolve, reject) => { - let host: string | undefined; - let database: string | undefined; - let username: string | undefined; - let password: string | undefined; - let storage: string | undefined; + try { - if (this.dialect === "mssql") { - host = GBConfigService.get("DATABASE_HOST"); - database = GBConfigService.get("DATABASE_NAME"); - username = GBConfigService.get("DATABASE_USERNAME"); - password = GBConfigService.get("DATABASE_PASSWORD"); - } else if (this.dialect === "sqlite") { - storage = GBConfigService.get("DATABASE_STORAGE"); - } + let host: string | undefined; + let database: string | undefined; + let username: string | undefined; + let password: string | undefined; + let storage: string | undefined; - let logging = (GBConfigService.get("DATABASE_LOGGING") === "true") - ? (str: string) => { logger.info(str); } - : false; + if (this.dialect === "mssql") { + host = GBConfigService.get("DATABASE_HOST"); + database = GBConfigService.get("DATABASE_NAME"); + username = GBConfigService.get("DATABASE_USERNAME"); + password = GBConfigService.get("DATABASE_PASSWORD"); + } else if (this.dialect === "sqlite") { + storage = GBConfigService.get("DATABASE_STORAGE"); + } - let encrypt = (GBConfigService.get("DATABASE_ENCRYPT") === "true"); + let logging = (GBConfigService.get("DATABASE_LOGGING") === "true") + ? (str: string) => { logger.info(str); } + : false; - 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 - }, - }); + let encrypt = (GBConfigService.get("DATABASE_ENCRYPT") === "true"); - 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); - } + 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 + }, + }); - setImmediate(cb); + 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); + } + }); } private createTableQueryOverride(tableName, attributes, options): string { @@ -192,70 +208,79 @@ export class GBCoreService implements IGBCoreService { return sql; } - syncDatabaseStructure(cb) { - if (GBConfigService.get("DATABASE_SYNC") === "true") { - const alter = (GBConfigService.get("DATABASE_SYNC_ALTER") === "true"); - const force = (GBConfigService.get("DATABASE_SYNC_FORCE") === "true"); - logger.info("Syncing database..."); - this.sequelize.sync({ - alter: alter, - force: force - }).then(value => { - logger.info("Database synced."); - cb(); - }, err => logger.error(err)); - } else { - logger.info("Database synchronization is disabled."); - cb(); - } + async syncDatabaseStructure() { + return new Promise( + (resolve, reject) => { + if (GBConfigService.get("DATABASE_SYNC") === "true") { + const alter = (GBConfigService.get("DATABASE_SYNC_ALTER") === "true"); + const force = (GBConfigService.get("DATABASE_SYNC_FORCE") === "true"); + logger.info("Syncing database..."); + this.sequelize.sync({ + alter: alter, + force: force + }).then(value => { + logger.info("Database synced."); + resolve(value); + }, err => reject(err)); + } else { + logger.info("Database synchronization is disabled."); + resolve(); + } + }); } /** * Loads all items to start several listeners. - * @param cb Instances loaded or error info. */ - loadInstances(cb: GBServiceCallback) { - GuaribasInstance.findAll({}) - .then((items: IGBInstance[]) => { - if (!items) items = []; + async loadInstances(): Promise { + return new Promise( + (resolve, reject) => { + GuaribasInstance.findAll({}) + .then((items: IGBInstance[]) => { + if (!items) items = []; - if (items.length == 0) { - cb([], null); - } else { - cb(items, null); - } - }) - .catch(reason => { - if (reason.message.indexOf("no such table: GuaribasInstance") != -1) { - cb([], null); - } else { - logger.info(`GuaribasServiceError: ${reason}`); - cb(null, reason); - } + if (items.length == 0) { + resolve([]); + } else { + resolve(items); + } + }) + .catch(reason => { + if (reason.message.indexOf("no such table: GuaribasInstance") != -1) { + resolve([]); + } else { + logger.info(`GuaribasServiceError: ${reason}`); + reject(reason); + } + }); }); } /** * Loads just one Bot instance. */ - loadInstance(botId: string, cb: GBServiceCallback) { - let options = { where: {} }; + async loadInstance(botId: string): Promise { + return new Promise( + (resolve, reject) => { - if (botId != "[default]") { - options.where = { botId: botId }; - } + let options = { where: {} }; - GuaribasInstance.findOne(options) - .then((instance: IGBInstance) => { - if (instance) { - cb(instance, null); - } else { - cb(null, null); + if (botId != "[default]") { + options.where = { botId: botId }; } - }) - .catch(err => { - cb(null, err); - logger.info(`GuaribasServiceError: ${err}`); + + GuaribasInstance.findOne(options) + .then((instance: IGBInstance) => { + if (instance) { + resolve(instance); + } else { + resolve(null); + } + }) + .catch(err => { + logger.info(`GuaribasServiceError: ${err}`); + reject(err); + }); }); } } diff --git a/deploy/core.gbapp/services/GBDeployer.ts b/deploy/core.gbapp/services/GBDeployer.ts index a99c7733..e201ba77 100644 --- a/deploy/core.gbapp/services/GBDeployer.ts +++ b/deploy/core.gbapp/services/GBDeployer.ts @@ -34,25 +34,14 @@ const logger = require("../../../src/logger"); const Path = require("path"); -const Fs = require("fs"); -const FsExtra = require("fs-extra"); const _ = require("lodash"); -const Async = require("async"); const UrlJoin = require("url-join"); -const Walk = require("fs-walk"); -const WaitUntil = require("wait-until"); import { KBService } from './../../kb.gbapp/services/KBService'; import { GBImporter } from "./GBImporter"; -import { GBCoreService } from "./GBCoreService"; import { GBServiceCallback, IGBCoreService, IGBInstance } from "botlib"; -import { Sequelize } from 'sequelize-typescript'; -import { Promise } from "bluebird"; import { GBConfigService } from "./GBConfigService"; -import { DataTypeUUIDv1 } from "sequelize"; -import { GBError, GBERROR_TYPE } from "botlib"; - -import { GBConversationalService } from "./GBConversationalService"; +import { GBError } from "botlib"; import { GuaribasPackage } from '../models/GBModel'; /** Deployer service for bots, themes, ai and more. */ @@ -69,156 +58,131 @@ export class GBDeployer { this.importer = importer; } - /** Deploys a bot to the storage. */ - deployBot(localPath: string, cb: GBServiceCallback) { + /** + * Deploys a bot to the storage. + */ + + async deployBot(localPath: string): Promise { let packageType = Path.extname(localPath); let packageName = Path.basename(localPath); - - this.importer.importIfNotExistsBotPackage( + let instance = await this.importer.importIfNotExistsBotPackage( packageName, - localPath, - (data, err) => { - if (err) { - logger.info(err); - } else { - cb(data, null); - } - } + localPath ); + return instance; } - deployPackageToStorage( + async deployPackageToStorage( instanceId: number, - packageName: string, - cb: GBServiceCallback - ) { - GuaribasPackage.create({ + packageName: string): Promise { + return GuaribasPackage.create({ packageName: packageName, instanceId: instanceId - }).then((item: GuaribasPackage) => { - cb(item, null); }); + } - deployTheme(localPath: string, cb: GBServiceCallback) { + deployTheme(localPath: string) { // DISABLED: Until completed, "/ui/public". // FsExtra.copy(localPath, this.workDir + packageName) // .then(() => { - // cb(null, null); + // }) // .catch(err => { // var gberr = GBError.create( // `GuaribasBusinessError: Error copying package: ${localPath}.` // ); - // cb(null, gberr); // }); } - deployPackageFromLocalPath(localPath: string, cb: GBServiceCallback) { + async deployPackageFromLocalPath(localPath: string) { + let packageType = Path.extname(localPath); switch (packageType) { case ".gbot": - this.deployBot(localPath, cb); - break; + return this.deployBot(localPath); case ".gbtheme": - this.deployTheme(localPath, cb); - break; + return this.deployTheme(localPath); // PACKAGE: Put in package logic. case ".gbkb": let service = new KBService(); - service.deployKb(this.core, this, localPath, cb); - break; + return service.deployKb(this.core, this, localPath); case ".gbui": + break; default: var err = GBError.create( `GuaribasBusinessError: Unknow package type: ${packageType}.` ); - cb(null, err); + Promise.reject(err); break; } } - undeployPackageFromLocalPath( + async undeployPackageFromLocalPath( instance: IGBInstance, - localPath: string, - cb: GBServiceCallback + localPath: string + ) { let packageType = Path.extname(localPath); let packageName = Path.basename(localPath); - this.getPackageByName(instance.instanceId, packageName, (p, err) => { - switch (packageType) { - case ".gbot": - // TODO: this.undeployBot(packageName, localPath, cb); - break; + let p = await this.getPackageByName(instance.instanceId, packageName); - case ".gbtheme": - // TODO: this.undeployTheme(packageName, localPath, cb); - break; + switch (packageType) { + case ".gbot": + // TODO: this.undeployBot(packageName, localPath); + break; - case ".gbkb": - let service = new KBService(); - service.undeployKbFromStorage(instance, p.packageId, cb); - break; + case ".gbtheme": + // TODO: this.undeployTheme(packageName, localPath); + break; - case ".gbui": - break; + case ".gbkb": + let service = new KBService(); + return service.undeployKbFromStorage(instance, p.packageId); - default: - var err = GBError.create( - `GuaribasBusinessError: Unknow package type: ${packageType}.` - ); - cb(null, err); - break; - } + case ".gbui": + + break; + + default: + var err = GBError.create( + `GuaribasBusinessError: Unknown package type: ${packageType}.` + ); + Promise.reject(err); + break; + } + } + + async getPackageByName(instanceId: number, packageName: string): + Promise { + var where = { packageName: packageName, instanceId: instanceId }; + return GuaribasPackage.findOne({ + where: where }); } - getPackageByName( - instanceId: number, - packageName: string, - cb: GBServiceCallback - ) { - - var where = { packageName: packageName, instanceId: instanceId }; - - GuaribasPackage.findOne({ - where: where - }) - .then((value: GuaribasPackage) => { - cb(value, null); - }) - .error(reason => { - cb(null, reason); - }); - } - - /** * * Hot deploy processing. * */ - scanBootPackage(cb: GBServiceCallback) { + async scanBootPackage() { const deployFolder = "deploy"; let bootPackage = GBConfigService.get("BOOT_PACKAGE"); if (bootPackage === "none") { - cb(true, null); + return Promise.resolve(true); } else { - this.deployPackageFromLocalPath( - UrlJoin(deployFolder, bootPackage), - (data, err) => { - logger.info(`Boot package deployed: ${bootPackage}`); - if (err) logger.info(err); - } + return this.deployPackageFromLocalPath( + UrlJoin(deployFolder, bootPackage) ); } } diff --git a/deploy/core.gbapp/services/GBImporter.ts b/deploy/core.gbapp/services/GBImporter.ts index 81ee3968..1a860b9f 100644 --- a/deploy/core.gbapp/services/GBImporter.ts +++ b/deploy/core.gbapp/services/GBImporter.ts @@ -33,22 +33,10 @@ "use strict"; -const _ = require("lodash"); -const Parse = require("csv-parse"); -const Async = require("async"); const UrlJoin = require("url-join"); -const Walk = require("fs-walk"); -const logger = require("../../../src/logger"); - -import { KBService } from './../../kb.gbapp/services/KBService'; -import { Sequelize } from 'sequelize-typescript'; -import { Promise } from "bluebird"; import Fs = require("fs"); import Path = require("path"); -import { DataTypeUUIDv1 } from "sequelize"; -import { GBConfigService } from "./GBConfigService"; -import { GBCoreService } from "./GBCoreService"; -import { GBServiceCallback, IGBCoreService, IGBInstance } from "botlib"; +import { IGBCoreService, IGBInstance } from "botlib"; import { SecService } from "../../security.gblib/services/SecService"; import { GuaribasInstance } from "../models/GBModel"; @@ -58,12 +46,10 @@ export class GBImporter { constructor(core: IGBCoreService) { this.core = core; } - importIfNotExistsBotPackage( + + async importIfNotExistsBotPackage( packageName: string, - localPath: string, - cb: GBServiceCallback - ) { - let _this_ = this; + localPath: string) { let packageJson = JSON.parse( Fs.readFileSync(UrlJoin(localPath, "package.json"), "utf8") @@ -71,20 +57,18 @@ export class GBImporter { let botId = packageJson.botId; - this.core.loadInstance(botId, (instance, err) => { - if (instance) { - cb(instance, null); - } else { - this.createInstanceInternal(packageName, localPath, packageJson, cb); - } - }); + let instance = await this.core.loadInstance(botId); + if (instance) { + return Promise.resolve(instance); + } else { + return this.createInstanceInternal(packageName, localPath, packageJson); + } } - private createInstanceInternal( + private async createInstanceInternal( packageName: string, localPath: string, - packageJson: any, - cb: GBServiceCallback + packageJson: any ) { const settings = JSON.parse( Fs.readFileSync(UrlJoin(localPath, "settings.json"), "utf8") @@ -97,11 +81,10 @@ export class GBImporter { GuaribasInstance.create(packageJson).then((instance: IGBInstance) => { - // PACKAGE: security.json loading let service = new SecService(); // TODO: service.importSecurityFile(localPath, instance); - cb(instance, null); + Promise.resolve(instance); }); } } \ No newline at end of file diff --git a/deploy/core.gbapp/services/GBMinService.ts b/deploy/core.gbapp/services/GBMinService.ts index 1cf292ef..dfd84e86 100644 --- a/deploy/core.gbapp/services/GBMinService.ts +++ b/deploy/core.gbapp/services/GBMinService.ts @@ -77,7 +77,6 @@ export class GBMinService { * Static initialization of minimal instance. * * @param core Basic database services to identify instance, for example. - * @param cb Returns the loaded instance. */ constructor( core: GBCoreService, @@ -91,9 +90,7 @@ export class GBMinService { /** Constructs a new minimal instance for each bot. */ - buildMin(cb: GBServiceCallback, server: any, appPackages: Array) { - - var _this_ = this; + async buildMin(server: any, appPackages: Array): Promise { // Serves default UI on root address '/'. @@ -105,246 +102,232 @@ export class GBMinService { // Loads all bot instances from storage. - _this_.core.loadInstances((instances: IGBInstance[], err) => { + let instances = await this.core.loadInstances(); - // Gets the authorization key for each instance from Bot Service. + // Gets the authorization key for each instance from Bot Service. - instances.forEach(instance => { - let options = { - url: - "https://directline.botframework.com/v3/directline/tokens/generate", - method: "POST", - headers: { - Authorization: `Bearer ${instance.webchatKey}` - } - }; - request(options).then((response: - string) => { + Promise.all(instances).then(async (instance: IGBInstance) => { - // Serves the bot information object via http so clients can get - // instance information stored on server. + let options = { + url: + "https://directline.botframework.com/v3/directline/tokens/generate", + method: "POST", + headers: { + Authorization: `Bearer ${instance.webchatKey}` + } + }; - let responseObject = JSON.parse(response); - server.get("/instances/:botId", (req, res) => { + let response = await request(options); - // Returns the instance object to clients requesting bot info. + // Serves the bot information object via http so clients can get + // instance information stored on server. - let botId = req.params.botId; - _this_.core.loadInstance( - botId, - (instance: IGBInstance, err) => { - if (instance) { + let responseObject = JSON.parse(response); + server.get("/instances/:botId", (req, res) => { + (async () => { - // TODO: Make dynamic: https://CHANGE.api.cognitive.microsoft.com/sts/v1.0 + // Returns the instance object to clients requesting bot info. - let options = { - url: - "https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken", - method: "POST", - headers: { - "Ocp-Apim-Subscription-Key": instance.speechKey - } - }; - request(options).then((response: - string) => { + let botId = req.params.botId; + let instance = await this.core.loadInstance(botId); + if (instance) { - res.send( - JSON.stringify({ - instanceId: instance.instanceId, - botId: botId, - theme: instance.theme, - secret: instance.webchatKey, // TODO: Use token. - speechToken: response, - conversationId: responseObject.conversationId - }) - ); - }).catch((reason) => { - let error = `Error loading Speech Service: ${reason}.`; - res.send(error); - logger.error(error); - }); - } else { - let error = `Instance not found: ${botId}.`; - res.send(error); - logger.error(error); - } + // TODO: Make dynamic: https://CHANGE.api.cognitive.microsoft.com/sts/v1.0 + + let options = { + url: + "https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken", + method: "POST", + headers: { + "Ocp-Apim-Subscription-Key": instance.speechKey } + }; + + response = await request(options); + + res.send( + JSON.stringify({ + instanceId: instance.instanceId, + botId: botId, + theme: instance.theme, + secret: instance.webchatKey, // TODO: Use token. + speechToken: response, + conversationId: responseObject.conversationId + }) ); - }); + } else { + let error = `Instance not found: ${botId}.`; + res.send(error); + logger.error(error); + } }); + }); - // Build bot adapter. + // Build bot adapter. - let adapter = new BotFrameworkAdapter({ - appId: instance.marketplaceId, - appPassword: instance.marketplacePassword - }); - const storage = new MemoryStorage(); - const conversationState = new ConversationState(storage); - const userState = new UserState(storage); - adapter.use(new BotStateSet(conversationState, userState)); + let adapter = new BotFrameworkAdapter({ + appId: instance.marketplaceId, + appPassword: instance.marketplacePassword + }); + const storage = new MemoryStorage(); + const conversationState = new ConversationState(storage); + const userState = new UserState(storage); + adapter.use(new BotStateSet(conversationState, userState)); - // The minimal bot is built here. + // The minimal bot is built here. - let min = new GBMinInstance(); - min.botId = instance.botId; - min.bot = adapter; - min.userState = userState; - min.core = _this_.core; - min.conversationalService = _this_.conversationalService; + let min = new GBMinInstance(); + min.botId = instance.botId; + min.bot = adapter; + min.userState = userState; + min.core = this.core; + min.conversationalService = this.conversationalService; - _this_.core.loadInstance(min.botId, (data, err) => { + min.instance = await this.core.loadInstance(min.botId); - min.instance = data; + // Call the loadBot context.activity for all packages. - // Call the loadBot context.activity for all packages. - - appPackages.forEach(e => { - e.sysPackages = new Array(); - [GBAdminPackage, GBAnalyticsPackage, GBCorePackage, GBSecurityPackage, - GBKBPackage, GBCustomerSatisfactionPackage, GBWhatsappPackage].forEach(sysPackage => { - logger.info(`Loading sys package: ${sysPackage.name}...`); - let p = Object.create(sysPackage.prototype) as IGBPackage; - p.loadBot(min); - e.sysPackages.push(p); - - if (sysPackage.name === "GBWhatsappPackage") { - let url = "/instances/:botId/whatsapp"; - server.post(url, (req, res) => { - p["channel"].received(req, res); - }); - } + appPackages.forEach(e => { + e.sysPackages = new Array(); + [GBAdminPackage, GBAnalyticsPackage, GBCorePackage, GBSecurityPackage, + GBKBPackage, GBCustomerSatisfactionPackage, GBWhatsappPackage].forEach(sysPackage => { + logger.info(`Loading sys package: ${sysPackage.name}...`); + let p = Object.create(sysPackage.prototype) as IGBPackage; + p.loadBot(min); + e.sysPackages.push(p); + if (sysPackage.name === "GBWhatsappPackage") { + let url = "/instances/:botId/whatsapp"; + server.post(url, (req, res) => { + p["channel"].received(req, res); }); - - e.loadBot(min); - }); - - }); - - - // Serves individual URL for each bot conversational interface... - - let url = `/api/messages/${instance.botId}`; - logger.info( - `GeneralBots(${instance.engineName}) listening on: ${url}.` - ); - - - min.dialogs.add('textPrompt', new TextPrompt()); - - server.post(`/api/messages/${instance.botId}`, (req, res) => { - - adapter.processActivity(req, res, async (context) => { - - const state = conversationState.get(context); - const dc = min.dialogs.createContext(context, state); - - const user = min.userState.get(dc.context); - if (!user.loaded) { - min.conversationalService.sendEvent( - dc, - "loadInstance", - { - instanceId: instance.instanceId, - botId: instance.botId, - theme: instance.theme, - secret: instance.webchatKey, // TODO: Use token. - } - ); - - user.loaded = true; - user.subjects = []; } - logger.info( - `[RCV]: ${context.activity.type}, ChannelID: ${context.activity.channelId}, + }); + + e.loadBot(min); + }); + + + // Serves individual URL for each bot conversational interface... + + let url = `/api/messages/${instance.botId}`; + logger.info( + `GeneralBots(${instance.engineName}) listening on: ${url}.` + ); + + min.dialogs.add('textPrompt', new TextPrompt()); + + server.post(`/api/messages/${instance.botId}`, (req, res) => { + + adapter.processActivity(req, res, async (context) => { + + const state = conversationState.get(context); + const dc = min.dialogs.createContext(context, state); + + const user = min.userState.get(dc.context); + if (!user.loaded) { + min.conversationalService.sendEvent( + dc, + "loadInstance", + { + instanceId: instance.instanceId, + botId: instance.botId, + theme: instance.theme, + secret: instance.webchatKey, // TODO: Use token. + } + ); + + user.loaded = true; + user.subjects = []; + } + + logger.info( + `[RCV]: ${context.activity.type}, ChannelID: ${context.activity.channelId}, ConversationID: ${context.activity.conversation.id}, Name: ${context.activity.name}, Text: ${context.activity.text}.` - ); + ); - if (context.activity.type === "conversationUpdate" && - context.activity.membersAdded.length > 0) { - - let member = context.activity.membersAdded[0]; - if (member.name === "GeneralBots") { - logger.info(`Bot added to conversation, starting chat...`); - appPackages.forEach(e => { - e.onNewSession(min, dc); - }); - await dc.begin('/'); - } - else { - logger.info(`Member added to conversation: ${member.name}`); - } - - } else if (context.activity.type === 'message') { - - // Check to see if anyone replied. If not then start echo dialog - - if (context.activity.text === "admin") { - dc.begin("/admin"); - } else { - await dc.continue(); - } - - } else if (context.activity.type === 'event') { - if (context.activity.name === "whoAmI") { - dc.begin("/whoAmI"); - } else if (context.activity.name === "showSubjects") { - dc.begin("/menu"); - } else if (context.activity.name === "giveFeedback") { - dc.begin("/feedback", { - fromMenu: true - }); - } else if (context.activity.name === "showFAQ") { - dc.begin("/faq"); - } else if (context.activity.name === "ask") { - dc.begin("/answer", { - // TODO: query: context.activity.data, - fromFaq: true - }); - } else if (context.activity.name === "quality") { - dc.begin("/quality", { - // TODO: score: context.activity.data - }); - } else { - await dc.continue(); - } + if (context.activity.type === "conversationUpdate" && + context.activity.membersAdded.length > 0) { + let member = context.activity.membersAdded[0]; + if (member.name === "GeneralBots") { + logger.info(`Bot added to conversation, starting chat...`); + appPackages.forEach(e => { + e.onNewSession(min, dc); + }); + await dc.begin('/'); } - }); + else { + logger.info(`Member added to conversation: ${member.name}`); + } + + } else if (context.activity.type === 'message') { + + // Check to see if anyone replied. If not then start echo dialog + + if (context.activity.text === "admin") { + dc.begin("/admin"); + } else { + await dc.continue(); + } + + } else if (context.activity.type === 'event') { + if (context.activity.name === "whoAmI") { + dc.begin("/whoAmI"); + } else if (context.activity.name === "showSubjects") { + dc.begin("/menu"); + } else if (context.activity.name === "giveFeedback") { + dc.begin("/feedback", { + fromMenu: true + }); + } else if (context.activity.name === "showFAQ") { + dc.begin("/faq"); + } else if (context.activity.name === "ask") { + dc.begin("/answer", { + // TODO: query: context.activity.data, + fromFaq: true + }); + } else if (context.activity.name === "quality") { + dc.begin("/quality", { + // TODO: score: context.activity.data + }); + } else { + await dc.continue(); + } + + } }); - - // Serves individual URL for each bot user interface. - - let uiUrl = `/${instance.botId}`; - server.use( - uiUrl, - express.static(UrlJoin(this.deployFolder, uiPackage, "build")) - ); - logger.info(`Bot UI ${uiPackage} acessible at: ${uiUrl}.`); - - // Setups handlers. - // send: function (context.activity, next) { - // logger.info( - // `[SND]: ChannelID: ${context.activity.address.channelId}, ConversationID: ${context.activity.address.conversation}, - // Type: ${context.activity.type} `); - // this.core.createMessage( - // this.min.conversation, - // this.min.conversation.startedBy, - // context.activity.source, - // (data, err) => { - // logger.info(context.activity.source); - // } - // ); - // next(); - - // Specialized load for each min instance. - - cb(min, null); }); + + // Serves individual URL for each bot user interface. + + let uiUrl = `/${instance.botId}`; + server.use( + uiUrl, + express.static(UrlJoin(this.deployFolder, uiPackage, "build")) + ); + logger.info(`Bot UI ${uiPackage} acessible at: ${uiUrl}.`); + + // Setups handlers. + // send: function (context.activity, next) { + // logger.info( + // `[SND]: ChannelID: ${context.activity.address.channelId}, ConversationID: ${context.activity.address.conversation}, + // Type: ${context.activity.type} `); + // this.core.createMessage( + // this.min.conversation, + // this.min.conversation.startedBy, + // context.activity.source, + // (data, err) => { + // logger.info(context.activity.source); + // } + // ); + // next(); + + // Specialized load for each min instance. }); + } /** Performs package deployment in all .gbai or default. */ @@ -352,7 +335,6 @@ export class GBMinService { return new Promise((resolve, reject) => { try { - var _this_ = this; let totalPackages = 0; let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH"); let paths = [this.deployFolder]; @@ -388,19 +370,19 @@ export class GBMinService { } - logger.info(`Starting looking for generalPackages...`); + logger.info(`Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...`); paths.forEach(e => { logger.info(`Looking in: ${e}...`); doIt(e); }); - /** Deploys all .gbapp files first. */ let appPackagesProcessed = 0; gbappPackages.forEach(e => { logger.info(`Deploying app: ${e}...`); + // Skips .gbapp inside deploy folder. if (!e.startsWith('deploy')) { import(e).then(m => { @@ -435,13 +417,11 @@ export class GBMinService { botPackages.forEach(e => { logger.info(`Deploying bot: ${e}...`); - _this_.deployer.deployBot(e, (data, err) => { + this.deployer.deployBot(e, (data, err) => { logger.info(`Bot: ${e} deployed...`); }); }); - // TODO: Wait here. - /** Then all remaining generalPackages are loaded. */ generalPackages.forEach(filename => { diff --git a/deploy/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts b/deploy/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts index e2c86b33..721c2fd6 100644 --- a/deploy/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts +++ b/deploy/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts @@ -41,6 +41,12 @@ import { BotAdapter } from 'botbuilder'; export class FeedbackDialog extends IGBDialog { + /** + * Setup dialogs flows and define services call. + * + * @param bot The bot adapter. + * @param min The minimal bot instance data. + */ static setup(bot: BotAdapter, min: GBMinInstance) { const service = new CSService(); @@ -58,10 +64,9 @@ export class FeedbackDialog extends IGBDialog { async (dc, value) => { let rate = value.entity; const user = min.userState.get(dc.context); - service.updateConversationRate(user.conversation, rate, item => { - let messages = ["Obrigado!", "Obrigado por responder."]; - dc.context.sendActivity(messages[0]); // TODO: Handle rnd. - }); + await service.updateConversationRate(user.conversation, rate); + let messages = ["Obrigado!", "Obrigado por responder."]; + await dc.context.sendActivity(messages[0]); // TODO: Handle rnd. } ]); diff --git a/deploy/customer-satisfaction.gbapp/dialogs/QualityDialog.ts b/deploy/customer-satisfaction.gbapp/dialogs/QualityDialog.ts index b3873961..8f2f7b75 100644 --- a/deploy/customer-satisfaction.gbapp/dialogs/QualityDialog.ts +++ b/deploy/customer-satisfaction.gbapp/dialogs/QualityDialog.ts @@ -41,6 +41,12 @@ const logger = require("../../../src/logger"); export class QualityDialog extends IGBDialog { + /** + * Setup dialogs flows and define services call. + * + * @param bot The bot adapter. + * @param min The minimal bot instance data. + */ static setup(bot: BotAdapter, min: GBMinInstance) { const service = new CSService(); @@ -61,25 +67,22 @@ export class QualityDialog extends IGBDialog { "Lamento... Vamos tentar novamente!", "Desculpe-me. Por favor, tente escrever de outra forma?" ]; - dc.context.sendActivity(msg[0]); + await dc.context.sendActivity(msg[0]); } else { let msg = [ "Ótimo, obrigado por contribuir com sua resposta.", "Certo, obrigado pela informação.", "Obrigado pela contribuição." ]; - dc.context.sendActivity(msg[0]); + await dc.context.sendActivity(msg[0]); - service.insertQuestionAlternate( + await service.insertQuestionAlternate( min.instance.instanceId, user.lastQuestion, - user.lastQuestionId, - (data, err) => { - logger.info("QuestionAlternate inserted."); - } + user.lastQuestionId ); - dc.replace('/ask', {isReturning: true}); + await dc.replace('/ask', {isReturning: true}); } } ]); diff --git a/deploy/customer-satisfaction.gbapp/services/CSService.ts b/deploy/customer-satisfaction.gbapp/services/CSService.ts index 4079bd37..a8d9ad3a 100644 --- a/deploy/customer-satisfaction.gbapp/services/CSService.ts +++ b/deploy/customer-satisfaction.gbapp/services/CSService.ts @@ -30,19 +30,6 @@ | | \*****************************************************************************/ -const logger = require("../../../src/logger"); -const Path = require("path"); -const Fs = require("fs"); -const FsExtra = require("fs-extra"); -const _ = require("lodash"); -const Parse = require("csv-parse"); -const Async = require("async"); -const UrlJoin = require("url-join"); -const Walk = require("fs-walk"); -const WaitUntil = require("wait-until"); - -import { GBServiceCallback } from "botlib"; -import { GBDeployer } from "../../core.gbapp/services/GBDeployer"; import { GuaribasQuestionAlternate } from '../models'; import { GuaribasConversation } from '../../analytics.gblib/models'; @@ -50,44 +37,45 @@ export class CSService { resolveQuestionAlternate( instanceId: number, - questionTyped: string, - cb: GBServiceCallback - ) { - GuaribasQuestionAlternate.findOne({ - where: { - instanceId: instanceId, - questionTyped: questionTyped - } - }).then((value: GuaribasQuestionAlternate) => { - cb(value, null); - }); + questionTyped: string): Promise { + return new Promise( + (resolve, reject) => { + GuaribasQuestionAlternate.findOne({ + where: { + instanceId: instanceId, + questionTyped: questionTyped + } + }).then((value: GuaribasQuestionAlternate) => { + resolve(value); + }).error(reason => reject(reason)); + }); } insertQuestionAlternate( instanceId: number, questionTyped: string, - questionText: string, - cb: GBServiceCallback - ) { - GuaribasQuestionAlternate.create({ - questionTyped: questionTyped, - questionText: questionText - }).then(item => { - if (cb) { - cb(item, null); - } - }); + questionText: string): Promise { + return new Promise( + (resolve, reject) => { + GuaribasQuestionAlternate.create({ + questionTyped: questionTyped, + questionText: questionText + }).then(item => { + resolve(item); + }).error(reason => reject(reason)); + }); } updateConversationRate( conversation: GuaribasConversation, - rate: number, - cb: GBServiceCallback - ) { - conversation.rate = rate; - conversation.save().then((value: GuaribasConversation) => { - cb(conversation, null); - }); + rate: number + ): Promise { + return new Promise( + (resolve, reject) => { + conversation.rate = rate; + conversation.save().then((value: GuaribasConversation) => { + resolve(conversation); + }).error(reason => reject(reason)); + }); } - } diff --git a/deploy/kb.gbapp/dialogs/AskDialog.ts b/deploy/kb.gbapp/dialogs/AskDialog.ts index 850f2055..f7b2413a 100644 --- a/deploy/kb.gbapp/dialogs/AskDialog.ts +++ b/deploy/kb.gbapp/dialogs/AskDialog.ts @@ -42,6 +42,12 @@ import { LuisRecognizer } from "botbuilder-ai"; const logger = require("../../../src/logger"); export class AskDialog extends IGBDialog { + /** + * Setup dialogs flows and define services call. + * + * @param bot The bot adapter. + * @param min The minimal bot instance data. + */ static setup(bot: BotAdapter, min: GBMinInstance) { const service = new KBService(); diff --git a/deploy/kb.gbapp/dialogs/FaqDialog.ts b/deploy/kb.gbapp/dialogs/FaqDialog.ts index 370ee333..331bdaa4 100644 --- a/deploy/kb.gbapp/dialogs/FaqDialog.ts +++ b/deploy/kb.gbapp/dialogs/FaqDialog.ts @@ -38,31 +38,35 @@ import { BotAdapter } from "botbuilder"; import { GBMinInstance } from "botlib"; export class FaqDialog extends IGBDialog { + /** + * Setup dialogs flows and define services call. + * + * @param bot The bot adapter. + * @param min The minimal bot instance data. + */ static setup(bot: BotAdapter, min: GBMinInstance) { const service = new KBService(); min.dialogs.add("/faq", [ async (dc, args) => { - service.getFaqBySubjectArray("faq", null, (data, err) => { - if (data) { - min.conversationalService.sendEvent(dc, "play", { - playerType: "bullet", - data: data.slice(0, 10) - }); + let data = await service.getFaqBySubjectArray("faq", null); + if (data) { + await min.conversationalService.sendEvent(dc, "play", { + playerType: "bullet", + data: data.slice(0, 10) + }); - let messages = [ - "Veja algumas perguntas mais frequentes logo na tela. Clique numa delas para eu responder.", - "Você pode clicar em alguma destas perguntas da tela que eu te respondo de imediato.", - "Veja a lista que eu preparei logo aí na tela..." - ]; + let messages = [ + "Veja algumas perguntas mais frequentes logo na tela. Clique numa delas para eu responder.", + "Você pode clicar em alguma destas perguntas da tela que eu te respondo de imediato.", + "Veja a lista que eu preparei logo aí na tela..." + ]; - dc.context.sendActivity(messages[0]); // TODO: RND messages. - dc.endAll(); - } - }); + await dc.context.sendActivity(messages[0]); // TODO: RND messages. + await dc.endAll(); + } } ]); - } } diff --git a/deploy/kb.gbapp/dialogs/MenuDialog.ts b/deploy/kb.gbapp/dialogs/MenuDialog.ts index d79f95c6..f908a4e4 100644 --- a/deploy/kb.gbapp/dialogs/MenuDialog.ts +++ b/deploy/kb.gbapp/dialogs/MenuDialog.ts @@ -52,6 +52,12 @@ const WaitUntil = require("wait-until"); export class MenuDialog extends IGBDialog { + /** + * Setup dialogs flows and define services call. + * + * @param bot The bot adapter. + * @param min The minimal bot instance data. + */ static setup(bot: BotAdapter, min: GBMinInstance) { var service = new KBService(); @@ -83,16 +89,12 @@ export class MenuDialog extends IGBDialog { if (user.subjects.length > 0) { - service.getFaqBySubjectArray( - "menu", - user.subjects, - (data, err) => { - min.conversationalService.sendEvent(dc, "play", { - playerType: "bullet", - data: data.slice(0, 6) - }); - } - ); + let data = await service.getFaqBySubjectArray("menu", user.subjects); + await min.conversationalService.sendEvent(dc, "play", { + playerType: "bullet", + data: data.slice(0, 6) + }); + } } else { const user = min.userState.get(dc.context); @@ -119,64 +121,63 @@ export class MenuDialog extends IGBDialog { const msg = MessageFactory.text('Greetings from example message'); var attachments = []; - service.getSubjectItems( + let data = await service.getSubjectItems( min.instance.instanceId, - rootSubjectId, - data => { - - msg.attachmentLayout='carousel'; - + rootSubjectId); - data.forEach(function (item: GuaribasSubject) { - var subject = item; + msg.attachmentLayout = 'carousel'; - var card = CardFactory.heroCard( - subject.title, - CardFactory.images([UrlJoin( - "/kb", - min.instance.kb, - "subjects", - subject.internalId + ".png" // TODO: or fallback to subject.png - )]), - CardFactory.actions([ - { - type: 'postBack', - title: 'Selecionar', - value: JSON.stringify({ - title: subject.title, - subjectId: subject.subjectId, - to: subject.to - }) - }])); - attachments.push(card); + data.forEach(function (item: GuaribasSubject) { - }); + var subject = item; - if (attachments.length == 0) { - const user = min.userState.get(dc.context); - if (user.subjects && user.subjects.length > 0) { - dc.context.sendActivity( - `Vamos pesquisar sobre ${KBService.getFormattedSubjectItems( - user.subjects - )}?` - ); - } + var card = CardFactory.heroCard( + subject.title, + CardFactory.images([UrlJoin( + "/kb", + min.instance.kb, + "subjects", + subject.internalId + ".png" // TODO: or fallback to subject.png + )]), + CardFactory.actions([ + { + type: 'postBack', + title: 'Selecionar', + value: JSON.stringify({ + title: subject.title, + subjectId: subject.subjectId, + to: subject.to + }) + }])); - dc.replace("/ask", {}); - } else { - msg.attachments = attachments; - dc.context.sendActivity(msg); - } + attachments.push(card); + + }); + + if (attachments.length == 0) { + const user = min.userState.get(dc.context); + if (user.subjects && user.subjects.length > 0) { + await dc.context.sendActivity( + `Vamos pesquisar sobre ${KBService.getFormattedSubjectItems( + user.subjects + )}?` + ); } - ); + + await dc.replace("/ask", {}); + } else { + msg.attachments = attachments; + await dc.context.sendActivity(msg); + } + const user = min.userState.get(dc.context); user.isAsking = true; }, async (dc, value) => { var text = value; - if (AzureText.isIntentNo(text)) { + if (text==="no"||text==="n") { // TODO: Migrate to a common. dc.replace("/feedback"); } else { dc.replace("/ask"); diff --git a/deploy/kb.gbapp/services/KBService.ts b/deploy/kb.gbapp/services/KBService.ts index c7e1ca86..3428d7cf 100644 --- a/deploy/kb.gbapp/services/KBService.ts +++ b/deploy/kb.gbapp/services/KBService.ts @@ -33,14 +33,17 @@ const logger = require("../../../src/logger"); const Path = require("path"); const Fs = require("fs"); -const Parse = require("csv-parse"); -const Async = require("async"); +const promise = require('bluebird'); +const parse = promise.promisify(require('csv-parse')); const UrlJoin = require("url-join"); -const Walk = require("fs-walk"); const marked = require("marked"); +const path = require("path"); +const asyncPromise = require('async-promises'); +const walkPromise = require('walk-promise'); + import { GBConfigService } from './../../core.gbapp/services/GBConfigService'; import { GuaribasQuestion, GuaribasAnswer, GuaribasSubject } from "../models"; -import { GBServiceCallback, IGBCoreService, IGBConversationalService, IGBInstance } from "botlib"; +import { IGBCoreService, IGBConversationalService, IGBInstance } from "botlib"; import { AzureSearch } from "pragmatismo-io-framework"; import { GBDeployer } from "../../core.gbapp/services/GBDeployer"; import { GuaribasPackage } from "../../core.gbapp/models/GBModel"; @@ -52,55 +55,66 @@ export class KBServiceSearchResults { export class KBService { - getAnswerById( + async getAnswerById( instanceId: number, - answerId: number, - cb: GBServiceCallback - ) { - GuaribasAnswer.findAll({ - where: { - instanceId: instanceId, - answerId: answerId - } - }).then((item: GuaribasAnswer[]) => { - cb(item[0], null); - }); - } - - getAnswerByText( - instanceId: number, - text: string, - cb: GBServiceCallback - ) { - GuaribasQuestion.findOne({ - where: { - instanceId: instanceId, - content: `%${text.trim()}%` - } - }).then((question: GuaribasQuestion) => { - if (question) { + answerId: number + ): Promise { + return new Promise( + (resolve, reject) => { GuaribasAnswer.findAll({ where: { instanceId: instanceId, - answerId: question.answerId + answerId: answerId } - }).then((answer: GuaribasAnswer[]) => { - cb({ question: question, answer: answer[0] }, null); + }).then((item: GuaribasAnswer[]) => { + resolve(item[0]); + }).error((reason) => { + reject(reason); }); - } - else { - cb(null, null); - } - }); + }); } + async getAnswerByText( + instanceId: number, + text: string + ): Promise { + return new Promise( + (resolve, reject) => { - addAnswer(obj: GuaribasAnswer, cb: GBServiceCallback) { - GuaribasAnswer.create(obj).then(item => { - if (cb) { - cb(item, null); - } - }); + GuaribasQuestion.findOne({ + where: { + instanceId: instanceId, + content: `%${text.trim()}%` + } + }).then((question: GuaribasQuestion) => { + if (question) { + GuaribasAnswer.findAll({ + where: { + instanceId: instanceId, + answerId: question.answerId + } + }).then((answer: GuaribasAnswer[]) => { + resolve({ question: question, answer: answer[0] }); + }); + } + else { + resolve(null); + } + }).error((reason) => { + reject(reason); + }); + }); + } + + async addAnswer(obj: GuaribasAnswer): Promise { + return new Promise( + (resolve, reject) => { + GuaribasAnswer.create(obj).then(item => { + resolve(item); + }).error((reason) => { + reject(reason); + }); + }); } async ask( @@ -110,32 +124,25 @@ export class KBService { subjects: GuaribasSubject[] ): Promise { - return new Promise((resolve, reject) => { + // Builds search query. - // Builds search query. + what = what.toLowerCase(); + what = what.replace("?", " "); + what = what.replace("!", " "); + what = what.replace(".", " "); + what = what.replace("/", " "); + what = what.replace("\\", " "); - what = what.toLowerCase(); - what = what.replace("?", " "); - what = what.replace("!", " "); - what = what.replace(".", " "); - what = what.replace("/", " "); - what = what.replace("\\", " "); - - if (subjects) { - let text = KBService.getSubjectItemsSeparatedBySpaces( - subjects - ); - if (text) { - what = `${what} ${text}`; - } + if (subjects) { + let text = KBService.getSubjectItemsSeparatedBySpaces( + subjects + ); + if (text) { + what = `${what} ${text}`; } - - // TODO: Filter by instance. what = `${what}&$filter=instanceId eq ${instanceId}`; - - // Performs search. - - var _this_ = this; - + } + // TODO: Filter by instance. what = `${what}&$filter=instanceId eq ${instanceId}`; + try { if (instance.searchKey && GBConfigService.get("DATABASE_DIALECT") == "mssql") { let service = new AzureSearch( instance.searchKey, @@ -143,41 +150,24 @@ export class KBService { instance.searchIndex, instance.searchIndexer ); - - service.search(what, (err: any, results: any) => { - if (results && results.length > 0) { - // Ponders over configuration. - - if (results[0]["@search.score"] >= searchScore) { - _this_.getAnswerById( - instance.instanceId, - results[0].answerId, - (answer, err) => { - if (err) { reject(err); } else { - resolve({ answer: answer, questionId: results[0].questionId }); - } - } - ); - } else { - resolve(null); - } - } else { - resolve(null); - } - }); + let results = await service.search(what); + if (results && results.length > 0 && + results[0]["@search.score"] >= searchScore) { + let value = await this.getAnswerById( + instance.instanceId, + results[0].answerId); + return Promise.resolve({ answer: value, questionId: results[0].questionId }); + } } else { - this.getAnswerByText(instance.instanceId, what, (data, err) => { - if (data) { - resolve({ answer: data.answer, questionId: data.question.questionId }); - } - else { - if (err) { reject(err); } else { - resolve(null); - } - } - }); + let data = await this.getAnswerByText(instance.instanceId, what); + return Promise.resolve( + { answer: data.answer, questionId: data.question.questionId } + ); } - }); + } + catch (reason) { + return Promise.reject(reason); + } } getSearchSchema(indexName) { @@ -298,166 +288,179 @@ export class KBService { return out.join(" "); } - getSubjectItems( + async getSubjectItems( instanceId: number, - parentId: number, - cb: GBServiceCallback - ) { - var where = { parentSubjectId: parentId, instanceId: instanceId }; - GuaribasSubject.findAll({ - where: where - }) - .then((values: GuaribasSubject[]) => { - cb(values, null); - }) - .error(reason => { - cb(null, reason); + parentId: number + ): Promise { + return new Promise( + (resolve, reject) => { + var where = { parentSubjectId: parentId, instanceId: instanceId }; + GuaribasSubject.findAll({ + where: where + }) + .then((values: GuaribasSubject[]) => { + resolve(values); + }) + .error(reason => { + reject(reason); + }); }); } - getFaqBySubjectArray(from: string, subjects: any, cb) { - let where = { - from: from - }; + async getFaqBySubjectArray(from: string, subjects: any): Promise { + return new Promise( + (resolve, reject) => { - if (subjects) { - if (subjects[0]) { - where["subject1"] = subjects[0].title; - } + let where = { + from: from + }; - if (subjects[1]) { - where["subject2"] = subjects[1].title; - } - - if (subjects[2]) { - where["subject3"] = subjects[2].title; - } - - if (subjects[3]) { - where["subject4"] = subjects[3].title; - } - } - GuaribasQuestion.findAll({ - where: where - }) - .then((items: GuaribasQuestion[]) => { - if (!items) items = []; - if (items.length == 0) { - cb([], null); - } else { - cb(items, null); - } - }) - .catch(reason => { - if (reason.message.indexOf("no such table: IGBInstance") != -1) { - cb([], null); - } else { - cb(null, reason); - logger.info(`GuaribasServiceError: ${reason}`); - } - }); - } - - - importKbTabularFile( - basedir: string, - filename: string, - instanceId: number, - packageId: number, - cb - ) { - var filePath = UrlJoin(basedir, filename); - - var parser = Parse( - { - delimiter: "\t" - }, - function (err, data) { - Async.eachSeries(data, function (line, callback) { - callback(); - let subjectsText = line[0]; - var from = line[1]; - var to = line[2]; - var question = line[3]; - var answer = line[4]; - - // Skip the first line. - - if (!(subjectsText === "subjects" && from == "from")) { - let format = ".txt"; - - // Extract answer from external media if any. - - if (answer.indexOf(".md") > -1) { - let mediaFilename = UrlJoin(basedir, "..", "articles", answer); - if (Fs.existsSync(mediaFilename)) { - answer = Fs.readFileSync(mediaFilename, "utf8"); - format = ".md"; - } else { - logger.info("[GBImporter] File not found: ", mediaFilename); - answer = - "Por favor, contate a administração para rever esta pergunta."; - } - } - - let subjectArray = subjectsText.split("."); - let subject1: string, - subject2: string, - subject3: string, - subject4: string; - - var indexer = 0; - subjectArray.forEach(element => { - if (indexer == 0) { - subject1 = subjectArray[indexer].substring(0, 63); - } else if (indexer == 1) { - subject2 = subjectArray[indexer].substring(0, 63); - } else if (indexer == 2) { - subject3 = subjectArray[indexer].substring(0, 63); - } else if (indexer == 3) { - subject4 = subjectArray[indexer].substring(0, 63); - } - indexer++; - }); - - GuaribasAnswer.create({ - instanceId: instanceId, - content: answer, - format: format, - packageId: packageId - }).then(function (answer: GuaribasAnswer) { - GuaribasQuestion.create({ - from: from, - to: to, - subject1: subject1, - subject2: subject2, - subject3: subject3, - subject4: subject4, - content: question, - instanceId: instanceId, - answerId: answer.answerId, - packageId: packageId - }); - }); - } else { - logger.warn("[GBImporter] Missing header in file: ", filename); + if (subjects) { + if (subjects[0]) { + where["subject1"] = subjects[0].title; } - }); - } - ); - Fs.createReadStream(filePath, { - encoding: "UCS-2" - }).pipe(parser); + + if (subjects[1]) { + where["subject2"] = subjects[1].title; + } + + if (subjects[2]) { + where["subject3"] = subjects[2].title; + } + + if (subjects[3]) { + where["subject4"] = subjects[3].title; + } + } + GuaribasQuestion.findAll({ + where: where + }) + .then((items: GuaribasQuestion[]) => { + if (!items) items = []; + if (items.length == 0) { + resolve([]); + } else { + resolve(items); + } + }) + .catch(reason => { + if (reason.message.indexOf("no such table: IGBInstance") != -1) { + resolve([]); + } else { + reject(reason); + logger.info(`GuaribasServiceError: ${reason}`); + } + }); + }); } - sendAnswer(conversationalService: IGBConversationalService, dc: any, answer: GuaribasAnswer) { + async importKbTabularFile( + filePath: string, + instanceId: number, + packageId: number + ): Promise { + return new Promise( + (resolve, reject) => { + + let file = Fs.readFileSync(filePath, "UCS-2"); + let opts = { + delimiter: "\t" + }; + + var parser = parse(file, opts).then((data) => { + asyncPromise.eachSeries(data, (line) => { + return new Promise((resolve, reject) => { + + // Extracts values from columns in the current line. + + let subjectsText = line[0]; + var from = line[1]; + var to = line[2]; + var question = line[3]; + var answer = line[4]; + + // Skips the first line. + + if (!(subjectsText === "subjects" && from == "from")) { + let format = ".txt"; + + // Extracts answer from external media if any. + + if (answer.indexOf(".md") > -1) { + let mediaFilename = UrlJoin(path.dirname(filePath), "..", "articles", answer); + if (Fs.existsSync(mediaFilename)) { + answer = Fs.readFileSync(mediaFilename, "utf8"); + format = ".md"; + } else { + logger.info("[GBImporter] File not found: ", mediaFilename); + answer = + "Por favor, contate a administração para rever esta pergunta."; + } + } + + // Processes subjects hierarchy splitting by dots. + + let subjectArray = subjectsText.split("."); + let subject1: string, subject2: string, subject3: string, + subject4: string; + var indexer = 0; + + subjectArray.forEach(element => { + if (indexer == 0) { + subject1 = subjectArray[indexer].substring(0, 63); + } else if (indexer == 1) { + subject2 = subjectArray[indexer].substring(0, 63); + } else if (indexer == 2) { + subject3 = subjectArray[indexer].substring(0, 63); + } else if (indexer == 3) { + subject4 = subjectArray[indexer].substring(0, 63); + } + indexer++; + }); + + // Now with all the data ready, creates entities in the store. + + GuaribasAnswer.create({ + instanceId: instanceId, + content: answer, + format: format, + packageId: packageId + }).then((answer: GuaribasAnswer) => { + GuaribasQuestion.create({ + from: from, + to: to, + subject1: subject1, + subject2: subject2, + subject3: subject3, + subject4: subject4, + content: question, + instanceId: instanceId, + answerId: answer.answerId, + packageId: packageId + }).then((question: GuaribasQuestion) => { + resolve(question); + }).error(reason => reject(reason)); + }).error(reason => reject(reason));; + + } else { + logger.warn("[GBImporter] Missing header in file: ", filePath); + } + }); + }); + }).error(reason => reject(reason)); + }); + } + + sendAnswer(conversationalService: IGBConversationalService, + dc: any, answer: GuaribasAnswer) { if (answer.content.endsWith('.mp4')) { conversationalService.sendEvent(dc, "play", { playerType: "video", data: answer.content }); - } else if (answer.content.length > 140 && dc.message.source != "directline") { + } else if (answer.content.length > 140 && + dc.message.source != "directline") { let messages = [ "Vou te responder na tela para melhor visualização...", "A resposta está na tela...", @@ -486,120 +489,124 @@ export class KBService { } } - - importKbPackage( + async importKbPackage( localPath: string, packageStorage: GuaribasPackage, instance: IGBInstance - ) { - this.importSubjectFile( - packageStorage.packageId, - UrlJoin(localPath, "subjects.json"), - instance - ); - let _this_ = this; - setTimeout(() => { - _this_.importKbTabularDirectory( - localPath, - instance, - packageStorage.packageId - ); - }, 3000); + ): Promise { + return new Promise( + (resolve, reject) => { + + // Imports subjects tree into database and return it. + + this.importSubjectFile( + packageStorage.packageId, + UrlJoin(localPath, "subjects.json"), + instance + ).then((value: GuaribasQuestion[]) => { + + // Import all .tsv files in the tabular directory. + + this.importKbTabularDirectory( + localPath, + instance, + packageStorage.packageId + ); + }); + }); } importKbTabularDirectory( localPath: string, instance: IGBInstance, packageId: number - ) { - let _this_ = this; - Walk.files( - UrlJoin(localPath, "tabular"), - (basedir, filename, stat, next) => { - if (filename.endsWith(".tsv")) { - _this_.importKbTabularFile( - basedir, - filename, - instance.instanceId, - packageId, - (data, err) => { - if (err) { - logger.info(err); - } else { - logger.info("Import KB done."); - } + ): Promise { + return new Promise( + (resolve, reject) => { + + walkPromise(UrlJoin(localPath, "tabular")).then((files) => { + files.array.forEach(file => { + if (file.endsWith(".tsv")) { + this.importKbTabularFile( + file, + instance.instanceId, + packageId); } - ); - } - }, - function (err) { - if (err) logger.info(err); - } - ); - } - - importSubjectFile( - packageId: number, - filename: string, - instance: IGBInstance - ) { - var subjects = JSON.parse(Fs.readFileSync(filename, "utf8")); - - function doIt(subjects: GuaribasSubject[], parentSubjectId: number) { - Async.eachSeries(subjects, (item, callback) => { - let mediaFilename = item.id + ".png"; - GuaribasSubject.create({ - internalId: item.id, - parentSubjectId: parentSubjectId, - instanceId: instance.instanceId, - from: item.from, - to: item.to, - title: item.title, - description: item.description, - packageId: packageId - }).then((value: any) => { - if (item.children) { - doIt(item.children, value.subjectId); - } - }); - callback(); - }); - } - doIt(subjects.children, null); - } - - - undeployKbFromStorage( - instance: IGBInstance, - packageId: number, - cb: GBServiceCallback - ) { - GuaribasQuestion.destroy({ - where: { instanceId: instance.instanceId, packageId: packageId } - }).then(value => { - GuaribasAnswer.destroy({ - where: { instanceId: instance.instanceId, packageId: packageId } - }).then(value => { - GuaribasSubject.destroy({ - where: { instanceId: instance.instanceId, packageId: packageId } - }).then(value => { - GuaribasPackage.destroy({ - where: { instanceId: instance.instanceId, packageId: packageId } - }).then(value => { - cb(null, null); }); }); }); - }); + } + + async importSubjectFile( + packageId: number, + filename: string, + instance: IGBInstance + ): Promise { + return new Promise( + (resolve, reject) => { + + var subjects = JSON.parse(Fs.readFileSync(filename, "utf8")); + + const doIt = (subjects: GuaribasSubject[], parentSubjectId: number) => + new Promise((resolve, reject) => { + asyncPromise.eachSeries(subjects, (item, callback) => { + let mediaFilename = item.id + ".png"; + GuaribasSubject.create({ + internalId: item.id, + parentSubjectId: parentSubjectId, + instanceId: instance.instanceId, + from: item.from, + to: item.to, + title: item.title, + description: item.description, + packageId: packageId + }).then((value: any) => { + if (item.children) { + doIt(item.children, value.subjectId); + } + }); + callback(); + }); + }); + + doIt(subjects.children, null); + resolve() + }); + } + + undeployKbFromStorage( + instance: IGBInstance, + packageId: number + ) { + // TODO: call reject. + return new Promise( + (resolve, reject) => { + GuaribasQuestion.destroy({ + where: { instanceId: instance.instanceId, packageId: packageId } + }).then(value => { + GuaribasAnswer.destroy({ + where: { instanceId: instance.instanceId, packageId: packageId } + }).then(value => { + GuaribasSubject.destroy({ + where: { instanceId: instance.instanceId, packageId: packageId } + }).then(value => { + GuaribasPackage.destroy({ + where: { instanceId: instance.instanceId, packageId: packageId } + }).then(value => { + resolve(null); + }); + }); + }); + }); + }); } /** - * Deploys a knowledge base to the storage using the .gbkb format. - * - * @param localPath Path to the .gbkb folder. - * @param cb Package instance or error info. - */ - deployKb(core: IGBCoreService, deployer: GBDeployer, localPath: string, cb: GBServiceCallback) { + * Deploys a knowledge base to the storage using the .gbkb format. + * + * @param localPath Path to the .gbkb folder. + */ + async deployKb(core: IGBCoreService, deployer: GBDeployer, localPath: string) { let packageType = Path.extname(localPath); let packageName = Path.basename(localPath); logger.info("[GBDeployer] Opening package: ", packageName); @@ -607,18 +614,10 @@ export class KBService { Fs.readFileSync(UrlJoin(localPath, "package.json"), "utf8") ); - core.loadInstance(packageObject.botId, (instance, err) => { - deployer.deployPackageToStorage( - instance.instanceId, - packageName, - (p, err) => { - this.importKbPackage(localPath, p, instance); - setTimeout(() => { - cb(null, null); - }, 8000); - } - ); - }); + let instance = await core.loadInstance(packageObject.botId); + let p = await deployer.deployPackageToStorage( + instance.instanceId, + packageName); + await this.importKbPackage(localPath, p, instance); } - } diff --git a/deploy/security.gblib/services/SecService.ts b/deploy/security.gblib/services/SecService.ts index d14cc784..f1a893bd 100644 --- a/deploy/security.gblib/services/SecService.ts +++ b/deploy/security.gblib/services/SecService.ts @@ -39,13 +39,13 @@ const UrlJoin = require("url-join"); const Walk = require("fs-walk"); const logger = require("../../../src/logger"); -import { GBServiceCallback, GBService, IGBInstance } from "botlib"; +import { GBServiceCallback, GBService, IGBInstance } from "botlib"; import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from "../models"; export class SecService extends GBService { - importSecurityFile(localPath: string, instance: IGBInstance) { - var security = JSON.parse( + async importSecurityFile(localPath: string, instance: IGBInstance) { + let security = JSON.parse( Fs.readFileSync(UrlJoin(localPath, "security.json"), "utf8") ); security.groups.forEach(group => { @@ -61,43 +61,46 @@ export class SecService extends GBService { userName: user.userName }); userDb.save().then(userDb => { - let userGroup = GuaribasUserGroup.build(); - userGroup.groupId = groupDb.groupId; - userGroup.userId = userDb.userId; - userGroup.save(); + let userGroup = GuaribasUserGroup.build(); + userGroup.groupId = groupDb.groupId; + userGroup.userId = userDb.userId; + userGroup.save(); }); }); }); }); } - ensureUser( + async ensureUser( instanceId: number, userSystemId: string, userName: string, address: string, channelName: string, - displayName: string, - cb: GBServiceCallback - ) { - GuaribasUser.findOne({ - attributes: ["instanceId", "internalAddress"], - where: { - instanceId: instanceId, - userSystemId: userSystemId - } - }).then(user => { - if (!user) { - user = GuaribasUser.build(); - } - user.userSystemId = userSystemId; - user.userName = userName; - user.displayName = displayName; - user.internalAddress = address; - user.email = userName; - user.defaultChannel = channelName; - user.save(); - cb(user, null); - }); + displayName: string + ): Promise { + return new Promise( + (resolve, reject) => { + + GuaribasUser.findOne({ + attributes: ["instanceId", "internalAddress"], + where: { + instanceId: instanceId, + userSystemId: userSystemId + } + }).then(user => { + if (!user) { + user = GuaribasUser.build(); + } + user.userSystemId = userSystemId; + user.userName = userName; + user.displayName = displayName; + user.internalAddress = address; + user.email = userName; + user.defaultChannel = channelName; + user.save(); + resolve(user); + }).error(reason => reject(reason)); + }); } -} \ No newline at end of file +} diff --git a/deploy/whatsapp.gblib/services/WhatsappDirectLine.ts b/deploy/whatsapp.gblib/services/WhatsappDirectLine.ts index 2f6e80d8..dd89f9af 100644 --- a/deploy/whatsapp.gblib/services/WhatsappDirectLine.ts +++ b/deploy/whatsapp.gblib/services/WhatsappDirectLine.ts @@ -138,7 +138,6 @@ export class WhatsappDirectLine extends GBService { .then((conversationId) => { this.conversationIds[from] = conversationId; - this.inputMessage(client, conversationId, text, from, fromName); diff --git a/package.json b/package.json index e3d78fca..8ac41898 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "tedious": "^2.6.4", "url-join": "^4.0.0", "wait-until": "^0.0.2", + "walk-promise": "^0.2.0", "winston": "^3.0.0" }, "devDependencies": { diff --git a/src/app.ts b/src/app.ts index ea15669a..54749078 100644 --- a/src/app.ts +++ b/src/app.ts @@ -77,16 +77,18 @@ export class GBServer { extended: true })); - server.listen(port, () => { + server.listen(port, async () => { - logger.info(`Accepting connections on ${port}...`); - logger.info(`Starting instances...`); + try { - // Reads basic configuration, initialize minimal services. + logger.info(`Accepting connections on ${port}...`); + logger.info(`Starting instances...`); - GBConfigService.init(); - let core = new GBCoreService(); - core.initDatabase(() => { + // Reads basic configuration, initialize minimal services. + + GBConfigService.init(); + let core = new GBCoreService(); + await core.initDatabase(); // Boot a bot package if any. @@ -104,23 +106,16 @@ export class GBServer { p.loadPackage(core, core.sequelize); }); - (async () => { - try { - await minService.deployPackages(core, server, appPackages); - logger.info(`The Bot Server is in RUNNING mode...`); + await minService.deployPackages(core, server, appPackages); + logger.info(`The Bot Server is in RUNNING mode...`); - minService.buildMin(instance => { - logger.info(`Instance loaded: ${instance.botId}...`); - }, server, appPackages); + let instance = await minService.buildMin(server, appPackages); + logger.info(`Instance loaded: ${instance.botId}...`); + return core; + } catch (err) { + logger.info(err); + } - } catch (err) { - logger.info(err); - } - })() - - }); - - return core; }); } }