Alexa skill async await fetching data from DynamoDB - node.js

The following code is my launch handler in my Alexa skill and I have a variable named x inside of my handler. I am trying to set x to data that I'm getting from dynamoDB and to use it outside of the get function (I got the function from https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.NodeJs.03.html#GettingStarted.NodeJs.03.02) so that Alexa can speak the value (a string) of x (as you see in the return). The statement in my "get" function is not changing the value of x outside of the get function itself. I know that the x inside of the get function is actually being changed because I am logging it to console. So I posted a similar post on this, and I initially thought it was a scoping issue, but turns out it's because the get function is asynchronous. Hence, I added the async and await keywords as shown below. I'm new to NodeJS so that's where I thought I should put them, according to what I've researched. This is still not working however.
const LaunchHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === `LaunchRequest`;
},
async handle(handlerInput) {
var x;
//DYNAMO GET FUNCTION
await DBClient.get(params, function(err, data) {
if (err) {
console.error("Unable to read item. Error JSON:", JSON.stringify(err, null, 2));
} else {
x = data.Item.Answer;
} });
return handlerInput.responseBuilder
.speak(x)
.withShouldEndSession(false)
.getResponse();
},
};
As a side note, here's the JSON that I'm (successfully) returning from the database:
{
"Item": {
"Answer": "Sunny weather",
"Question": "What is the weather like today"
}
}

Are you looking for something like this? Within the handle function i call another function getSpeechOutput to create some feedback text. Thus function calls the dynamodb function getGA to get user data
const getSpeechOutput = async function (version) {
const gadata = await ga.getGA(gaQueryUsers, 'ga:users')
let speechOutput;
...
return ...
}
const UsersIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'UsersIntent';
},
async handle(handlerInput) {
try {
let speechOutput
...
speechOutput = await getSpeechOutput("long");
...
return handlerInput.responseBuilder
.speak(speechOutput)
.reprompt("Noch eine Frage?")
.withSimpleCard(defaulttext.SKILL_NAME, speechOutput)
.getResponse();
} catch (error) {
console.error(error);
}
},
};
Thats the db function:
const getUser = async function (userId) {
const dynamodbParams = {
TableName: process.env.DYNAMODB_TABLE_BLICKANALYTICS,
Key: {
id: userId
}
}
return await dynamoDb.get(dynamodbParams).promise()
}

Related

API call with axios fails if special characters are present in the API output

I am currently creating an Alexa Skill with node.js and have axios loaded with version 0.21.1.
If the queried API returns normal characters everything works without problems. However, if there is a &, the call fails. Why does it do that?
Here is my code:
const name = 'example';
const apiUrl= function() {
return `https://example.com/${name}`
}
const APIRequest = async (url) => {
try {
const { data } = await axios.get(url);
return data;
} catch (error) {
console.error('cannot fetch quotes', error);
}
};
const APIHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'APIIntent'
},
async handle(handlerInput) {
try {
const [Data] = await Promise.all([
(APIRequest(apiUrl()))
]);
const speechText = `Data 1: ${Data.1}, Data 2: ${Data.2}`;
return handlerInput.responseBuilder
.speak(speechText)
.getResponse();
} catch (error) {
return handlerInput.responseBuilder
.speak('Unfortunately I do not know at the moment')
.getResponse();
}
},
};
Here is the API output
Works:
{
"1": "this is a text",
"2": "this is a text"
}
Does not work:
{
"1": "this is a text & more information",
"2": "this is a text"
}
Coding for alexa skill using nodejs, my presumption was you might be doing so with AWS lambda, in this case I suggest using lambda local or serverless
I understand it is difficult to debug if you're not using above options.
I have two suggestions for your issue,
First one: Axios interceptors. It is very likely to work though I personally didn't get a chance to use it in Alexa skill development.
axios.interceptors.response.use(response => {
let data = response.headers["content-type"];
if (data.includes("charset=ISO-8859-1")) {
responsee.data = data.decode(response.data, 'ISO-8859-1');
}
return response;
});
Ref:- https://github.com/axios/axios/issues/332
Second Approach: Encode response and decode response.data to the desired format
use iso-8859-2
const response = await axios.request({
method: 'GET',
url: 'https://www.test.com',
responseType: 'arraybuffer',
responseEncoding: 'binary'
});
let result = iso88592.decode(response.data.toString('binary'));

Node.js - adding multiple parameters dynamically using MSSQL package

