From 3f13609d5981163b86cca9bd817c954d1e70412e Mon Sep 17 00:00:00 2001 From: Rodrigo Rodriguez Date: Wed, 20 Jan 2021 18:23:42 -0300 Subject: [PATCH] fix(basic.gblib): COPY and CONVERT is now generating good JS. --- package-lock.json | 7 ++- package.json | 1 + .../admin.gbapp/services/GBAdminService.ts | 20 ++++++- packages/basic.gblib/services/GBVMService.ts | 13 +++-- packages/core.gbapp/services/GBDeployer.ts | 57 ++++++++++-------- packages/core.gbapp/services/GBMinService.ts | 37 +++++++++--- packages/default.gbui/package-lock.json | 36 ++++++++++++ packages/default.gbui/package.json | 2 + packages/default.gbui/src/GBUIApp.js | 22 ++++--- packages/default.gbui/src/components/SEO.js | 58 +++++++++++++++++++ .../src/components/SidebarMenu.js | 15 ++--- packages/kb.gbapp/services/KBService.ts | 53 ++++++++++++++++- src/app.ts | 4 +- 13 files changed, 262 insertions(+), 63 deletions(-) create mode 100644 packages/default.gbui/src/components/SEO.js diff --git a/package-lock.json b/package-lock.json index ab8e08b7..bd3142f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "botserver", - "version": "2.0.79", + "version": "2.0.91", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -20344,6 +20344,11 @@ "spdx-ranges": "^2.0.0" } }, + "speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==" + }, "split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", diff --git a/package.json b/package.json index 72263485..97d4ef5c 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "sequelize": "5.21.5", "sequelize-typescript": "1.1.0", "simple-git": "2.23.0", + "speakingurl": "^14.0.1", "sppull": "2.6.7", "strict-password-generator": "1.1.2", "swagger-client": "2.1.18", diff --git a/packages/admin.gbapp/services/GBAdminService.ts b/packages/admin.gbapp/services/GBAdminService.ts index a429b77d..ca3264bc 100644 --- a/packages/admin.gbapp/services/GBAdminService.ts +++ b/packages/admin.gbapp/services/GBAdminService.ts @@ -49,6 +49,7 @@ import { GuaribasAdmin } from '../models/AdminModel'; const Path = require('path'); const msRestAzure = require('ms-rest-azure'); const PasswordGenerator = require('strict-password-generator').default; +const crypto = require("crypto"); /** * Services for server administration. @@ -145,6 +146,21 @@ export class GBAdminService implements IGBAdminService { return passwordGenerator.generatePassword(options); } + /** + * @see https://stackoverflow.com/a/52171480 + */ + public static getHash(str, seed = 0) { + let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; + for (let i = 0, ch; i < str.length; i++) { + ch = str.charCodeAt(i); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909); + return 4294967296 * (2097151 & h2) + (h1 >>> 0); + } + public static async undeployPackageCommand(text: any, min: GBMinInstance) { const packageName = text.split(' ')[1]; const importer = new GBImporter(min.core); @@ -176,7 +192,7 @@ export class GBAdminService implements IGBAdminService { // .gbot packages are handled using storage API, so no download // of local resources is required. - + if (!localFolder.endsWith('.gbot')) { GBLog.warn(`${GBConfigService.get('CLOUD_USERNAME')} must be authorized on SharePoint related site to download to: ${localFolder}`); await s.downloadFolder( @@ -261,7 +277,7 @@ export class GBAdminService implements IGBAdminService { const refreshToken = await this.getValue(instanceId, 'refreshToken'); const resource = 'https://graph.microsoft.com'; const authenticationContext = new AuthenticationContext(authorizationUrl); - + authenticationContext.acquireTokenWithRefreshToken( refreshToken, instance.marketplaceId, diff --git a/packages/basic.gblib/services/GBVMService.ts b/packages/basic.gblib/services/GBVMService.ts index f5d82aff..7e18bf02 100644 --- a/packages/basic.gblib/services/GBVMService.ts +++ b/packages/basic.gblib/services/GBVMService.ts @@ -289,8 +289,12 @@ export class GBVMService extends GBService { return `sendFile (step, ${$3})\n`; }); - code = code.replace(/(COPY)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `sys().copyFile (step, ${$3})\n`; + code = code.replace(/(copy)(\s*)(.*)/gi, ($0, $1, $2, $3) => { + return `sys().copyFile(step, ${$3})\n`; + }); + + code = code.replace(/(convert)(\s*)(.*)/gi, ($0, $1, $2, $3) => { + return `sys().convert(step, ${$3})\n`; }); code = code.replace(/(save)(\s)(.*)/gi, ($0, $1, $2, $3) => { @@ -433,9 +437,6 @@ export class GBVMService extends GBService { code = code.replace(/("[^"]*"|'[^']*')|\bsendFileTo\b/gi, ($0, $1) => { return $1 === undefined ? 'this.sendFileTo' : $1; }); - code = code.replace(/("[^"]*"|'[^']*')|\bcopyFile\b/gi, ($0, $1) => { - return $1 === undefined ? 'this.copyFile' : $1; - }); code = code.replace(/("[^"]*"|'[^']*')|\bsendFile\b/gi, ($0, $1) => { return $1 === undefined ? 'this.sendFile' : $1; }); @@ -448,7 +449,7 @@ export class GBVMService extends GBService { code = code.replace(/("[^"]*"|'[^']*')|\bmenu\b/gi, ($0, $1) => { return $1 === undefined ? 'this.menu' : $1; }); - + // await insertion. code = code.replace(/this\./gm, 'await this.'); diff --git a/packages/core.gbapp/services/GBDeployer.ts b/packages/core.gbapp/services/GBDeployer.ts index f179d1a6..83fb26e4 100644 --- a/packages/core.gbapp/services/GBDeployer.ts +++ b/packages/core.gbapp/services/GBDeployer.ts @@ -186,7 +186,7 @@ export class GBDeployer implements IGBDeployer { const instances = await core.loadInstances(); await CollectionUtil.asyncForEach(instances, async instance => { this.mountGBKBAssets(`${instance.botId}.gbkb`, - instance.botId, `${instance.botId}.gbkb`); + instance.botId, `${instance.botId}.gbkb`); }); GBLog.info(`Package deployment done.`); @@ -320,7 +320,7 @@ export class GBDeployer implements IGBDeployer { public async publishNLP(instance: IGBInstance): Promise { const service = new AzureDeployerService(this); const res = await service.publishNLP(instance.cloudLocation, instance.nlpAppId, - instance.nlpAuthoringKey); + instance.nlpAuthoringKey); if (res.status !== 200 && res.status !== 201) { throw res.bodyAsText; } } @@ -371,29 +371,29 @@ export class GBDeployer implements IGBDeployer { const libraryId = process.env.STORAGE_LIBRARY; GBLog.info(`Connecting to Config.xslx (siteId: ${siteId}, libraryId: ${libraryId})...`); - + // Connects to MSFT storage. - + const token = await min.adminService.acquireElevatedToken(min.instance.instanceId); const client = MicrosoftGraph.Client.init({ authProvider: done => { done(null, token); } }); - + // Retrieves all files in .bot folder. - + const botId = min.instance.botId; const path = `/${botId}.gbai/${botId}.gbot`; let url = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${libraryId}/drive/root:${path}:/children`; GBLog.info(`Loading .gbot from Excel: ${url}`); const res = await client - .api(url) - .get(); - + .api(url) + .get(); + // Finds Config.xlsx. - + const document = res.value.filter(m => { return m.name === 'Config.xlsx'; }); @@ -496,14 +496,14 @@ export class GBDeployer implements IGBDeployer { if (handled) { return pck; } - + // Deploy platform packages here accordingly to their extension. - + switch (packageType) { case '.gbot': - + // Extracts configuration information from .gbot files. - + if (process.env.ENABLE_PARAMS_ONLINE === 'false') { if (Fs.existsSync(localPath)) { GBLog.info(`Loading .gbot from ${localPath}.`); @@ -512,9 +512,9 @@ export class GBDeployer implements IGBDeployer { } else { min.instance.params = await this.loadParamsFromTabular(min); } - + // Updates instance object. - + await this.core.saveInstance(min.instance); break; @@ -561,6 +561,7 @@ export class GBDeployer implements IGBDeployer { break; default: + const err = GBError.create(`Unhandled package type: ${packageType}.`); Promise.reject(err); break; @@ -581,6 +582,7 @@ export class GBDeployer implements IGBDeployer { // Removes objects from storage, cloud resources and local files if any. switch (packageType) { + case '.gbot': const packageObject = JSON.parse(Fs.readFileSync(urlJoin(localPath, 'package.json'), 'utf8')); await this.undeployBot(packageObject.botId, packageName); @@ -596,17 +598,17 @@ export class GBDeployer implements IGBDeployer { break; case '.gbtheme': - rimraf.sync(localPath); break; case '.gbdialog': - rimraf.sync(localPath); break; case '.gblib': break; + case '.gbapp': break; + default: const err = GBError.create(`Unhandled package type: ${packageType}.`); Promise.reject(err); @@ -647,6 +649,7 @@ export class GBDeployer implements IGBDeployer { } // Removes the index. + try { await search.deleteIndex(); } catch (err) { @@ -659,6 +662,7 @@ export class GBDeployer implements IGBDeployer { } // Creates the data source and index on the cloud. + try { await search.createDataSource(dsName, dsName, 'GuaribasQuestion', 'azuresql', connectionString); } catch (err) { @@ -694,7 +698,7 @@ export class GBDeployer implements IGBDeployer { if (!Fs.existsSync(`${root}/build`) && process.env.DISABLE_WEB !== 'true') { - // Write a .env required to fix some bungs in create-react-app facility. + // Write a .env required to fix some bungs in create-react-app tool. Fs.writeFileSync(`${root}/.env`, 'SKIP_PREFLIGHT_CHECK=true'); @@ -702,15 +706,18 @@ export class GBDeployer implements IGBDeployer { GBLog.info(`Installing modules default.gbui (It may take a few minutes)...`); child_process.execSync(`${npm} install`, { cwd: root }); + GBLog.info(`Transpiling default.gbui...`); child_process.execSync(`${npm} run build`, { cwd: root }); } // Clean up node_modules folder as it is only needed during compile time. - GBLog.info(`Cleaning default.gbui node_modules...`); const nodeModules = urlJoin(root, 'node_modules'); - rimraf.sync(nodeModules); + if (Fs.existsSync(nodeModules)) { + rimraf.sync(nodeModules); + GBLog.info(`Cleaning default.gbui node_modules...`); + } } /** @@ -729,13 +736,13 @@ export class GBDeployer implements IGBDeployer { const gbaiName = `${botId}.gbai`; GBServer.globals.server.use(`/kb/${gbaiName}/${packageName}/assets`, - express.static(urlJoin('work', gbaiName, filename, 'assets'))); + express.static(urlJoin('work', gbaiName, filename, 'assets'))); GBServer.globals.server.use(`/kb/${gbaiName}/${packageName}/images`, - express.static(urlJoin('work', gbaiName, filename, 'images'))); + express.static(urlJoin('work', gbaiName, filename, 'images'))); GBServer.globals.server.use(`/kb/${gbaiName}/${packageName}/audios`, - express.static(urlJoin('work', gbaiName, filename, 'audios'))); + express.static(urlJoin('work', gbaiName, filename, 'audios'))); GBServer.globals.server.use(`/kb/${gbaiName}/${packageName}/videos`, - express.static(urlJoin('work', gbaiName, filename, 'videos'))); + express.static(urlJoin('work', gbaiName, filename, 'videos'))); GBLog.info(`KB (.gbkb) assets accessible at: /kb/${botId}.gbai/${packageName}.`); } diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 55c4c0e9..e4016519 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -131,6 +131,7 @@ export class GBMinService { public async buildMin(instances: IGBInstance[]) { // Servers default UI on root address '/' if web enabled. + if (process.env.DISABLE_WEB !== 'true') { const url = GBServer.globals.wwwroot ? GBServer.globals.wwwroot @@ -139,6 +140,8 @@ export class GBMinService { GBServer.globals.server.use('/', express.static(url)); } + + // Servers the bot information object via HTTP so clients can get // instance information stored on server. @@ -216,12 +219,13 @@ export class GBMinService { await this.invokeLoadBot(GBServer.globals.appPackages, GBServer.globals.sysPackages, min); - // Serves individual URL for each bot conversational interface... + // Serves individual URL for each bot conversational interface. - const url = `/api/messages/${instance.botId}`; - GBServer.globals.server.post(url, async (req, res) => { + const receiver = async (req, res) => { await this.receiver(adapter, req, res, conversationState, min, instance, GBServer.globals.appPackages); - }); + }; + const url = `/api/messages/${instance.botId}`; + GBServer.globals.server.post(url, receiver); GBLog.info(`GeneralBots(${instance.engineName}) listening on: ${url}.`); // Serves individual URL for each bot user interface. @@ -237,7 +241,14 @@ export class GBMinService { uiUrlAlt, express.static(urlJoin(GBDeployer.deployFolder, GBMinService.uiPackage, 'build')) ); - + const domain = min.core.getParam(min.instance, 'Domain', null); + if (domain) { + GBServer.globals.server.use( + domain, + express.static(urlJoin(GBDeployer.deployFolder, GBMinService.uiPackage, 'build')) + ); + GBLog.info(`Bot UI ${GBMinService.uiPackage} accessible at custom domain: ${domain}.`); + } GBLog.info(`Bot UI ${GBMinService.uiPackage} accessible at: ${uiUrl} and ${uiUrlAlt}.`); } @@ -512,7 +523,12 @@ export class GBMinService { speechToken: speechToken, conversationId: webchatTokenContainer.conversationId, authenticatorTenant: instance.authenticatorTenant, - authenticatorClientId: instance.marketplaceId + authenticatorClientId: instance.marketplaceId, + paramLogoImageUrl: this.core.getParam(instance, 'Logo Image Url', null), + paramLogoImageAlt: this.core.getParam(instance, 'Logo Image Alt', null), + paramLogoImageWidth: this.core.getParam(instance, 'Logo Image Width', null), + paramLogoImageHeight: this.core.getParam(instance, 'Logo Image Height', null), + paramLogoImageType: this.core.getParam(instance, 'Logo Image Type', null) }) ); } else { @@ -603,6 +619,7 @@ export class GBMinService { if (GBServer.globals.minBoot === undefined) { GBServer.globals.minBoot = min; } + // TODO: min.appPackages = core.getPackagesByInstanceId(min.instance.instanceId); // Creates a hub of services available in .gbapps. @@ -735,7 +752,7 @@ export class GBMinService { user.welcomed = false; firstTime = true; - // Sends loadInstance event to .gbui clients. + // Sends loadInstance event to .gbui clients and loads FAQ. await min.conversationalService.sendEvent(min, step, 'loadInstance', { instanceId: instance.instanceId, @@ -743,6 +760,12 @@ export class GBMinService { theme: instance.theme ? instance.theme : 'default.gbtheme', secret: instance.webchatKey }); + const service = new KBService(min.core.sequelize); + const data = await service.getFaqBySubjectArray('faq', undefined); + await min.conversationalService.sendEvent(min, step, 'play', { + playerType: 'bullet', + data: data.slice(0, 10) + }); // This same event is dispatched either to all participants // including the bot, that is filtered bellow. diff --git a/packages/default.gbui/package-lock.json b/packages/default.gbui/package-lock.json index ee732c70..76b5e38a 100644 --- a/packages/default.gbui/package-lock.json +++ b/packages/default.gbui/package-lock.json @@ -1199,6 +1199,11 @@ "@types/yargs": "^13.0.0" } }, + "@midudev/react-static-content": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@midudev/react-static-content/-/react-static-content-1.0.4.tgz", + "integrity": "sha512-P2+rdqhysYNon5/noOoUiFJghVxbl64qGgxsKwe10mjrkQvgIy4vCcOLN5Nw00er/cBSuWY/hVHkuSeQ6sI5VA==" + }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -11919,6 +11924,37 @@ "shallowequal": "^1.0.1" } }, + "react-super-seo": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/react-super-seo/-/react-super-seo-1.0.5.tgz", + "integrity": "sha512-OtenkmcfjA97kaem/HxL0oaRO3hYhfalaoNt2I1nAVMzGh3I/xDWwy+J4ynA6LlPc+SqBY5BHcrRzjWT75hr1A==", + "requires": { + "react-helmet": "^6.0.0" + }, + "dependencies": { + "react-fast-compare": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", + "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" + }, + "react-helmet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", + "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", + "requires": { + "object-assign": "^4.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.1.1", + "react-side-effect": "^2.1.0" + } + }, + "react-side-effect": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.1.tgz", + "integrity": "sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ==" + } + } + }, "react-transition-group": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz", diff --git a/packages/default.gbui/package.json b/packages/default.gbui/package.json index 5c67911f..d163b5af 100644 --- a/packages/default.gbui/package.json +++ b/packages/default.gbui/package.json @@ -7,6 +7,7 @@ "license": "AGPL-3.0", "homepage": ".", "dependencies": { + "@midudev/react-static-content": "^1.0.4", "ajv": "^6.10.2", "botframework-directlinejs": "0.11.6", "botframework-webchat": "^4.7.1", @@ -20,6 +21,7 @@ "react-player": "^1.14.2", "react-powerbi": "0.5.2", "react-scripts": "^3.3.0", + "react-super-seo": "^1.0.5", "react-transition-group": "^4.3.0", "rxjs": "^6.5.4", "url-join": "4.0.1" diff --git a/packages/default.gbui/src/GBUIApp.js b/packages/default.gbui/src/GBUIApp.js index ad7c164c..8eb87bf3 100644 --- a/packages/default.gbui/src/GBUIApp.js +++ b/packages/default.gbui/src/GBUIApp.js @@ -37,12 +37,13 @@ import GBVideoPlayer from './players/GBVideoPlayer.js'; import GBLoginPlayer from './players/GBLoginPlayer.js'; import GBBulletPlayer from './players/GBBulletPlayer.js'; import SidebarMenu from './components/SidebarMenu.js'; +import SEO from './components/SEO.js'; import GBCss from './components/GBCss.js'; import { DirectLine } from 'botframework-directlinejs'; import { ConnectionStatus } from 'botframework-directlinejs'; import ReactWebChat from 'botframework-webchat'; import { UserAgentApplication } from 'msal'; - +import StaticContent from '@midudev/react-static-content' class GBUIApp extends React.Component { constructor() { @@ -138,7 +139,7 @@ class GBUIApp extends React.Component { } authenticate() { - + if (this.state.instanceClient.authenticatorClientId === null) { return; } @@ -343,14 +344,17 @@ class GBUIApp extends React.Component { } return ( -
- {gbCss} - {sideBar} -
{playerComponent}
-
- {chat} + + +
+ {gbCss} + {sideBar} +
{playerComponent}
+
+ {chat} +
-
+ ); } } diff --git a/packages/default.gbui/src/components/SEO.js b/packages/default.gbui/src/components/SEO.js new file mode 100644 index 00000000..e103f5bb --- /dev/null +++ b/packages/default.gbui/src/components/SEO.js @@ -0,0 +1,58 @@ +/*****************************************************************************\ +| ( )_ _ | +| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | +| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' v `\ /'_`\ | +| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) | +| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | +| | | ( )_) | | +| (_) \___/' | +| | +| 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. | +| | +\*****************************************************************************/ + +import React from "react" +import { SuperSEO } from 'react-super-seo'; + +const footer = () => ( + ); +export default footer \ No newline at end of file diff --git a/packages/default.gbui/src/components/SidebarMenu.js b/packages/default.gbui/src/components/SidebarMenu.js index 75eb2457..0db1ef5c 100644 --- a/packages/default.gbui/src/components/SidebarMenu.js +++ b/packages/default.gbui/src/components/SidebarMenu.js @@ -50,11 +50,10 @@ class SideBarMenu extends React.Component { return (
- General Bots Logo + General Bots Logo
@@ -72,16 +71,14 @@ class SideBarMenu extends React.Component {
this.send("showSubjects")} - > + onClick={() => this.send("showSubjects")}> Subjects
this.send("giveFeedback")} - > + onClick={() => this.send("giveFeedback")}> Suggestions
diff --git a/packages/kb.gbapp/services/KBService.ts b/packages/kb.gbapp/services/KBService.ts index 76a48c1d..2e094d8c 100644 --- a/packages/kb.gbapp/services/KBService.ts +++ b/packages/kb.gbapp/services/KBService.ts @@ -44,6 +44,7 @@ const walkPromise = require('walk-promise'); // tslint:disable-next-line:newline-per-chained-call const { SearchService } = require('azure-search-client'); const Excel = require('exceljs'); +const getSlug = require('speakingurl'); import { GBDialogStep, GBLog, @@ -53,15 +54,14 @@ import { IGBInstance, IGBKBService } from 'botlib'; +import { GBAdminService } from '../../admin.gbapp/services/GBAdminService'; import { CollectionUtil } from 'pragmatismo-io-framework'; import { Op } from 'sequelize'; import { Sequelize } from 'sequelize-typescript'; -import { GBServer } from '../../../src/app'; import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService'; import { GuaribasPackage } from '../../core.gbapp/models/GBModel'; import { GBDeployer } from '../../core.gbapp/services/GBDeployer'; import { CSService } from '../../customer-satisfaction.gbapp/services/CSService'; -import { SecService } from '../../security.gbapp/services/SecService'; import { GuaribasAnswer, GuaribasQuestion, GuaribasSubject } from '../models'; import { Messages } from '../strings'; import { GBConfigService } from './../../core.gbapp/services/GBConfigService'; @@ -137,6 +137,54 @@ export class KBService implements IGBKBService { }); } + /** + * Returns a question object given a SEO friendly URL. + */ + public async getQuestionIdFromURL(core: IGBCoreService, url: string) { + + // Extracts questionId from URL. + + const id = url.substr(url.lastIndexOf('-') + 1); + + // Extracts botId from URL. + + let path = /(http[s]?:\/\/)?([^\/\s]+\/)(.*)/gi; + const botId = url.replace(path, ($0, $1, $2, $3) => { + return $3.substr($3.indexOf('/')); + }); + + // Finds the associated question. + + const instance = await core.loadInstanceByBotId(botId); + const question = await GuaribasQuestion.findAll({ + where: { + instanceId: instance.instanceId, + questionId: id + } + }); + + return question; + } + + public async getQuestionsSEO(instanceId: number) { + + const questions = await GuaribasQuestion.findAll({ + where: { + instanceId: instanceId + } + }); + + let output = []; + for (let i = 0; i < questions.length; i++) { + const answer = questions[i]; + const text = getSlug(answer.content); + let url = `${text}-${i}`; + output.push(url); + } + + return output; + } + public async getAnswerByText(instanceId: number, text: string): Promise { text = text.trim(); const service = new CSService(); @@ -519,6 +567,7 @@ export class KBService implements IGBKBService { } }); } + public async importKbTabularDirectory(localPath: string, instance: IGBInstance, packageId: number): Promise { const files = await walkPromise(localPath); diff --git a/src/app.ts b/src/app.ts index 0e5fd67f..9c9e2635 100644 --- a/src/app.ts +++ b/src/app.ts @@ -62,8 +62,8 @@ export class RootData { public appPackages: any[]; // Loaded .gbapp package list public minService: GBMinService; // Minimalist service core public bootInstance: IGBInstance; // General Bot Interface Instance - public minInstances: any[]; // - public minBoot: GBMinInstance; + public minInstances: any[]; // List of bot instances. + public minBoot: GBMinInstance; // Reference to boot bot. public wwwroot: string; // .gbui or a static webapp. public entryPointDialog: string; // To replace default welcome dialog. }