diff --git a/deploy/admin.gbapp/dialogs/AdminDialog.ts b/deploy/admin.gbapp/dialogs/AdminDialog.ts index 50890d3a..67b51854 100644 --- a/deploy/admin.gbapp/dialogs/AdminDialog.ts +++ b/deploy/admin.gbapp/dialogs/AdminDialog.ts @@ -30,33 +30,33 @@ | | \*****************************************************************************/ -"use strict" +"use strict"; -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" +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 { GBAdminService } from "../services/GBAdminService"; /** * Dialogs for administration tasks. */ export class AdminDialog extends IGBDialog { - - 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 packageName = text.split(" ")[1]; + let importer = new GBImporter(min.core); + let deployer = new GBDeployer(min.core, importer); + dc.context.sendActivity(`Undeploying package ${packageName}...`); await deployer.undeployPackageFromLocalPath( min.instance, - UrlJoin("deploy", packageName)) - dc.context.sendActivity(`Package ${packageName} undeployed...`) + UrlJoin("deploy", packageName) + ); + dc.context.sendActivity(`Package ${packageName} undeployed...`); } static async deployPackageCommand( @@ -65,11 +65,17 @@ export class AdminDialog extends IGBDialog { deployer: GBDeployer, min: GBMinInstance ) { - let packageName = text.split(" ")[1] - await dc.context.sendActivity(`Deploying package ${packageName}... (It may take a few seconds)`) - let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH") - await deployer.deployPackageFromLocalPath(UrlJoin(additionalPath, packageName)) - await dc.context.sendActivity(`Package ${packageName} deployed... Please run rebuildIndex command.`) + let packageName = text.split(" ")[1]; + await dc.context.sendActivity( + `Deploying package ${packageName}... (It may take a few seconds)` + ); + let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH"); + await deployer.deployPackageFromLocalPath( + UrlJoin(additionalPath, packageName) + ); + await dc.context.sendActivity( + `Package ${packageName} deployed... Please run rebuildIndex command.` + ); } static async rebuildIndexCommand(min: GBMinInstance, dc) { @@ -78,95 +84,120 @@ export class AdminDialog extends IGBDialog { min.instance.searchHost, min.instance.searchIndex, min.instance.searchIndexer - ) - dc.context.sendActivity("Rebuilding index...") - await search.deleteIndex() - let kbService = new KBService(min.core.sequelize) - await search.createIndex(kbService.getSearchSchema(min.instance.searchIndex), "gb") - await dc.context.sendActivity("Index rebuilt.") + ); + dc.context.sendActivity("Rebuilding index..."); + await search.deleteIndex(); + let kbService = new KBService(min.core.sequelize); + 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) + let importer = new GBImporter(min.core); + let deployer = new GBDeployer(min.core, importer); min.dialogs.add("/admin", [ + async dc => { + await AdminDialog.refreshAdminToken(min, dc); + // await dc.context.sendActivity( + // `Deploying package ... (It may take a few seconds)` + // ); + // await AdminDialog.deployPackageCommand( + // "deployPackage ProjectOnline.gbkb", + // dc, + // deployer, + // min + // ); + await dc.endAll(); + } + ]); - async (dc) => { - - await dc.context.sendActivity(`Deploying package ... (It may take a few seconds)`) - await AdminDialog.deployPackageCommand("deployPackage ProjectOnline.gbkb", dc, deployer, min) - await dc.endAll() - - }]) + min.dialogs.add("/adminUpdateToken", [ + async (dc, args, next) => { + await dc.endAll(); + let service = new GBAdminService(); + await service.saveValue("authenticatorToken", args.token) + await dc.context.sendActivity("Token has been updated."); + await dc.replace("/ask") + } + ]); min.dialogs.add("/admin1", [ - async (dc, args) => { - const prompt = "Please, authenticate:" - await dc.prompt('textPrompt', prompt) + const prompt = "Please, authenticate:"; + await dc.prompt("textPrompt", prompt); }, async (dc, value) => { - let text = value - const user = min.userState.get(dc.context) + let text = value; + const user = min.userState.get(dc.context); - if ( - !user.authenticated || - text === GBConfigService.get("ADMIN_PASS") - ) { - user.authenticated = true + 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?") + ); + await dc.prompt("textPrompt", "Which task do you wanna run now?"); } else { - await dc.endAll() + await dc.endAll(); } }, async (dc, value) => { - var text = value - const user = min.userState.get(dc.context) + var text = value; + const user = min.userState.get(dc.context); if (text === "quit") { - user.authenticated = false - await dc.replace("/") + 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 }) + 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 }) + 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 }) + 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 }) + 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 }) + 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 }) + 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...") + await AdminDialog.refreshAdminToken(min, dc); } } - ]) + ]); + } + + private static async refreshAdminToken(min: any, dc: any) { + let config = { + authenticatorTenant: min.instance.authenticatorTenant, + authenticatorClientID: min.instance.authenticatorClientID + }; + await min.conversationalService.sendEvent(dc, "play", { + playerType: "login", + data: config + }); + await dc.context.sendActivity("Update your Administrative token by Login..."); } } diff --git a/deploy/admin.gbapp/index.ts b/deploy/admin.gbapp/index.ts index f047df13..a2304980 100644 --- a/deploy/admin.gbapp/index.ts +++ b/deploy/admin.gbapp/index.ts @@ -38,11 +38,15 @@ import { AdminDialog } from './dialogs/AdminDialog' import { GBMinInstance, IGBPackage, IGBCoreService } from 'botlib' import { Sequelize } from 'sequelize-typescript' +import { GuaribasAdmin } from './models/AdminModel'; export class GBAdminPackage implements IGBPackage { sysPackages: IGBPackage[] = null loadPackage(core: IGBCoreService, sequelize: Sequelize): void { + core.sequelize.addModels([ + GuaribasAdmin + ]) } unloadPackage(core: IGBCoreService): void { diff --git a/deploy/admin.gbapp/models/AdminModel.ts b/deploy/admin.gbapp/models/AdminModel.ts new file mode 100644 index 00000000..a91ac851 --- /dev/null +++ b/deploy/admin.gbapp/models/AdminModel.ts @@ -0,0 +1,61 @@ +/*****************************************************************************\ +| ( )_ _ | +| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | +| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ | +| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) | +| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | +| | | ( )_) | | +| (_) \___/' | +| | +| 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"; + +import { + Table, + Column, + Model, + CreatedAt, + UpdatedAt, +} from "sequelize-typescript"; + + +@Table +export class GuaribasAdmin extends Model + { + + @Column + key: string; + + @Column + value: string; + + @Column + @CreatedAt + createdAt: Date; + + @Column + @UpdatedAt + updatedAt: Date; +} diff --git a/deploy/admin.gbapp/services/GBAdminService.ts b/deploy/admin.gbapp/services/GBAdminService.ts new file mode 100644 index 00000000..99e7bcb5 --- /dev/null +++ b/deploy/admin.gbapp/services/GBAdminService.ts @@ -0,0 +1,57 @@ +/*****************************************************************************\ +| ( )_ _ | +| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | +| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ | +| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) | +| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | +| | | ( )_) | | +| (_) \___/' | +| | +| 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" + +import { GuaribasAdmin } from "../models/AdminModel"; + +export class GBAdminService { + + async saveValue(key: string, value: string): Promise { + let options = { where: {} } + options.where = { key: key } + let admin = await GuaribasAdmin.findOne(options); + if (admin == null) { + admin = new GuaribasAdmin(); + admin.key = key; + } + admin.value = value; + return admin.save() + } + + async getValue(key: string) { + let options = { where: {} } + options.where = { key: key } + let obj = await GuaribasAdmin.findOne(options); + return Promise.resolve(obj.value); + } +} diff --git a/deploy/core.gbapp/dialogs/WelcomeDialog.ts b/deploy/core.gbapp/dialogs/WelcomeDialog.ts index e308c362..14fbfae7 100644 --- a/deploy/core.gbapp/dialogs/WelcomeDialog.ts +++ b/deploy/core.gbapp/dialogs/WelcomeDialog.ts @@ -54,14 +54,15 @@ export class WelcomeDialog extends IGBDialog { user.once = true; var a = new Date(); const date = a.getHours(); - var msg = 4; - date < 12 - ? Messages[locale].good_morning - : date < 18 - ? Messages[locale].good_evening - : Messages[locale].good_night; + var msg = + date < 12 + ? Messages[locale].good_morning + : date < 18 + ? Messages[locale].good_evening + : Messages[locale].good_night; await dc.context.sendActivity(Messages[locale].hi(msg)); + await dc.replace("/ask", { firstTime: true }); if ( dc.context.activity && diff --git a/deploy/core.gbapp/models/GBModel.ts b/deploy/core.gbapp/models/GBModel.ts index d7d43c54..03a6d58f 100644 --- a/deploy/core.gbapp/models/GBModel.ts +++ b/deploy/core.gbapp/models/GBModel.ts @@ -30,14 +30,14 @@ | | \*****************************************************************************/ -"use strict" +"use strict"; import { DataTypes, DataTypeUUIDv4, DataTypeDate, DataTypeDecimal -} from "sequelize" +} from "sequelize"; import { Sequelize, @@ -54,170 +54,214 @@ import { DataType, PrimaryKey, AutoIncrement -} from "sequelize-typescript" +} from "sequelize-typescript"; -import { IGBInstance } from "botlib" +import { IGBInstance } from "botlib"; @Table -export class GuaribasInstance extends Model implements IGBInstance { - +export class GuaribasInstance extends Model + implements IGBInstance { @PrimaryKey @AutoIncrement @Column - instanceId: number - - @Column applicationPrincipal: string + instanceId: number; @Column - whoAmIVideo: string + applicationPrincipal: string; - @Column botId: string + @Column + whoAmIVideo: string; - @Column title: string + @Column + botId: string; - @Column description: string + @Column + title: string; - @Column version: string + @Column + description: string; - @Column enabledAdmin: boolean + @Column + version: string; + + @Column + enabledAdmin: boolean; /* Services section on bot.json */ - @Column engineName: string + @Column + engineName: string; - @Column marketplaceId: string + @Column + marketplaceId: string; - @Column textAnalyticsKey: string + @Column + textAnalyticsKey: string; - @Column textAnalyticsServerUrl: string + @Column + textAnalyticsServerUrl: string; - @Column marketplacePassword: string + @Column + marketplacePassword: string; - @Column webchatKey: string + @Column + webchatKey: string; - @Column whatsappBotKey: string + @Column + authenticatorTenant: string; + @Column + authenticatorSignUpSignInPolicy: string; + @Column + authenticatorClientID: string; - @Column whatsappServiceKey: string + @Column + whatsappBotKey: string; - @Column whatsappServiceNumber: string + @Column + whatsappServiceKey: string; - @Column whatsappServiceUrl: string + @Column + whatsappServiceNumber: string; - @Column whatsappServiceWebhookUrl: string + @Column + whatsappServiceUrl: string; - @Column speechKey: string + @Column + whatsappServiceWebhookUrl: string; - @Column spellcheckerKey: string + @Column + smsKey: string; - @Column theme: string + @Column + smsSecret: string; - @Column ui: string - - @Column kb: string + @Column + smsServiceNumber: string; @Column - nlpAppId: string - + speechKey: string; + @Column - nlpSubscriptionKey: string - + spellcheckerKey: string; + + @Column + theme: string; + + @Column + ui: string; + + @Column + kb: string; + + @Column + nlpAppId: string; + + @Column + nlpSubscriptionKey: string; + @Column @Column({ type: DataType.STRING(512) }) - nlpServerUrl: string + nlpServerUrl: string; - @Column searchHost: string + @Column + searchHost: string; - @Column searchKey: string + @Column + searchKey: string; - @Column searchIndex: string + @Column + searchIndex: string; - @Column searchIndexer: string + @Column + searchIndexer: string; /* Settings section of bot.json */ - @Column(DataType.FLOAT) nlpVsSearch: number + @Column(DataType.FLOAT) + nlpVsSearch: number; - @Column(DataType.FLOAT) searchScore: number + @Column(DataType.FLOAT) + searchScore: number; - @Column(DataType.FLOAT) nlpScore: number + @Column(DataType.FLOAT) + nlpScore: number; @Column @CreatedAt - createdAt: Date + createdAt: Date; @Column @UpdatedAt - updatedAt: Date + updatedAt: Date; } @Table export class GuaribasPackage extends Model { - @PrimaryKey @AutoIncrement @Column - packageId: number + packageId: number; @Column - packageName: string + packageName: string; @ForeignKey(() => GuaribasInstance) @Column - instanceId: number + instanceId: number; @BelongsTo(() => GuaribasInstance) - instance: GuaribasInstance + instance: GuaribasInstance; @Column @CreatedAt - createdAt: Date + createdAt: Date; @Column @UpdatedAt - updatedAt: Date + updatedAt: Date; } @Table export class GuaribasChannel extends Model { - @PrimaryKey @AutoIncrement @Column - channelId: number + channelId: number; - @Column title: string + @Column + title: string; @Column @CreatedAt - createdAt: Date + createdAt: Date; @Column @UpdatedAt - updatedAt: Date + updatedAt: Date; } @Table export class GuaribasException extends Model { - @PrimaryKey @AutoIncrement @Column - exceptionId: number + exceptionId: number; - @Column message: string + @Column + message: string; @ForeignKey(() => GuaribasInstance) @Column - instanceId: number + instanceId: number; @BelongsTo(() => GuaribasInstance) - instance: GuaribasInstance + instance: GuaribasInstance; @Column @CreatedAt - createdAt: Date + createdAt: Date; @Column @UpdatedAt - updatedAt: Date + updatedAt: Date; } diff --git a/deploy/core.gbapp/services/GBConversationalService.ts b/deploy/core.gbapp/services/GBConversationalService.ts index 1bc38579..e1488026 100644 --- a/deploy/core.gbapp/services/GBConversationalService.ts +++ b/deploy/core.gbapp/services/GBConversationalService.ts @@ -1,3 +1,4 @@ +import { IGBInstance } from 'botlib'; /*****************************************************************************\ | ( )_ _ | | _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | @@ -41,6 +42,8 @@ import { LuisRecognizer } from "botbuilder-ai"; import { MessageFactory } from "botbuilder"; import { Messages } from "../strings"; import { AzureText } from "pragmatismo-io-framework"; +const Nexmo = require("nexmo"); + export interface LanguagePickerSettings { defaultLocale?: string; @@ -66,6 +69,19 @@ export class GBConversationalService implements IGBConversationalService { return dc.context.sendActivity(msg); } + async sendSms(min: GBMinInstance, mobile: string, text: string) : Promise { + const nexmo = new Nexmo({ + apiKey: min.instance.smsKey, + apiSecret: min.instance.smsSecret, + }); + nexmo.message.sendSms( + min.instance.smsServiceNumber, + mobile, + text, + ); + } + + async routeNLP(dc: any, min: GBMinInstance, text: string): Promise { // Invokes LUIS. diff --git a/deploy/core.gbapp/services/GBCoreService.ts b/deploy/core.gbapp/services/GBCoreService.ts index e77766dd..34e53d83 100644 --- a/deploy/core.gbapp/services/GBCoreService.ts +++ b/deploy/core.gbapp/services/GBCoreService.ts @@ -37,6 +37,7 @@ import { Sequelize } from "sequelize-typescript" import { GBConfigService } from "./GBConfigService" import { IGBInstance, IGBCoreService } from "botlib" import { GuaribasInstance } from "../models/GBModel" +import { GBAdminService } from "../../admin.gbapp/services/GBAdminService"; /** * Core service layer. @@ -47,6 +48,11 @@ export class GBCoreService implements IGBCoreService { */ public sequelize: Sequelize + /** + * Administrative services. + */ + public adminService: GBAdminService + /** * Allows filtering on SQL generated before send to the database. */ @@ -72,6 +78,7 @@ export class GBCoreService implements IGBCoreService { */ constructor() { this.dialect = GBConfigService.get("DATABASE_DIALECT") + this.adminService = new GBAdminService(); } /** @@ -98,8 +105,8 @@ export class GBCoreService implements IGBCoreService { let logging = GBConfigService.get("DATABASE_LOGGING") === "true" ? (str: string) => { - logger.info(str) - } + logger.info(str) + } : false let encrypt = GBConfigService.get("DATABASE_ENCRYPT") === "true" @@ -240,79 +247,38 @@ export class GBCoreService implements IGBCoreService { } 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() - } - }) + 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...") + return this.sequelize.sync({ + alter: alter, + force: force + }); + } else { + let msg = "Database synchronization is disabled."; + logger.info(msg) + return Promise.reject(msg) + } } /** * Loads all items to start several listeners. */ async loadInstances(): Promise { - return new Promise((resolve, reject) => { - GuaribasInstance.findAll({}) - .then((items: IGBInstance[]) => { - if (!items) items = [] - - 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) - } - }) - }) + return GuaribasInstance.findAll({}); } /** * Loads just one Bot instance. */ async loadInstance(botId: string): Promise { - return new Promise((resolve, reject) => { - let options = { where: {} } + let options = { where: {} } - if (botId != "[default]") { - options.where = { botId: botId } - } + if (botId != "[default]") { + options.where = { botId: botId } + } - GuaribasInstance.findOne(options) - .then((instance: IGBInstance) => { - if (instance) { - resolve(instance) - } else { - resolve(null) - } - }) - .catch(err => { - logger.info(`GuaribasServiceError: ${err}`) - reject(err) - }) - }) + return GuaribasInstance.findOne(options); } } diff --git a/deploy/core.gbapp/services/GBDeployer.ts b/deploy/core.gbapp/services/GBDeployer.ts index 0e0c8555..669eaaca 100644 --- a/deploy/core.gbapp/services/GBDeployer.ts +++ b/deploy/core.gbapp/services/GBDeployer.ts @@ -134,7 +134,7 @@ export class GBDeployer { appPackagesProcessed++ }) .catch(err => { - logger.info(`Error deploying App (.gbapp): ${e}: ${err}`) + logger.error(`Error deploying App (.gbapp): ${e}: ${err}`) appPackagesProcessed++ }) } else { @@ -145,14 +145,16 @@ export class GBDeployer { WaitUntil() .interval(1000) .times(10) - .condition(function(cb) { + .condition(function (cb) { logger.info(`Waiting for app package deployment...`) cb(appPackagesProcessed == gbappPackages.length) }) - .done(function(result) { - logger.info(`App Package deployment done.`) + .done(function (result) { + logger.info(`App Package deployment done.`); - core.syncDatabaseStructure() + (async () => { + await core.syncDatabaseStructure() + })() /** Deploys all .gbot files first. */ @@ -179,7 +181,7 @@ export class GBDeployer { server.use("/themes/" + filenameOnly, express.static(filename)) logger.info( `Theme (.gbtheme) assets accessible at: ${"/themes/" + - filenameOnly}.` + filenameOnly}.` ) /** Knowledge base for bots. */ @@ -205,13 +207,13 @@ export class GBDeployer { }) WaitUntil() - .interval(1000) + .interval(100) .times(5) - .condition(function(cb) { + .condition(function (cb) { logger.info(`Waiting for package deployment...`) cb(totalPackages == generalPackages.length) }) - .done(function(result) { + .done(function (result) { if (botPackages.length === 0) { logger.info( "The server is running with no bot instances, at least one .gbot file must be deployed." diff --git a/deploy/core.gbapp/services/GBMinService.ts b/deploy/core.gbapp/services/GBMinService.ts index 2ddfa923..2939ddc7 100644 --- a/deploy/core.gbapp/services/GBMinService.ts +++ b/deploy/core.gbapp/services/GBMinService.ts @@ -121,7 +121,7 @@ export class GBMinService { server.get("/instances/:botId", (req, res) => { (async () => { - + // Returns the instance object to clients requesting bot info. let botId = req.params.botId @@ -136,7 +136,9 @@ export class GBMinService { theme: instance.theme, secret: instance.webchatKey, // TODO: Use token. speechToken: speechToken, - conversationId: webchatToken.conversationId + conversationId: webchatToken.conversationId, + authenticatorTenant: instance.authenticatorTenant, + authenticatorClientID: instance.authenticatorClientID }) ) } else { @@ -270,97 +272,105 @@ export class GBMinService { appPackages: any[] ) { return adapter.processActivity(req, res, async context => { - const state = conversationState.get(context) - const dc = min.dialogs.createContext(context, state) - dc.context.activity.locale = "en-US" - const user = min.userState.get(dc.context) - if (!user.loaded) { - await min.conversationalService.sendEvent(dc, "loadInstance", { - instanceId: instance.instanceId, - botId: instance.botId, - theme: instance.theme, - secret: instance.webchatKey - }) - user.loaded = true - user.subjects = [] - } + try { + const state = conversationState.get(context) + const dc = min.dialogs.createContext(context, state) + dc.context.activity.locale = "en-US" + const user = min.userState.get(dc.context) - logger.info( - `[RCV]: ${context.activity.type}, ChannelID: ${ + if (!user.loaded) { + await min.conversationalService.sendEvent(dc, "loadInstance", { + instanceId: instance.instanceId, + botId: instance.botId, + theme: instance.theme, + secret: instance.webchatKey + }) + user.loaded = true + user.subjects = [] + } + + logger.info( + `[RCV]: ${context.activity.type}, ChannelID: ${ context.activity.channelId - }, 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) - }) + }, Name: ${context.activity.name}, Text: ${context.activity.text}.` + ) + if ( + context.activity.type === "conversationUpdate" && + context.activity.membersAdded.length > 0 + ) { - // Starts root dialog. + 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("/") + // Processes the root dialog. - } else { - logger.info(`Member added to conversation: ${member.name}`) - } - - // Processes messages. - - } else if (context.activity.type === "message") { - - // Checks for /admin request. - - if (context.activity.text === "admin") { - await dc.begin("/admin") - - // Checks for /menu JSON signature. - - } else if (context.activity.text.startsWith("{\"title\"")) { - await dc.begin("/menu", {data:JSON.parse(context.activity.text)}) - - // Otherwise, continue to the active dialog in the stack. - - } else { - await dc.continue() - } - - // Processes events. - - } else if (context.activity.type === "event") { - - // Empties dialog stack before going to the target. - - await dc.endAll() - - if (context.activity.name === "whoAmI") { - await dc.begin("/whoAmI") - } else if (context.activity.name === "showSubjects") { - await dc.begin("/menu") - } else if (context.activity.name === "giveFeedback") { - await dc.begin("/feedback", { - fromMenu: true - }) - } else if (context.activity.name === "showFAQ") { - await dc.begin("/faq") - } else if (context.activity.name === "ask") { - await dc.begin("/answer", { - query: (context.activity as any).data, - fromFaq: true - }) - } else if (context.activity.name === "quality") { - await dc.begin("/quality", { - // TODO: score: context.activity.data - }) - } else { - await dc.continue() + await dc.begin("/") + + } else { + logger.info(`Member added to conversation: ${member.name}`) + } + + // Processes messages. + + } else if (context.activity.type === "message") { + + // Checks for /admin request. + + if (context.activity.text === "admin") { + await dc.begin("/admin") + + // Checks for /menu JSON signature. + + } else if (context.activity.text.startsWith("{\"title\"")) { + await dc.begin("/menu", { data: JSON.parse(context.activity.text) }) + + // Otherwise, continue to the active dialog in the stack. + + } else { + await dc.continue() + } + + // Processes events. + + } else if (context.activity.type === "event") { + + // Empties dialog stack before going to the target. + + await dc.endAll() + + if (context.activity.name === "whoAmI") { + await dc.begin("/whoAmI") + } else if (context.activity.name === "showSubjects") { + await dc.begin("/menu") + } else if (context.activity.name === "giveFeedback") { + await dc.begin("/feedback", { + fromMenu: true + }) + } else if (context.activity.name === "showFAQ") { + await dc.begin("/faq") + } else if (context.activity.name === "ask") { + await dc.begin("/answer", { + query: (context.activity as any).data, + fromFaq: true + }) + + } else if (context.activity.name === "quality") { + await dc.begin("/quality", { score: (context.activity as any).data }) + } else if (context.activity.name === "updateToken") { + let token = (context.activity as any).data + await dc.begin("/adminUpdateToken", { token: token }) + } else { + await dc.continue() + } } + } catch (error) { + let msg = `Error in main activity: ${error}.` + logger.error(msg) } }) } diff --git a/deploy/default.gbui/public/index.html b/deploy/default.gbui/public/index.html index bdcdb980..53078641 100644 --- a/deploy/default.gbui/public/index.html +++ b/deploy/default.gbui/public/index.html @@ -55,4 +55,6 @@
+ + \ No newline at end of file diff --git a/deploy/default.gbui/src/GBUIApp.js b/deploy/default.gbui/src/GBUIApp.js index 7206da64..dbb1c768 100644 --- a/deploy/default.gbui/src/GBUIApp.js +++ b/deploy/default.gbui/src/GBUIApp.js @@ -45,6 +45,7 @@ import { SpeechSynthesizer } from "botframework-webchat/CognitiveServices"; import { SynthesisGender } from "botframework-webchat/CognitiveServices"; import { Chat } from "botframework-webchat"; import GBPowerBIPlayer from "./players/GBPowerBIPlayer.js"; +import { UserAgentApplication } from "msal"; class GBUIApp extends React.Component { constructor() { @@ -58,6 +59,22 @@ class GBUIApp extends React.Component { }; } + sendToken(token) { + setTimeout(() => { + window.botConnection + .postActivity({ + type: "event", + name: "updateToken", + data: token, + locale: "en-us", + textFormat: "plain", + timestamp: new Date().toISOString(), + from: { id: "webUser", name: "You" } + }) + .subscribe(this.send("success")); + }, 400); + } + send(command) { window.botConnection .postActivity({ @@ -93,8 +110,11 @@ class GBUIApp extends React.Component { configureChat() { var botId = window.location.href.split("/")[3]; + if (botId.indexOf('#') != -1) { + botId = botId.split("#")[0]; + } - if (!botId) { + if (!botId || botId == "") { botId = "[default]"; } @@ -102,7 +122,7 @@ class GBUIApp extends React.Component { .then(res => res.json()) .then( result => { - this.setState({instanceClient:result}); + this.setState({ instanceClient: result }); this.setupBotConnection(); }, error => { @@ -114,6 +134,38 @@ class GBUIApp extends React.Component { ); } + authenticate() { + let _this_ = this; + let authority = + "https://login.microsoftonline.com/" + + this.state.instanceClient.authenticatorTenant; + + let graphScopes = ["Directory.AccessAsUser.All"]; + + let userAgentApplication = new UserAgentApplication( + this.state.instanceClient.authenticatorClientID, + authority, + function (errorDesc, token, error, tokenType) { + userAgentApplication.acquireTokenSilent(graphScopes).then(function (accessToken) { + _this_.sendToken(accessToken); + }, function (error) { + console.log(error); + }) + } + ); + + if (!userAgentApplication.isCallback(window.location.hash) && window.parent === window && !window.opener) { + var user = userAgentApplication.getUser(); + if (user) { + userAgentApplication.acquireTokenSilent(graphScopes).then(function (accessToken) { + _this_.sendToken(accessToken); + }, function (error) { + console.log(error); + }) + } + } + } + setupBotConnection() { let _this_ = this; window["botchatDebug"] = true; @@ -124,14 +176,13 @@ class GBUIApp extends React.Component { botConnection.connectionStatus$.subscribe(connectionStatus => { if (connectionStatus === ConnectionStatus.Online) { + _this_.setState({ botConnection: botConnection }); botConnection.postActivity({ type: "event", value: "startGB", from: this.getUser(), name: "startGB" - }); - - _this_.setState({ botConnection: botConnection }); + }); } }); @@ -145,8 +196,9 @@ class GBUIApp extends React.Component { ) .subscribe(activity => { _this_.setState({ instance: activity.value }); + _this_.authenticate() }); - + botConnection.activity$ .filter(activity => activity.type === "event" && activity.name === "stop") .subscribe(activity => { @@ -168,7 +220,7 @@ class GBUIApp extends React.Component { } render() { - + let playerComponent = ""; @@ -224,7 +276,7 @@ class GBUIApp extends React.Component { /> ); break; - case "login": + case "login": playerComponent = ( ; - let gbCss =
; + let gbCss =
; let sideBar = ( @@ -254,7 +306,7 @@ class GBUIApp extends React.Component {
); - + if (this.state.botConnection && this.state.instance) { let token = this.state.instanceClient.speechToken; gbCss = ; @@ -264,7 +316,7 @@ class GBUIApp extends React.Component { resolve(token); }); } - + speechOptions = { speechRecognizer: new SpeechRecognizer({ locale: "pt-br", @@ -301,7 +353,7 @@ class GBUIApp extends React.Component { return (
- {gbCss} + {gbCss} {sideBar}
{playerComponent}
{chat} diff --git a/deploy/default.gbui/src/index.js b/deploy/default.gbui/src/index.js index e7820101..6d320a9c 100644 --- a/deploy/default.gbui/src/index.js +++ b/deploy/default.gbui/src/index.js @@ -39,3 +39,4 @@ ReactDOM.render( , document.getElementById("root") ); + diff --git a/deploy/default.gbui/src/players/GBLoginPlayer.js b/deploy/default.gbui/src/players/GBLoginPlayer.js index 63730de4..ab264703 100644 --- a/deploy/default.gbui/src/players/GBLoginPlayer.js +++ b/deploy/default.gbui/src/players/GBLoginPlayer.js @@ -31,68 +31,54 @@ \*****************************************************************************/ import React from "react"; +import { Logger, LogLevel } from "msal"; import { UserAgentApplication } from "msal"; class GBLoginPlayer extends React.Component { - - constructor(tenant) { + constructor() { super(); this.state = { - token: "", + login: {} }; } - - - login() { - - let config = { - tenant: "pragmatismo.onmicrosoft.com", //"6ecb2a67-15af-4582-ab85-cc65096ce471", - signUpSignInPolicy: "b2c_1_susi", - clientID: '47cbaa05-dbb4-46f8-8608-da386c5131f1'} - - - let authority = "https://login.microsoftonline.com/tfp/" + - config.tenant + "/" + - config.signUpSignInPolicy; - - let userAgentApplication = new UserAgentApplication( - config.clientID, authority, - function (errorDesc, token, error, tokenType) { - console.log(token); - } + doLogin(info) { + let logger = new Logger( + (logLevel, message, piiEnabled) => { + console.log(message); + }, + { level: LogLevel.Verbose } ); + + let authority = + "https://login.microsoftonline.com/" + + this.state.login.authenticatorTenant; + let graphScopes = ["Directory.AccessAsUser.All"]; - userAgentApplication.loginPopup(graphScopes).then(function (idToken) { - userAgentApplication.acquireTokenSilent(graphScopes).then(function (accessToken) { - console.log(accessToken); - - }, function (error) { - userAgentApplication.acquireTokenPopup(graphScopes).then(function (accessToken) { - console.log(accessToken); - - }, function (error) { + let userAgentApplication = new UserAgentApplication( + this.state.login.authenticatorClientID, + authority, + function (errorDesc, token, error, tokenType) { + if (error) { console.log(error); - }); + } }) - }, function (error) { - console.log(error); - }); + + userAgentApplication.loginRedirect(graphScopes); } - play() { + play(data) { + this.setState({ login: data }); + } + stop() { + this.setState({ login: [] }); } render() { - return ( - ; } } diff --git a/deploy/kb.gbapp/dialogs/AskDialog.ts b/deploy/kb.gbapp/dialogs/AskDialog.ts index 8010e384..d30e4e64 100644 --- a/deploy/kb.gbapp/dialogs/AskDialog.ts +++ b/deploy/kb.gbapp/dialogs/AskDialog.ts @@ -40,7 +40,6 @@ import { BotAdapter } from "botbuilder"; import { Messages } from "../strings"; import { LuisRecognizer } from "botbuilder-ai"; - const logger = require("../../../src/logger"); export class AskDialog extends IGBDialog { @@ -69,7 +68,7 @@ export class AskDialog extends IGBDialog { throw new Error(`/answer being called with no args.query text.`); } - let locale = dc.context.activity.locale + let locale = dc.context.activity.locale; // Stops any content on projector. @@ -163,7 +162,7 @@ export class AskDialog extends IGBDialog { ); await dc.replace("/ask", { isReturning: true }); } else { - if (!(await min.conversationalService.runNLP(dc, min, text))) { + if (!(await min.conversationalService.routeNLP(dc, min, text))) { await dc.context.sendActivity(Messages[locale].did_not_find); await dc.replace("/ask", { isReturning: true }); } @@ -181,16 +180,21 @@ export class AskDialog extends IGBDialog { user.subjects = []; } let text = []; - if (user.subjects.length > 0) { - text = Messages[locale].which_question; - } - if (args && args.isReturning) { + // Three forms of asking. + + if (args.firstTime) { + text = Messages[locale].ask_first_time; + } else if (args && args.isReturning) { text = Messages[locale].anything_else; + } else if (user.subjects.length > 0) { + text = Messages[locale].which_question; + } else { + throw new Error("Invalid use of /ask"); } if (text.length > 0) { - await dc.prompt("textPrompt", text[0]); + await dc.prompt("textPrompt", text); } }, async (dc, value) => { diff --git a/deploy/kb.gbapp/strings.ts b/deploy/kb.gbapp/strings.ts index 3747bf97..aa0a29d2 100644 --- a/deploy/kb.gbapp/strings.ts +++ b/deploy/kb.gbapp/strings.ts @@ -13,7 +13,9 @@ export const Messages = { `Vamos pesquisar sobre ${query}... O que deseja saber?`, see_faq: "Please take a look at the FAQ I've prepared for you. You can click on them to get the answer.", - will_answer_projector:"I'll answer on the projector to a better experience..." + will_answer_projector: + "I'll answer on the projector to a better experience...", + ask_first_time: "What are you looking for?" }, "pt-BR": { did_not_find: "Desculpe-me, não encontrei nada a respeito.", @@ -29,6 +31,8 @@ export const Messages = { `Let's search about ${query}... What do you want to know?`, see_faq: "Veja algumas perguntas mais frequentes logo na tela. Clique numa delas para eu responder.", - will_answer_projector:"Vou te responder na tela para melhor visualização..." + will_answer_projector: + "Vou te responder na tela para melhor visualização...", + ask_first_time: "Sobre como eu poderia ajudar?" } }; diff --git a/package.json b/package.json index beab1225..1bcef2cb 100644 --- a/package.json +++ b/package.json @@ -42,20 +42,22 @@ "botlib": "^0.0.33", "chokidar": "^2.0.4", "csv-parse": "^3.0.0", - "dotenv-extended": "^2.2.0", + "dotenv-extended": "^2.3.0", "express": "^4.16.3", "express-promise-router": "^3.0.3", "fs-extra": "^7.0.0", "fs-walk": "^0.0.2", "localize": "^0.4.7", "marked": "^0.5.0", + "ms": "^2.1.1", + "nexmo": "^2.3.2", "pragmatismo-io-framework": "^1.0.15", "reflect-metadata": "^0.1.12", "request-promise-native": "^1.0.5", - "sequelize": "^4.38.0", + "sequelize": "^4.38.1", "sequelize-typescript": "^0.6.6", "sqlite3": "^4.0.2", - "swagger-client": "^3.8.19", + "swagger-client": "^3.8.21", "tedious": "^2.6.4", "url-join": "^4.0.0", "wait-until": "^0.0.2", diff --git a/src/app.ts b/src/app.ts index b8eea841..7dd72ce8 100644 --- a/src/app.ts +++ b/src/app.ts @@ -83,7 +83,6 @@ export class GBServer { try { logger.info(`Accepting connections on ${port}...`) - logger.info(`Starting instances...`) // Reads basic configuration, initialize minimal services. @@ -93,10 +92,11 @@ export class GBServer { // Boot a bot package if any. + logger.info(`Starting instances...`) let deployer = new GBDeployer(core, new GBImporter(core)) // Build a minimal bot instance for each .gbot deployment. - + let conversationalService = new GBConversationalService(core) let minService = new GBMinService(core, conversationalService, deployer);