I am trying to write a function that takes care of all of the queries that I execute to the database, however, I am struggling to find a way to dynamically add parameters to my request.
All NPM documentation, somewhat unhelpfully, provides non-dynamic examples of adding parameters (https://www.npmjs.com/package/mssql).
Example NPM documentation:
function runStoredProcedure() {
return pool2.then((pool) => {
pool.request() // or: new sql.Request(pool2)
.input('input_parameter', sql.Int, 10)
.output('output_parameter', sql.VarChar(50))
.execute('procedure_name', (err, result) => {
// ... error checks
console.dir(result)
})
});
}
My implementation looks like this
// promise style:
const pool2 = new sql.ConnectionPool(config, err => {
// ... error checks
});
pool2.on('error', err => {
// ... error handler
})
function runStoredProcedure(res, proc, sqlParams) {
return pool2.then((pool) => {
pool.request() // or: new sql.Request(pool2)
.input('input_parameter', sql.Int, 10) //
.execute(proc, (err, recordset) => {
// ... error checks
res.json(recordset[0]);
})
});
}
Ideally, I would like to declare the pool.request() and then foreach for my parameters.
I thought this question would be useful to post as real use cases of the mssql package would look at adding parameters dynamically, in spite of the examples given in the documentation.
pool.request returns a request object. You don't have to use the fluent API, but can ignore the return value of each input call and just call them separately:
function runStoredProcedure(res, proc, sqlParams) {
return pool2.then((pool) => {
const req = pool.request();
sqlParams.forEach(function(param) {
req.input(param.name, param.type, param.value);
});
req.execute(proc, (err, recordset) => {
// ... error checks
res.json(recordset[0]);
});
});
}
// create parameter json
let sqlParams = [{ "name": "ParamName1", "type": sql.Date, "value": obj.ParamName1 },
{ "name": "ParamName2", "type": sql.Int, "value": obj.ParamName2 }];
// Call Function
let result = await getDbResult(sqlParams, 'Procedure name ');
// Dynamic function of call Stored Procedure
exports.getDbResult = async (sqlParams, SpName) => {
try {
let pool = await sql.connect(dbconfig)
const result1 = await pool.request();
sqlParams.forEach(function (param) {
result1.input(param.name, param.type, param.value);
});
return await result1.execute(SpName);
} catch (err) {
console.log(err);
}
}

Serverless: dynamodb giving error on create record when trying with async/await

I am trying to create a record in dynamodb(Using dynamoose). code is
class Test {
constructor() {
this.table = dynamoose.model(tableName, tableSchema);
}
// userdata object - {
// cusotmerEmail: 'tushar.gaurav+testf40#accionlabs.com',
// customerBusinessName: 'DoogleDnd',
// customerFirstName: 'Tushar',
// customerId: 101211,
// customerLastName: 'Gaurav',
// isDeleted: false,
// sku: '100',
// userId: '5c1776e94bea867c3f896236'
// }
async createUser(userData) {
try {
const res = await this.table.create(userData);
console.log('Update user record - ', res);
return res;
} catch (error) {
throw new Error(error);
}
}
}
*input values to the create function are correct as the same input I tried with batchPut(), it's working.
And even update call to the table is also working.
async updateUser(userData) {
try {
const res = await this.table.update(userData);
console.log('Updated user record - ', res);
return res;
} catch (error) {
throw new Error(error);
}
}
This is the error I am getting -
Error - {"message":"The conditional request failed", "code":"ConditionalCheckFailedException", "statusCode":400}
This is the calling function -
module.exports.subscribeUser = async (event) => {
let inputBody = (typeof event.body === 'object' ? event.body :
JSON.parse(event.body));
inputBody.userId = event.pathParameters.id;
try {
// Validate input
await asisvc.validateInput(inputBody);
inputBody = await UserSvc.constructUserObject(inputBody);
console.log('Constructed object - ', JSON.stringify(inputBody));
const userData = await testObj.createUser(inputBody);
return Utils.buildResp(codes.ok, { userData }, {});
} catch (error) {
console.log(error);
return Utils.buildResp(codes.badrequest, { Error:
Utils.getErrString(error) }, {});
}
};
I tried googling it, but didn't find any proper document.
Thanks in advance.
In Dynamoose by default we check to see if the primary key already exists in the table when using the Model.create method.
So your error:
{"message":"The conditional request failed", "code":"ConditionalCheckFailedException", "statusCode":400}
Indicates that the primary key already exists in the table. So you are trying to create a duplicate item.
In the documentation there is an options property that you can use to allow overriding the object.
For example the following code will allow overrides:
const res = await this.table.create(userData, {overwrite: true});

Combining multiple queries in one aws lambda function

In my DynamoDB table I've got items with userA (MyUser) following UserB(myFriends..)
Primary-------|---- Srotkey---|---FriendID---|
Myid..... Friend-01 22223333
in the same table I also have the user profiles..
Primary-------|---- Srotkey---|----Name----|
Myid..... Friend-01
22223333 Profile Rose
Now I want to have a a function to return my friend's profile.
I suppose my lambda "getFriendProfile" function should do two queries, get the id for the person I'm following in the first query and then use that result to fetch her profile in the second query.
I know how to get those result individually but I don't know how to combine them and put them both into one function.
First query (getPeopleIFollow)
module.exports.handler = async (event, context, callback) => {
const _userID = event.userID;
var params = {
TableName: "mytableName",
ProjectionExpression: "FriendID",
KeyConditionExpression: "#tp = :userId and begins_with(#sk, :skv)",
ExpressionAttributeNames: {
"#tp": "userId",
"#sk": "Sortkey",
},
ExpressionAttributeValues: {
":userId": { "S": _userID },
":skv": { "S": "Friend" },
}
}
var Friend_ID;
try {
let data = await dynamodb.query(params).promise();
data.Items.map(
(dataField) => {
Friend_ID = dataField.FriendID.S,
}
);
callback(null, Friend_ID );
}
catch (error) {
console.log(error);
callback(error);
}
};
My other function looks just looks very much like the first one..
getProfileNames..
module.exports.handler = async (event, context, callback) => {
const _userID = event.myfriendID;
var params = {
TableName: "mytableName",
ProjectionExpression: "Name",
KeyConditionExpression: "#tp = :userId and begins_with(#sk, :skv)",
ExpressionAttributeNames: {
"#tp": "userId",
"#sk": "Sortkey",
},
ExpressionAttributeValues: {
":userId": { "S": _userID },
":skv": { "S": "Profile" },
}
}
try {
let data = await dynamodb.query(params).promise();
const items = data.Items.map(
(dataField) => {
return {
friend_name: dataField.Name.S,
}
}
);
callback(null, items );
}
catch (error) {
console.log(error);
callback(error);
}
};
Keeping functionality separate to each Lambda is actually a nice way to organise code. So you could keep your code as it is and invoke the getProfileNames lambda for each of the getPeopleIFollow results.
I think it's also a good idea to abstract as much as possible out of your handler so that it becomes easy to follow at a high level. So the following examples assumes your code is in other functions that return promises.
(untested code)
module.exports.handler = async (event, context, callback) => {
const _userID = event.userID;
try {
const userFriends = await getUserFriends(userId);
const friendProfiles = await getFriendProfiles(userFriends);
callback(null, friendProfiles );
} catch (error) {
console.log(error);
callback(error);
}
}
The lambda invocation happens in getFriendProfiles which uses Promise.all to invoke lambdas for each separate friendProfile request.
(untested code)
function getFriendProfiles(userFriends){
return Promise.all(userFriends.map(userFriend => {
const params = {
ClientContext: "MyApp",
FunctionName: "MyFunction",
InvocationType: "RequestResponse", // important, you choose this so you get a response otherwise (eg if you choose the 'Event' InvocationType) I think the promise will resolve once the invocation is successful
LogType: "Tail",
Payload: JSON.stringify({ myfriendID: userFriend }), // I think this is right, I usually use pathParameters or body for my payloads
Qualifier: "1"
};
lambda.invoke(params).promise();
});
}
Having said all that - what this number of db queries shows is that perhaps this DynamoDb schema is not the most efficient. This might be a case where indexes could be helpful, or even using an RDS rather than dynamodb.

Use Dynasty DynamoDB Query in Alexa Response

I am using Dynasty in my Nodejs Alexa Skill Service, run on AWS Lambda, to query DynamoDB. Due to the structure of the API, I am unable to use a query's result in my Alexa response. In the code below, the callback passed to 'then' is run after the handler returns, so 'name' is never assigned. How can I use information obtained in the query callback in my response?
const dynasty = require('dynasty')(credentials);
const myIntentHandler = {
canHandle(input) {
return input.requestEnvelope.request.intent.name === 'MyIntent';
},
handle(input) {
const userId = input.requestEnvelope.session.user.userId;
const users = dynasty.table('user');
var name;
users.find(userId).then(function(user) {
if(user) {
name = user.name;
} else {
...
}
});
return input.responseBuilder.speak('Hello ' + name).getResponse();
}
};
The Alexa SDK for NodeJS v2 supports promises in handlers.
So, from you handler you return a promise, chained off of the Dynasty query promise.
const dynasty = require('dynasty')(credentials);
const myIntentHandler = {
canHandle(input) {
return input.requestEnvelope.request.intent.name === 'MyIntent';
},
handle(input) {
const userId = input.requestEnvelope.session.user.userId;
const users = dynasty.table('user');
var name;
return new Promise((resolve, reject) => {
users.find(userId).then(function(user) {
if(user) {
name = user.name;
let response
= input.responseBuilder
.speak('Hello ' + name)
.getResponse();
resolve(response);
} else {
...
// handle this case
// and resolve(..) or reject(..)
}
});
});
}
};

Resources