* Error handling improved and logging enriched as well.
* Setting DATABASE_ is now STORAGE_.
This commit is contained in:
parent
c2d0ef3f2e
commit
b922a5b413
12 changed files with 145 additions and 77 deletions
|
@ -65,8 +65,8 @@ Notes:
|
|||
### Configure the server to deploy specific directory
|
||||
|
||||
1. Create/Edit the .env file and add the ADDITIONAL_DEPLOY_PATH key pointing to the .gbai local parent folder of .gbapp, .gbot, .gbtheme, .gbkb package directories.
|
||||
2. Specify DATABASE_SYNC to TRUE so database sync is run when the server is run.
|
||||
3. In case of Microsoft SQL Server add the following keys: DATABASE_HOST, DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_DIALECT to `mssql`.
|
||||
2. Specify STORAGE_SYNC to TRUE so database sync is run when the server is run.
|
||||
3. In case of Microsoft SQL Server add the following keys: STORAGE_HOST, STORAGE_NAME, STORAGE_USERNAME, STORAGE_PASSWORD, STORAGE_DIALECT to `mssql`.
|
||||
|
||||
Note:
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# Release History
|
||||
|
||||
## Version 0.1.4
|
||||
|
||||
|
||||
## Version 0.1.3
|
||||
|
||||
* FIX: Admin now is internationalized.
|
||||
|
@ -24,8 +27,8 @@
|
|||
## Version 0.0.30
|
||||
|
||||
- FIX: Packages updated.
|
||||
- NEW: DATABASE_SYNC_ALTER environment parameter.
|
||||
- NEW: DATABASE_SYNC_FORCE environment parameter.
|
||||
- NEW: STORAGE_SYNC_ALTER environment parameter.
|
||||
- NEW: STORAGE_SYNC_FORCE environment parameter.
|
||||
- NEW: Define constraint names in MSSQL.
|
||||
|
||||
## Version 0.0.29
|
||||
|
@ -44,7 +47,7 @@
|
|||
|
||||
- FIX: Packages updated.
|
||||
- NEW: If a bot package's name begins with '.', then it is ignored.
|
||||
- NEW: Created DATABASE_LOGGING environment parameter.
|
||||
- NEW: Created STORAGE_LOGGING environment parameter.
|
||||
|
||||
## Version 0.0.25
|
||||
|
||||
|
|
|
@ -48,7 +48,6 @@ import { Messages } from "../strings";
|
|||
* Dialogs for administration tasks.
|
||||
*/
|
||||
export class AdminDialog extends IGBDialog {
|
||||
|
||||
static async undeployPackageCommand(text: any, min: GBMinInstance) {
|
||||
let packageName = text.split(" ")[1];
|
||||
let importer = new GBImporter(min.core);
|
||||
|
@ -59,9 +58,7 @@ export class AdminDialog extends IGBDialog {
|
|||
);
|
||||
}
|
||||
|
||||
static async deployPackageCommand(text: string,
|
||||
deployer: GBDeployer
|
||||
) {
|
||||
static async deployPackageCommand(text: string, deployer: GBDeployer) {
|
||||
let packageName = text.split(" ")[1];
|
||||
let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH");
|
||||
await deployer.deployPackageFromLocalPath(
|
||||
|
@ -92,10 +89,7 @@ export class AdminDialog extends IGBDialog {
|
|||
password === GBConfigService.get("ADMIN_PASS") &&
|
||||
GBAdminService.StrongRegex.test(password)
|
||||
) {
|
||||
|
||||
await dc.context.sendActivity(
|
||||
Messages[locale].welcome
|
||||
);
|
||||
await dc.context.sendActivity(Messages[locale].welcome);
|
||||
await dc.prompt("textPrompt", Messages[locale].which_task);
|
||||
} else {
|
||||
await dc.prompt("textPrompt", Messages[locale].wrong_password);
|
||||
|
@ -105,11 +99,12 @@ export class AdminDialog extends IGBDialog {
|
|||
async (dc, value) => {
|
||||
const locale = dc.context.activity.locale;
|
||||
var text = value;
|
||||
const user = min.userState.get(dc.context);
|
||||
let cmdName = text.split(" ")[0];
|
||||
dc.context.sendActivity(Messages[locale].working(cmdName))
|
||||
|
||||
dc.context.sendActivity(Messages[locale].working(cmdName));
|
||||
let unknownCommand = false;
|
||||
|
||||
if (text === "quit") {
|
||||
user.authenticated = false;
|
||||
await dc.replace("/");
|
||||
} else if (cmdName === "deployPackage") {
|
||||
await AdminDialog.deployPackageCommand(text, deployer);
|
||||
|
@ -124,12 +119,19 @@ export class AdminDialog extends IGBDialog {
|
|||
await dc.replace("/admin", { firstRun: false });
|
||||
} else if (cmdName === "setupSecurity") {
|
||||
await AdminDialog.setupSecurity(min, dc);
|
||||
} else {
|
||||
unknownCommand = true;
|
||||
}
|
||||
else{
|
||||
|
||||
if (unknownCommand) {
|
||||
await dc.context.sendActivity(Messages[locale].unknown_command);
|
||||
dc.endAll()
|
||||
await dc.replace("/answer", { query: text });
|
||||
} else {
|
||||
await dc.context.sendActivity(
|
||||
Messages[locale].finshed_working(cmdName)
|
||||
);
|
||||
}
|
||||
await dc.endAll();
|
||||
await dc.replace("/answer", { query: text });
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
@ -152,8 +154,6 @@ export class AdminDialog extends IGBDialog {
|
|||
min.instance.botId
|
||||
}/token&state=${state}&response_mode=query`;
|
||||
|
||||
await dc.context.sendActivity(
|
||||
Messages[locale].consent(url)
|
||||
);
|
||||
await dc.context.sendActivity(Messages[locale].consent(url));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@ export const Messages = {
|
|||
authenticate: "Please, authenticate:",
|
||||
welcome: "Welcome to Pragmatismo.io GeneralBots Administration.",
|
||||
which_task: "Which task do you wanna run now?",
|
||||
working:(command)=> `I'm working on ${command}`,
|
||||
working:(command)=> `I'm working on ${command}...`,
|
||||
finshed_working:"Done.",
|
||||
unknown_command: text =>
|
||||
`Well, but ${text} is not a administrative General Bots command, I will try to search for it.`,
|
||||
hi: text => `Hello, ${text}.`,
|
||||
|
|
|
@ -58,11 +58,11 @@ export class GBConfigService {
|
|||
|
||||
if (!value) {
|
||||
switch (key) {
|
||||
case "DATABASE_DIALECT":
|
||||
case "STORAGE_DIALECT":
|
||||
value = "sqlite"
|
||||
break
|
||||
|
||||
case "DATABASE_STORAGE":
|
||||
case "STORAGE_STORAGE":
|
||||
value = "./guaribas.sqlite"
|
||||
break
|
||||
|
||||
|
@ -70,17 +70,17 @@ export class GBConfigService {
|
|||
value = undefined
|
||||
break
|
||||
|
||||
case "DATABASE_SYNC":
|
||||
case "DATABASE_SYNC_ALTER":
|
||||
case "DATABASE_SYNC_FORCE":
|
||||
case "STORAGE_SYNC":
|
||||
case "STORAGE_SYNC_ALTER":
|
||||
case "STORAGE_SYNC_FORCE":
|
||||
value = "false"
|
||||
break
|
||||
|
||||
case "DATABASE_LOGGING":
|
||||
case "STORAGE_LOGGING":
|
||||
value = "false"
|
||||
break
|
||||
|
||||
case "DATABASE_ENCRYPT":
|
||||
case "STORAGE_ENCRYPT":
|
||||
value = "true"
|
||||
break
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ import { LuisRecognizer } from "botbuilder-ai";
|
|||
import { MessageFactory } from "botbuilder";
|
||||
import { Messages } from "../strings";
|
||||
import { AzureText } from "pragmatismo-io-framework";
|
||||
import { any } from "bluebird";
|
||||
const Nexmo = require("nexmo");
|
||||
|
||||
export interface LanguagePickerSettings {
|
||||
|
@ -103,31 +104,44 @@ export class GBConversationalService implements IGBConversationalService {
|
|||
subscriptionKey: min.instance.nlpSubscriptionKey,
|
||||
serviceEndpoint: min.instance.nlpServerUrl
|
||||
});
|
||||
let res = await model.recognize(dc.context);
|
||||
|
||||
let nlp: any;
|
||||
try {
|
||||
nlp = await model.recognize(dc.context);
|
||||
} catch (error) {
|
||||
let msg = `Error calling NLP server, check if you have a published model and assigned keys on the service. Error: ${
|
||||
error.statusCode ? error.statusCode : ""
|
||||
} ${error.message}`;
|
||||
return Promise.reject(new Error(msg));
|
||||
}
|
||||
|
||||
// Resolves intents returned from LUIS.
|
||||
|
||||
let topIntent = LuisRecognizer.topIntent(res);
|
||||
let topIntent = LuisRecognizer.topIntent(nlp);
|
||||
if (topIntent) {
|
||||
var intent = topIntent;
|
||||
var entity =
|
||||
res.entities && res.entities.length > 0
|
||||
? res.entities[0].entity.toUpperCase()
|
||||
nlp.entities && nlp.entities.length > 0
|
||||
? nlp.entities[0].entity.toUpperCase()
|
||||
: null;
|
||||
|
||||
if (intent === "None") {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
logger.info("NLP called:" + intent + ", " + entity);
|
||||
|
||||
try {
|
||||
await dc.replace("/" + intent, res.entities);
|
||||
await dc.replace("/" + intent, nlp.entities);
|
||||
return Promise.resolve(true);
|
||||
} catch (error) {
|
||||
let msg = `Error running NLP (${intent}): ${error}`;
|
||||
logger.info(msg);
|
||||
return Promise.reject(msg);
|
||||
let msg = `Error finding dialog associated to NLP event: ${intent}: ${
|
||||
error.message
|
||||
}`;
|
||||
return Promise.reject(new Error(msg));
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
} else {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
async checkLanguage(dc, min, text) {
|
||||
|
|
|
@ -77,7 +77,7 @@ export class GBCoreService implements IGBCoreService {
|
|||
* Constructor retrieves default values.
|
||||
*/
|
||||
constructor() {
|
||||
this.dialect = GBConfigService.get("DATABASE_DIALECT")
|
||||
this.dialect = GBConfigService.get("STORAGE_DIALECT")
|
||||
this.adminService = new GBAdminService(this)
|
||||
}
|
||||
|
||||
|
@ -94,22 +94,22 @@ export class GBCoreService implements IGBCoreService {
|
|||
let storage: string | undefined
|
||||
|
||||
if (this.dialect === "mssql") {
|
||||
host = GBConfigService.get("DATABASE_HOST")
|
||||
database = GBConfigService.get("DATABASE_NAME")
|
||||
username = GBConfigService.get("DATABASE_USERNAME")
|
||||
password = GBConfigService.get("DATABASE_PASSWORD")
|
||||
host = GBConfigService.get("STORAGE_HOST")
|
||||
database = GBConfigService.get("STORAGE_NAME")
|
||||
username = GBConfigService.get("STORAGE_USERNAME")
|
||||
password = GBConfigService.get("STORAGE_PASSWORD")
|
||||
} else if (this.dialect === "sqlite") {
|
||||
storage = GBConfigService.get("DATABASE_STORAGE")
|
||||
storage = GBConfigService.get("STORAGE_STORAGE")
|
||||
}
|
||||
|
||||
let logging =
|
||||
GBConfigService.get("DATABASE_LOGGING") === "true"
|
||||
GBConfigService.get("STORAGE_LOGGING") === "true"
|
||||
? (str: string) => {
|
||||
logger.info(str)
|
||||
}
|
||||
: false
|
||||
|
||||
let encrypt = GBConfigService.get("DATABASE_ENCRYPT") === "true"
|
||||
let encrypt = GBConfigService.get("STORAGE_ENCRYPT") === "true"
|
||||
|
||||
this.sequelize = new Sequelize({
|
||||
host: host,
|
||||
|
@ -247,9 +247,9 @@ export class GBCoreService implements IGBCoreService {
|
|||
}
|
||||
|
||||
async syncDatabaseStructure() {
|
||||
if (GBConfigService.get("DATABASE_SYNC") === "true") {
|
||||
const alter = GBConfigService.get("DATABASE_SYNC_ALTER") === "true"
|
||||
const force = GBConfigService.get("DATABASE_SYNC_FORCE") === "true"
|
||||
if (GBConfigService.get("STORAGE_SYNC") === "true") {
|
||||
const alter = GBConfigService.get("STORAGE_SYNC_ALTER") === "true"
|
||||
const force = GBConfigService.get("STORAGE_SYNC_FORCE") === "true"
|
||||
logger.info("Syncing database...")
|
||||
return this.sequelize.sync({
|
||||
alter: alter,
|
||||
|
@ -258,7 +258,6 @@ export class GBCoreService implements IGBCoreService {
|
|||
} else {
|
||||
let msg = "Database synchronization is disabled.";
|
||||
logger.info(msg)
|
||||
return Promise.reject(msg)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -152,7 +152,11 @@ export class GBDeployer {
|
|||
.done(async result => {
|
||||
logger.info(`App Package deployment done.`);
|
||||
|
||||
await core.syncDatabaseStructure();
|
||||
try{
|
||||
await core.syncDatabaseStructure();
|
||||
}catch(e){
|
||||
throw e;
|
||||
}
|
||||
|
||||
/** Deploys all .gbot files first. */
|
||||
|
||||
|
@ -212,7 +216,7 @@ export class GBDeployer {
|
|||
})
|
||||
.done(function(result) {
|
||||
if (botPackages.length === 0) {
|
||||
logger.info(
|
||||
logger.warn(
|
||||
"The server is running with no bot instances, at least one .gbot file must be deployed."
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -62,6 +62,8 @@ import {
|
|||
IGBCoreService,
|
||||
IGBConversationalService
|
||||
} from "botlib";
|
||||
import { GuaribasInstance } from "../models/GBModel";
|
||||
import { Messages } from "../strings";
|
||||
|
||||
/** Minimal service layer for a bot. */
|
||||
|
||||
|
@ -103,7 +105,8 @@ export class GBMinService {
|
|||
|
||||
async buildMin(
|
||||
server: any,
|
||||
appPackages: Array<IGBPackage>
|
||||
appPackages: Array<IGBPackage>,
|
||||
instances:GuaribasInstance[]
|
||||
): Promise<GBMinInstance> {
|
||||
// Serves default UI on root address '/'.
|
||||
|
||||
|
@ -112,10 +115,7 @@ export class GBMinService {
|
|||
"/",
|
||||
express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, "build"))
|
||||
);
|
||||
|
||||
// Loads all bot instances from storage and starting loading them.
|
||||
|
||||
let instances = await this.core.loadInstances();
|
||||
|
||||
Promise.all(
|
||||
instances.map(async instance => {
|
||||
// Gets the authorization key for each instance from Bot Service.
|
||||
|
@ -363,11 +363,15 @@ export class GBMinService {
|
|||
instance: any,
|
||||
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"; // TODO: Make dynamic.
|
||||
|
||||
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);
|
||||
|
||||
if (!user.loaded) {
|
||||
|
@ -459,10 +463,14 @@ export class GBMinService {
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
let msg = `Error in main activity: ${error.message} ${
|
||||
let msg = `ERROR: ${error.message} ${
|
||||
error.stack ? error.stack : ""
|
||||
}`;
|
||||
logger.error(msg);
|
||||
|
||||
await dc.context.sendActivity(Messages[dc.context.activity.locale].very_sorry_about_error)
|
||||
await dc.begin("/ask", { isReturning: true });
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -487,10 +495,9 @@ export class GBMinService {
|
|||
return Promise.resolve(JSON.parse(json));
|
||||
} catch (error) {
|
||||
let msg = `Error calling Direct Line client, verify Bot endpoint on the cloud. Error is: ${error}.`;
|
||||
logger.error(msg);
|
||||
return Promise.reject(msg);
|
||||
return Promise.reject(new Error(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Speech to Text / Text to Speech token from the provider.
|
||||
|
@ -513,8 +520,7 @@ export class GBMinService {
|
|||
return await request(options);
|
||||
} catch (error) {
|
||||
let msg = `Error calling Speech to Text client. Error is: ${error}.`;
|
||||
logger.error(msg);
|
||||
return Promise.reject(msg);
|
||||
return Promise.reject(new Error(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,16 @@ export const Messages = {
|
|||
good_morning: "good morning",
|
||||
good_evening: "good evening",
|
||||
good_night: "good night",
|
||||
hi: (msg ) => `Hello, ${msg}.`
|
||||
hi: (msg ) => `Hello, ${msg}.`,
|
||||
very_sorry_about_error: `I'm sorry to inform that there was an error which was recorded to be solved.`
|
||||
|
||||
},
|
||||
"pt-BR": {
|
||||
show_video: "Vou te mostrar um vídeo. Por favor, aguarde...",
|
||||
good_morning: "bom dia",
|
||||
good_evening: "boa tarde",
|
||||
good_night: "boa noite",
|
||||
hi: (msg ) => `Oi, ${msg}.`
|
||||
hi: (msg ) => `Oi, ${msg}.`,
|
||||
very_sorry_about_error: `Lamento, ocorreu um erro que já foi registrado para ser tratado.`
|
||||
}
|
||||
};
|
||||
|
|
|
@ -148,7 +148,7 @@ export class KBService {
|
|||
}
|
||||
// TODO: Filter by instance. what = `${what}&$filter=instanceId eq ${instanceId}`
|
||||
try {
|
||||
if (instance.searchKey && GBConfigService.get("DATABASE_DIALECT") == "mssql") {
|
||||
if (instance.searchKey && GBConfigService.get("STORAGE_DIALECT") == "mssql") {
|
||||
let service = new AzureSearch(
|
||||
instance.searchKey,
|
||||
instance.searchHost,
|
||||
|
@ -179,7 +179,7 @@ export class KBService {
|
|||
}
|
||||
}
|
||||
catch (reason) {
|
||||
return Promise.reject(reason)
|
||||
return Promise.reject(new Error(reason));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
46
src/app.ts
46
src/app.ts
|
@ -55,6 +55,7 @@ import { GBAdminPackage } from "../deploy/admin.gbapp/index";
|
|||
import { GBCustomerSatisfactionPackage } from "../deploy/customer-satisfaction.gbapp";
|
||||
import { IGBPackage } from "botlib";
|
||||
import { GBAdminService } from "../deploy/admin.gbapp/services/GBAdminService";
|
||||
import { GuaribasInstance } from "deploy/core.gbapp/models/GBModel";
|
||||
|
||||
let appPackages = new Array<IGBPackage>();
|
||||
|
||||
|
@ -62,7 +63,10 @@ let appPackages = new Array<IGBPackage>();
|
|||
* General Bots open-core entry point.
|
||||
*/
|
||||
export class GBServer {
|
||||
/** Program entry-point. */
|
||||
/**
|
||||
* Program entry-point.
|
||||
*/
|
||||
|
||||
static run() {
|
||||
// Creates a basic HTTP server that will serve several URL, one for each
|
||||
// bot instance. This allows the same server to attend multiple Bot on
|
||||
|
@ -108,6 +112,8 @@ export class GBServer {
|
|||
);
|
||||
}
|
||||
|
||||
// Creates the minimal service shared across all .gbapps.
|
||||
|
||||
let minService = new GBMinService(
|
||||
core,
|
||||
conversationalService,
|
||||
|
@ -132,16 +138,48 @@ export class GBServer {
|
|||
p.loadPackage(core, core.sequelize);
|
||||
});
|
||||
|
||||
// Loads all bot instances from object storage, if it's formatted.
|
||||
|
||||
logger.info(`All instances are being now loaded...`);
|
||||
let instances: GuaribasInstance[];
|
||||
try {
|
||||
instances = await core.loadInstances();
|
||||
} catch (error) {
|
||||
// Check if storage is empty and needs formatting.
|
||||
|
||||
let isInvalidObject =
|
||||
error.parent.number == 208 || error.parent.errno == 1; // MSSQL or SQLITE.
|
||||
if (
|
||||
isInvalidObject &&
|
||||
GBConfigService.get("STORAGE_SYNC") !== "true"
|
||||
) {
|
||||
throw `Operating storage is out of sync or there is a storage connection error. Try setting STORAGE_SYNC to true in .env file. Error: ${
|
||||
error.message
|
||||
}.`;
|
||||
}
|
||||
}
|
||||
|
||||
// Deploy packages and format object store according to .gbapp storage models.
|
||||
|
||||
logger.info(`Deploying packages.`);
|
||||
await deployer.deployPackages(core, server, appPackages);
|
||||
|
||||
// If instances is undefined here it's because storage has been formatted.
|
||||
// Load all instances from .gbot found on deploy package directory.
|
||||
if (!instances) {
|
||||
instances = await core.loadInstances();
|
||||
}
|
||||
|
||||
// Setup server dynamic (per bot instance) resources and listeners.
|
||||
|
||||
logger.info(`Building minimal instances.`);
|
||||
await minService.buildMin(server, appPackages);
|
||||
|
||||
logger.info(`All instances are now loaded and available.`);
|
||||
await minService.buildMin(server, appPackages, instances);
|
||||
logger.info(`The Bot Server is in RUNNING mode...`);
|
||||
|
||||
return core;
|
||||
} catch (err) {
|
||||
logger.info(`STOP: ${err} ${err.stack ? err.stack : ""}`);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue