Allow object of objects within inputs in action - node.js

I'm wondering how I have objects of objects as inputs in an action - for example,
module.exports = {
friendlyName: 'Signup',
description: 'Signup a user for an account.',
inputs: {
bride: {
firstName: {
description: 'The first name of the bride',
type: 'string',
required: true
}
},
groom: {
lastName: {
description: 'The first name of the groom',
type: 'string',
required: true
}
}
}
exits: {},
fn: async function (inputs, exits) {
sails.log.debug(inputs.bride.firstName); // I would like to be able to reference input like this
return exits.success();
}
};
How can I do this and access inputs.bride.firstName (instead of having to do inputs.brideFirstName)?
I receive the following error trying to do this:
'Invalid input definition ("bride"). Must have `type`, or at least some more information. (If you aren\'t sure what to use, just go with `type: \'ref\'.)' ] } }

Another way is to implement a custom validation with objects of objects
module.exports = {
friendlyName: 'Signup',
description: 'Signup a user for an account.',
inputs: {
bride: {
description: 'The first name of the bride',
type: 'json', // {'firstName': 'luis'}
custom: function(value) {
return _.isObject(value) && _.isString(value.firstName)
}
},
groom: {
description: 'The first name of the groom',
type: 'json', // {'lastname': 'lazy'}
custom: function(value) {
return _.isObject(value) && _.isString(value.lastName)
}
}
}
exits: {},
fn: async function (inputs, exits) {
sails.log.debug(inputs.bride.firstName); // luis
return exits.success();
}
};
More information on: https://sailsjs.com/documentation/concepts/models-and-orm/validations#?custom-validation-rules

You should use a json type for this case. Sails don't check attributes in json type.
module.exports = {
friendlyName: 'Signup',
description: 'Signup a user for an account.',
inputs: {
bride: {
description: 'The first name of the bride',
type: 'json', // {'firstName': 'luis'}
required: true
},
groom: {
description: 'The first name of the groom',
type: 'json', // {'lastname': 'lazy'}
required: true
}
}
exits: {},
fn: async function (inputs, exits) {
sails.log.debug(inputs.bride.firstName); // luis
return exits.success();
}
};
More info about types: https://sailsjs.com/documentation/concepts/models-and-orm/attributes#?type

Related

Serverless Event object failed validation at createError

I am working on a serverless/nodejs12 project. The project deploys fine, but when I try to invoke the endpoint with postman I get the following error. I have browsed through many postings of similar errors but still failed to understand what's going on. Will appreciate any pointers.
Thank you
at createError (/var/task/src/handlers/webpack:/home/serverless-workspace/notification/node_modules/#middy/util/index.js:259:1)
at validatorMiddlewareBefore (/var/task/src/handlers/webpack:/home/serverless-workspace/notification/node_modules/#middy/validator/index.js:55:1)
at runMiddlewares (/var/task/src/handlers/webpack:/home/serverless-workspace/notification/node_modules/#middy/core/index.js:120:1)
at runRequest (/var/task/src/handlers/webpack:/home/serverless-workspace/notification/node_modules/#middy/core/index.js:80:1) {
details: [
{
instancePath: '/body',
schemaPath: '#/properties/body/type',
keyword: 'type',
params: [Object],
message: 'must be object'
}
]
createNotification.js
--------------------------
async function createNotification(event, context) {
const { title } = event.body;
const { destination } = event.body;
const { destinationType } = event.body
const notification = {
id: uuid(),
title,
destination,
destinationType,
status: 'OPEN',
createdAt: new Date().toISOString(),
}
try {
await dynamodb.put({
TableName: process.env.NOTIFY_TABLE_NAME,
Item: notification
}).promise();
} catch (error) {
console.error(error);
throw new createError.InternalServerError(error);
}
return {
statusCode: 200,
body: JSON.stringify(notification),
};
}
export const handler = commonMiddleware(createNotification)
.use(validator({ inputSchema: createNotificationSchema }));
and the schema
----------------------
const schema = {
type: 'object',
properties: {
body: {
type: 'object',
required: ['status'],
default: { status: 'OPEN' },
properties: {
status: {
default: 'OPEN',
enum: ['OPEN', 'CLOSED'],
},
},
},
},
required: ['body'],
};
export default schema;
You're getting the error must be object. I'm guessing your input looks like { event: { body: 'JSON string' } }. You'll need to use another middy middleware to parse the body prior to validating the input. Which middleware will depend on what AWS event it's expecting. Middy >3.0.0 supports all AWS events.

Unable to Intercept Bot Response in Azure Bot Framework

I want to intercept all the messages exchanged between user and bot. PFB code that I have used to intercept. I am using Bot Diaolog framework for webchat. Here the issue is I am not able to extract values from activities object which contains messages sent from bot to user.
adapter.use(async (turnContext, next) => {
// pre-processing of the current incoming activity
console.log("adapture use::turnContext::" + turnContext.activity.text);
// hook up a handler to process any outgoing activities sent during this turn
turnContext.onSendActivities(async (sendContext, activities, nextSend) => {
// pre-processing of outgoing activities
await nextSend();
console.log("adapture use::activities::POST##::" + flat.stringify(activities));
// post-processing outgoing activities
});
await next();
// post-processing of the current incoming activity
console.log(`Processing activity ${turnContext.activity.id} finishing. `);
});
To access the bot's responses, you can update the bot.js file (i.e. the file with the activity handlers defined) to something like the below.
In short, the onSendActivities() method keeps an array of the activities that have been passed in. You can cycle thru the activities pushing them into an array and then acting on a particular one. Or, react as each arrives. Further below are examples of each output.
const { ActivityHandler, MessageFactory } = require('botbuilder');
class EchoBot extends ActivityHandler {
constructor() {
super();
[...]
const transcript = [];
this.onTurn(async (context, next1) => {
let responses = {};
logActivity(transcript, cloneActivity(context.activity));
context.onSendActivities(async (ctx, activities, next2) => {
responses = await next2();
activities.forEach((a) => logActivity(transcript, cloneActivity(a)));
console.log('TRANSCRIPT ', activities);
return responses;
});
await next1();
});
}
}
const logActivity = (transcript, activity) => {
if (!activity.timestamp) {
activity.timestamp = new Date();
}
transcript.push(activity);
};
const cloneActivity = (activity) => {
return Object.assign({}, activity);
};
module.exports.EchoBot = EchoBot;
Logging the transcript array: Shows the list of activities.
[
{
type: 'conversationUpdate',
id: '1hEUP37Da8S',
timestamp: 2021-09-07T23:01:04.910Z,
serviceUrl: 'https://directline.botframework.com/',
channelId: 'directline',
from: { id: 'dl_16310556645490.nc93iu9jr1' },
conversation: { id: '5JgOxxxxxxxxxxxxv6sw-g' },
recipient: { id: 'somebot#QaeuoeEamLg', name: 'Some Bot' },
membersAdded: [ [Object], [Object] ],
rawTimestamp: '2021-09-07T23:01:04.9109865Z',
callerId: 'urn:botframework:azure'
},
{
type: 'message',
text: 'Hello and welcome!',
inputHint: 'acceptingInput',
speak: 'Hello and welcome!',
channelId: 'directline',
locale: undefined,
serviceUrl: 'https://directline.botframework.com/',
conversation: { id: '5JgOxxxxxxxxxxxxv6sw-g' },
from: { id: 'somebot#QaeuoeEamLg', name: 'Some Bot' },
recipient: { id: 'dl_16310556645490.nc93iu9jr1' },
timestamp: 2021-09-07T23:01:06.547Z
},
{
type: 'message',
id: '5JgOxxxxxxxxxxxxv6sw-g|0000001',
timestamp: 2021-09-07T23:01:08.704Z,
localTimestamp: 2021-09-07T23:01:08.527Z,
localTimezone: 'America/Los_Angeles',
serviceUrl: 'https://directline.botframework.com/',
channelId: 'directline',
from: { id: 'dl_16310556645490.nc93iu9jr1', name: '' },
conversation: { id: '5JgOxxxxxxxxxxxxv6sw-g' },
recipient: { id: 'somebot#QaeuoeEamLg', name: 'Some Bot' },
textFormat: 'plain',
locale: 'en-US',
text: 'Hi',
entities: [ [Object] ],
channelData: {
clientActivityID: '1631055668527tzwhm47a4qd',
clientTimestamp: '2021-09-07T23:01:08.527Z'
},
rawTimestamp: '2021-09-07T23:01:08.704318Z',
rawLocalTimestamp: '2021-09-07T16:01:08.527-07:00',
callerId: 'urn:botframework:azure'
},
{
type: 'message',
text: 'Echo: Hi',
inputHint: 'acceptingInput',
speak: 'Echo: Hi',
channelId: 'directline',
locale: 'en-US',
serviceUrl: 'https://directline.botframework.com/',
conversation: { id: '5JgOxxxxxxxxxxxxv6sw-g' },
from: { id: 'somebot#QaeuoeEamLg', name: 'Some Bot' },
recipient: { id: 'dl_16310556645490.nc93iu9jr1', name: '' },
replyToId: '5JgOxxxxxxxxxxxxv6sw-g|0000001',
timestamp: 2021-09-07T23:01:09.147Z
}
]
Logging activities only: Shows the last item to have passed thru from either the bot or user.
[
{
type: 'message',
text: 'Echo: Hi',
inputHint: 'acceptingInput',
speak: 'Echo: Hi',
channelId: 'directline',
locale: 'en-US',
serviceUrl: 'https://directline.botframework.com/',
conversation: { id: 'AURKxxxxxxxxxJHpO-f' },
from: { id: 'somebot#QaeuoeEamLg', name: 'Some Bot' },
recipient: { id: 'dl_16310605730140.3nrm4evq6um', name: '' },
replyToId: 'AURKxxxxxxxxxJHpO-f|0000001'
}
]
Thank you Rajeesh Menoth. Posting your suggestions as an answer to help other community members.
For updating existing message we can pass NewActivityObject with the existing activity ID to the UpdateActivity method of the TurnContext Object
Below is the sample to pass new object ID
const newActivity = MessageFactory.text('The new text for the activity');
newActivity.id = activityId;
await turnContext.updateActivity(newActivity);
To update the existing card on button selection, you can use ReplyToId of incoming activity.
const message = MessageFactory.attachment(card);
message.id = context.activity.replyToId;
await context.updateActivity(message);
For Further information check Update Messages.

How to grab field value during a MongooseModel.bulkWrite operation?

Context:
I am trying to upsert in bulk an array of data, with an additional computed field: 'status'.
Status should be either :
- 'New' for newly inserted docs;
- 'Removed' for docs present in DB, but inexistent in incoming dataset;
- a percentage explaining the evolution for the field price, comparing the value in DB to the one in incoming dataset.
Implementations:
data.model.ts
import { Document, model, Model, models, Schema } from 'mongoose';
import { IPertinentData } from './site.model';
const dataSchema: Schema = new Schema({
sourceId: { type: String, required: true },
name: { type: String, required: true },
price: { type: Number, required: true },
reference: { type: String, required: true },
lastModified: { type: Date, required: true },
status: { type: Schema.Types.Mixed, required: true }
});
export interface IData extends IPertinentData, Document {}
export const Data: Model<IData> = models.Data || model<IData>('Data', dataSchema);
data.service.ts
import { Data, IPertinentData } from '../models';
export class DataService {
static async test() {
// await Data.deleteMany({});
const data = [
{
sourceId: 'Y',
reference: `y0`,
name: 'y0',
price: 30
},
{
sourceId: 'Y',
reference: 'y1',
name: 'y1',
price: 30
}
];
return Data.bulkWrite(
data.map(function(d) {
let status = '';
// #ts-ignore
console.log('price', this);
// #ts-ignore
if (!this.price) status = 'New';
// #ts-ignore
else if (this.price !== d.price) {
// #ts-ignore
status = (d.price - this.price) / this.price;
}
return {
updateOne: {
filter: { sourceId: d.sourceId, reference: d.reference },
update: {
$set: {
// Set percentage value when current price is greater/lower than new price
// Set status to nothing when new and current prices match
status,
name: d.name,
price: d.price
},
$currentDate: {
lastModified: true
}
},
upsert: true
}
};
}
)
);
}
}
... then in my backend controller, i just call it with some route :
try {
const results = await DataService.test();
return new HttpResponseOK(results);
} catch (error) {
return new HttpResponseInternalServerError(error);
}
Problem:
I've tried lot of implementation syntaxes, but all failed either because of type casting, and unsupported syntax like the $ symbol, and restrictions due to the aggregation...
I feel like the above solution might be closest to a working scenario but i'm missing a way to grab the value of the price field BEFORE the actual computation of status and the replacement with updated value.
Here the value of this is undefined while it is supposed to point to current document.
Questions:
Am i using correct Mongoose way for a bulk update ?
if yes, how to get the field value ?
Environment:
NodeJS 13.x
Mongoose 5.8.1
MongoDB 4.2.1
EUREKA !
Finally found a working syntax, pfeeeew...
...
return Data.bulkWrite(
data.map(d => ({
updateOne: {
filter: { sourceId: d.sourceId, reference: d.reference },
update: [
{
$set: {
lastModified: Date.now(),
name: d.name,
status: {
$switch: {
branches: [
// Set status to 'New' for newly inserted docs
{
case: { $eq: [{ $type: '$price' }, 'missing'] },
then: 'New'
},
// Set percentage value when current price is greater/lower than new price
{
case: { $ne: ['$price', d.price] },
then: {
$divide: [{ $subtract: [d.price, '$price'] }, '$price']
}
}
],
// Set status to nothing when new and current prices match
default: ''
}
}
}
},
{
$set: { price: d.price }
}
],
upsert: true
}
}))
);
...
Explanations:
Several problems were blocking me :
the '$field_value_to_check' instead of this.field with undefined 'this' ...
the syntax with $ symbol seems to work only within an aggregation update, using update: [] even if there is only one single $set inside ...
the first condition used for the inserted docs in the upsert process needs to check for the existence of the field price. Only the syntax with BSON $type worked...
Hope it helps other devs in same scenario.

Create Custom mapping type in Elasticsearch 7.3.2 using node js

When I am doing custom mapping using kibana its working properly but when I am doing the same thing in my node program its showing mapper parsing exception.
Reason:Root mapping definition has unsupported parameters:tags(custom mapping name)
Because in kibana i am able to use include_type_name =true but in my node program it is not available.
var name = req.body.templatename;
var index_patterns = req.body.index_patterns;
console.log(index_patterns);
const opts: IndicesPutTemplateParams = {
name: name,
body: {
index_patterns: [index_patterns],
settings: {
analysis: {
filter: {
autocomplete_filter: {
type: "edge_ngram",
min_gram: 1,
max_gram: 20
}
},
analyzer: {
autocomplete: {
type: "custom",
tokenizer: "standard",
filter: [
"lowercase",
"autocomplete_filter"
]
}
}
}
},
mappings: {
tags: {
properties: {
name: {
type: "text",
analyzer: "autocomplete",
search_analyzer: "standard"
},
normalized: {
type: "text"
},
status: {
type: "text"
},
createdat: {
type: "date"
},
updatedat: {
type: "date"
}
}
}
}
}
}
try {
esClient.indices.putTemplate(opts).then((data: any) => {
return res.json({
data
});
console.log(data);
}).catch((err: any) => {
console.log(err);
res.status(500).json({
err
})
});
} catch (error) {
res.status(500).json({
error
})
}
}```
As Per documentation you need to give include_type_name as
client.indices.putTemplate({
name: string,
include_type_name: boolean, --->
order: number,
create: boolean,
timeout: string,
master_timeout: string,
flat_settings: boolean,
body: object -> mapping object
})
Or you can drop mapping name tags from mapping
mappings: {
tags: { ---> remove

Loopback: Passing multiple object types in a remote method

I have an issue where when I pass two object types as a remote method argument, the first argument gets overwritten by the second argument. Below is code and results. How could I go about not having the second argument not overwrite the first argument?
module.exports = (Model) => {
Model.calculate = (primary, secondary) => {
console.log(JSON.stringify(primary, null, 2));
console.log(JSON.stringify(secondary, null, 2));
return new Promise((resolve, reject) => {
resolve({ Model: calculator.calculate() });
});
};
Model.remoteMethod('calculate', {
accepts: [
{ arg: 'primary', type: 'object', http: { source: 'body' } },
{ arg: 'secondary', type: 'object', http: { source: 'body' } }
],
returns: {arg: 'Result', type: 'string'}
});
};
When I pass in the primary argument
{
"name": "Tom"
} and secondary argument
{
"name: "Joe"
}
after console logging the JSON objects primary and secondary I get the result.
primary
{
"name": "Joe" <--- WHY?!
}
secondary
{
"name: "Joe"
}
As you can see Tom was overwritten to Joe.
Change:
Model.remoteMethod('calculate', {
accepts: [
{ arg: 'primary', type: 'object', http: { source: 'body' } },
{ arg: 'secondary', type: 'object', http: { source: 'body' } }
],
returns: {arg: 'Result', type: 'string'}
});
to:
Model.remoteMethod('calculate', {
accepts: [
{ arg: 'primary', type: 'object' },
{ arg: 'secondary', type: 'object' }
],
returns: {arg: 'Result', type: 'string'}
});
http: { source: 'body' } sends in the whole body of html as the object value, so you are sending that in, twice - it looks like a form field called name is what is being picked up, but provide more code if this isn't the case.
More info on optional HTTP mapping of input arguments here. But the main thing to note is that it is optional :-)

Resources