Dynamodb alexa query not calling data item - node.js

I am creating a skill where I want to call alexa to read differnt items at certain dates and times.
I currently have my table set up as follows:
Date|Time|State
Date is setup as my primary key with time as my sort key. I have setup date and time as slot values in ASK, and I can see that these values are being passed through. I have also made sure the format of my date and time is correct within dynamodb.
My issue is when I call alexa and ask for the state at a certain date and time, I can't get alexa to respond with the state corresponding with that date and time.
Can anyone help me with this? Or tell me where i'm going wrong, I will insert my code below.
const awsSDK = require('aws-sdk');
const updatedincident = 'updatedincident';
const docClient = new awsSDK.DynamoDB.DocumentClient();
var AWSregion = 'us-east-1'; // us-east-1
var AWS = require('aws-sdk');
var dbClient = new AWS.DynamoDB.DocumentClient();
AWS.config.update({
region: "'us-east-1'"
});
let GetMachineStateIntent = (context, callback, dateSlot, timeSlot) => {
var params = {
TableName: "updatedincident",
KeyConditionExpression: 'date = :dVal and time < :tVal',
ExpressionAttributeValues: {
':dVal': dateSlot,
':tVal': timeSlot
},
ScanIndexForward: false // gets values in reverse order by time
};
dbClient.query(params, function (err, data) {
if (err) {
// failed to read from table for some reason..
console.log('failed to load data item:\n' + JSON.stringify(err, null, 2));
// let skill tell the user that it couldn't find the data
sendResponse(context, callback, {
output: "the data could not be loaded from your database",
endSession: false
});
} else {
let dataItem = data.Items[0];
console.log('loaded data item:\n' + JSON.stringify(dataItem, null, 2));
// assuming the item has an attribute called "state"..
sendResponse(context, callback, {
output: dataItem.state,
endSession: false
});
}
});
};
function sendResponse(context, callback, responseOptions) {
if(typeof callback === 'undefined') {
context.succeed(buildResponse(responseOptions));
} else {
callback(null, buildResponse(responseOptions));
}
}
function buildResponse(options) {
var alexaResponse = {
version: "1.0",
response: {
outputSpeech: {
"type": "SSML",
"ssml": `<speak><prosody rate="slow">${options.output}</prosody></speak>`
},
shouldEndSession: options.endSession
}
};
if (options.repromptText) {
alexaResponse.response.reprompt = {
outputSpeech: {
"type": "SSML",
"ssml": `<speak><prosody rate="slow">${options.reprompt}</prosody></speak>`
}
};
}
return alexaResponse;
}
exports.handler = (event, context, callback) => {
try {
var request = event.request;
if (request.type === "LaunchRequest") {
sendResponse(context, callback, {
output: "welcome to my skill, I can tell you about the status of machines at different times. what data are you looking for?",
endSession: false
});
} else if (request.type === "IntentRequest") {
if (request.intent.name === "GetMachineStateIntent") {
var dateSlot = request.intent.slots.Date != null
? request.intent.slots.Date. value : null;
var timeSlot = request.intent.slots.Time != null
? request.intent.slots.Time.value : null;
// pass the slot values to the GetMachineStateIntent function
GetMachineStateIntent(context, callback, dateSlot, timeSlot);
} else if (request.intent.name === "AMAZON.StopIntent" || request.intent.name === "AMAZON.CancelIntent") {
sendResponse(context, callback, {
output: "ok. good bye!",
endSession: true
});
}
else if (request.intent.name === "AMAZON.HelpIntent") {
sendResponse(context, callback, {
output: "you can ask me about incidents that have happened or states of machines in the past",
reprompt: "what can I help you with?",
endSession: false
});
}
else {
sendResponse(context, callback, {
output: "I don't know that one! please try again!",
endSession: false
});
}
}
else if (request.type === "SessionEndedRequest") {
sendResponse(context, callback, ""); // no response needed
}
else {
// an unexpected request type received.. just say I don't know..
sendResponse(context, callback, {
output: "I don't know that one! please try again!",
endSession: false
});
}
} catch (e) {
// handle the error by logging it and sending back an failure
console.log('Unexpected error occurred in the skill handler!', e);
if(typeof callback === 'undefined') {
context.fail("Unexpected error");
} else {
callback("Unexpected error");
}
}
};
The response i currently get with my above code is
'the data could not be loaded from your database'
Cloud watch also tells me this
2018-05-16T09:29:06.635Z 93d4b6e6-58eb-11e8-b686-597d65771e90 failed to load data item:
{
"message": "Invalid KeyConditionExpression: Attribute name is a reserved keyword; reserved keyword: date",
"code": "ValidationException",
"time": "2018-05-16T09:29:06.633Z",
"requestId": "EQPQTAGO4QKH9SM5GSOA9O3DDFVV4KQNSO5AEMVJF66Q9ASUAAJG",
"statusCode": 400,
"retryable": false,
"retryDelay": 35.56027710686527
}

In the GetMachineStateIntent function, try changing the params structure like this:
var params = {
TableName: "updatedincident",
KeyConditionExpression: '#d = :dVal and #t < :tVal',
ExpressionAttributeValues: {
':dVal': dateSlot,
':tVal': timeSlot
},
ExpressionAttributeNames: {
'#d': 'date',
'#t': 'time'
},
ScanIndexForward: false // gets values in reverse order by time
};
It looks like the word date is a reserved keyword so it can't be used directly in an expression such as date = :dVal which is why you must give it an attribute name alias (#d) which maps back to the actual attribute name (date).

In DynamoDB, you have to use two keys i.e Primary key and Primary sort key. The query searches on the basis of these two keys for the requested value.
Try my code:
'FindName': function() {
var tableName = "CVRaman";
var userName = "Prateek";
var userId = "kapoor";
const dynamodbParams = {
TableName: tableName,
Key: {
userId: userId,
userName: userName,
},
ProjectionExpression: 'userName', //Projection Expression is used to select only specific columns which we want to get
};
console.log("Attempting to find name", dynamodbParams);
dynamoDb.get(dynamodbParams).promise()
.then(data => {
console.log('name found', dynamodbParams);
console.log(data.Item.userName);
var a = data.Item.userName;
console.log(a);
this.emit(':ask', 'Name as ' + a);
})
.catch(err => {
console.log(err);
this.emit(':tell', 'we have a problem');
});
},

Related

Data Item not getting inserted in DynamoDB Table from AWS Lambda Trigger

I have this Lambda code setup in JS for inserting a row in DynamoDB but the code seems to have some issue and doesn't trigger.Unable to figure out the issue as cloud watch also does captures anything. The API Gateway triggers the Lambda will inserts a row in DynamoDB. Tried debugging with console and logs also but unable to figure out the actual issue that is causing the code to break.
exports.handler = async (event) => {
console.log(event);
var eventBody = JSON.parse(event.body);
var eventType = eventBody.event_type;
var content = eventBody.content;
if (content.subscription) {
var subscription = content.subscription;
switch (eventType) {
case "subscription_created":
// Add new entry to License table
console.log('subscription created event');
var customer = content.customer;
var findUserScan = {
ExpressionAttributeValues: {
':email': { S: customer.email },
},
FilterExpression: 'email = :email',
TableName: _dynamodb_userprofile_table
};
try {
await dynamodb.scan(findUserScan, function (err, data) {
if (err) {
console.log("Error", err);
} else {
var userProfileAWS;
data.Items.forEach(function (userProfile, index, array) {
userProfileAWS = AWS.DynamoDB.Converter.unmarshall(userProfile);
});
var companyName;
if (userProfileAWS) {
companyName = userProfileAWS.organization;
}
try {
var licenseEntry = {
"subscriptionID": {
S: subscription.id
},
"companyName": {
S: companyName || ""
},
"customerEmail": {
S: customer.email
},
};
await dynamodb.putItem({
"TableName": _dynamodb_license_table,
"Item": licenseEntry
}).promise()
} catch (error) {
console.log(error)
throw new Error(`Error in dynamoDB: ${JSON.stringify(error)}`);
}
}
}).promise();
} catch (error) {
throw new Error(`Error in dynamoDB: ${JSON.stringify(error)}`);
}
break;

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.

Getting null value when trying to query value which is not present in dynamo db using node.js

I am new to dynamoDB and node.js I have written a code where it will make a query to the database (dynamodb) and look for an element which is entered by the user in the database. I am able to verify that but when the user tries with some other number which is not present in the database I am getting a null value.
My table name is "DevReferenceNumber" and only one column which is primary key "referencenumber".
'use strict';
var AWS = require('aws-sdk');
var docClient = new AWS.DynamoDB.DocumentClient({ region : 'us-east-1'});
function close(sessionAttributes, fulfillmentState, message) {
return {
sessionAttributes,
dialogAction: {
type: 'Close',
fulfillmentState,
message,
},
};
}
exports.handler = (event, context, callback) => {
try{
console.log(`event.bot.name=${event.bot.name}`);
if(event.bot.name != 'getCustomerReferenceNumber'){
callback('Invalid Bot Name');
}
dispatch(event, (response) => {
callback(null, response)
});
}catch(err){
callback("Error is occured while querying");
}
};
function dispatch(intentRequest, callback){
console.log(`dispatch UserID => ${intentRequest.userId}, intentName => ${intentRequest.currentIntent.name}`);
const intentName = intentRequest.currentIntent.name;
if(intentName === "checkReferenceNumber"){
return referenceNumber(intentRequest, callback);
}
}
function referenceNumber(intentRequest, callback){
const enteredReferenceNumber = intentRequest.currentIntent.slots.ReferenceNumber;
const sessionAttributes = intentRequest.sessionAttributes;
console.log("User has entered reference number is --> " + enteredReferenceNumber);
var params = {
TableName : "DevReferenceNumber",
KeyConditionExpression: "#refer = :ref",
ProjectionExpression : "#refer",
ExpressionAttributeNames: {
"#refer" : "referencenumber"
},
ExpressionAttributeValues:{
":ref": parseInt(enteredReferenceNumber)
}
};
docClient.query(params, function(err, data){
if(err){
callback(close(sessionAttributes, 'Fulfilled', {
contentType: 'PlainText',
content : 'Developer reference number is not matched with data from database'}));
}
else {
data.Items.forEach(function (item){
console.log("User matched data is ==> " + item.referencenumber);
callback(close(sessionAttributes, 'Fulfilled', {
contentType: 'PlainText',
content : 'Developer reference number is matched with data from database'}));
});
}
});
}
It is obvious that you will get a null record when you don't have a matching record. If you don't want null from node callback then you can do a custom logic to do a null check and return data according to the way you want.

access values after authentication in node js

I've a program that does the below.
Look into a DynamoDB table.
Get the data from the table.
Save the variables in session
After the process, print the values in console.
My code is as below.
intentHandlers['GetMYBusinessInfo'] = function (request, session, response, slots) {
console.log('entered ext bloxk');
if (!session.attributes.userName) {
console.log('eneterd the user entered the block');
var userName = 'jim';
isUserRegistered(userName.toLowerCase(), function (res, err) {
if (err) {
response.fail(err);
console.log(err);
}
else if (!res) {
response.shouldEndSession = true;
}
else {
console.log(res);
var countRes = JSON.stringify(res.Count);
var unpUserRegion = JSON.stringify(res.Items[0].Region);
var unpUserCity = JSON.stringify(res.Items[0].State);
var userRegion = JSON.parse(unpUserRegion);
var userCity = JSON.parse(unpUserCity);
session.attributes.city = userCity;
session.attributes.region = userRegion;
console.log("parsed " + countRes + "\t region is " + userRegion);
session.attributes.userName = true;
}
});
}
console.log(`session values after authentication are user city is ${session.attributes.city}`);
}
The method to check if the value is in DynamoDb or not.
function isUserRegistered(userName, callback) {
var params = {
TableName: "usersTable",
FilterExpression: "#nme = :nme",
ExpressionAttributeNames: {
"#nme": "Name",
},
ExpressionAttributeValues: {
":nme": userName
}
};
var count = 0;
docClient.scan(params, function (err, data) {
if (err) {
console.error("Unable to scan the table. Error JSON:", JSON.stringify(err, null, 2));
callback(false, err);
} else {
console.log("Scan succeeded." + data.Items.length);
if (data.Items.length === 0) {
callback(false);
}
else {
data.Items.forEach(function (itemData) {
console.log("Item :", ++count, JSON.stringify(itemData));
});
callback(data);
}
}
});
}
when I run this, the output that I get is:
session values after authentication are user city is undefined
Scan succeeded.1
Item : 1
{
"ID": "3",
"State": "wisconsin",
"Region": "midwest",
"Name": "jim"
}
{ Items: [ { ID: '3', State: 'wisconsin', Region: 'midwest', Name: 'jim' } ],
Count: 1,
ScannedCount: 1 }
parsed 1 region is midwest
Here I know that Node js being Non-blockable process, the above output is correct, but I want to get the value of city printed in session values after authentication are user city is {HereTheCityComes} instead of session values after authentication are user city is undefined.
I'm sure that placing the console.log(session values after authentication are user city is ${session.attributes.city}); in the last else block(place where the data is returned).
But I need this type of functionality(Get data as shown in my current scenario), as there is some other things to be done after checking if the user is available in database.
please let me know where am I going wrong and how can I fix this.
You can't synchronously expect async result.
What you can do here is solve your problem with promises.
Here is a solution:
intentHandlers['GetMYBusinessInfo'] = function(request, session, response, slots) {
console.log('entered ext bloxk');
var userPromise = Promise.resolve();
if (!session.attributes.userName) {
console.log('eneterd the user entered the block');
var userName = 'jim';
userPromise = new Promise(function (resolve, reject) {
isUserRegistered(userName.toLowerCase(), function (res, err) {
if (err) {
response.fail(err);
reject(err);
}
var countRes = JSON.stringify(res.Count);
var unpUserRegion = JSON.stringify(res.Items[0].Region);
var unpUserCity = JSON.stringify(res.Items[0].State);
var userRegion = JSON.parse(unpUserRegion);
var userCity = JSON.parse(unpUserCity);
session.attributes.city = userCity;
session.attributes.region = userRegion;
console.log("parsed " + countRes + "\t region is " + userRegion);
resolve(res);
});
});
}
userPromise.then(function () {
console.log(`session values after authentication are user city is ${session.attributes.city}`);
});
}
If you are not using ES6, then just install bluebird and use var Promise = require('bluebird')

Sending and validating Lambda code to DynamoDB

I have a Lambda node.js function like so:
function storeUser(email, username, password, salt, fn) {
// Bytesize
var len = 128;
crypto.randomBytes(len, function(err, token) {
if (err) return fn(err);
token = token.toString('hex');
dynamodb.putItem({
TableName: "Users",
Item: {
email: {
S: email
},
username: {
S: username
},
passwordHash: {
S: password
},
passwordSalt: {
S: salt
},
verified: {
BOOL: false
},
verifyToken: {
S: token
}
},
ConditionExpression: 'attribute_not_exists (email)'
}, function(err, data) {
if (err) return fn(err);
else fn(null, token);
});
});
}
The event handler is like so:
exports.handler = function(event, context) {
var email = event.email;
var username = event.username;
var clearPassword = event.password;
computeHash(clearPassword, function(err, salt, hash) {
if (err) {
context.fail('Error in hash: ' + err);
} else {
storeUser(email, username, hash, salt, function(err, token) {
if (err) {
if (err.code == 'ConditionalCheckFailedException') {
// userId already found
context.succeed({
created: false
});
} else {
context.fail('Error in storeUser: ' + err);
}
} else {
context.succeed({
created: true
});
}
});
}
});
}
I want to make the username and email unique. How do I ensure that in the write to the db and if one (or both) is not unique, get an error back saying which is not unique?
This is a quick and dirty way to do it. It's not extraordinarily efficient but it gets the job done:
const // You want const because no required packages ever change at runtime
AWS = require('aws-sdk'),
ddb = new AWS.DynamoDB();
exports.handler = function(event, context) {
// QUERY USERNAME FIRST
var table = 'tableName';
var paramsUNCheck = {
TableName : table,
ProjectionExpression : 'hash',
KeyConditionExpression : 'hash = :v_hash AND range = :v_rng',
ExpressionAttributeValues : {
':v_hash' : { S : 'hash' },
':v_rng' : { S : event.username }
}
}
ddb.query (paramsUNCheck, function(err, data) {
if (err) {
context.fail (JSON.stringify(err, null, 2))
} else {
if (data.Count === 0) {
var paramsEMCheck = {
TableName : table,
ProjectionExpression : 'hash',
KeyConditionExpression : 'hash = :v_hash',
FilterExpression : 'email = :v_eml',
ExpressionAttributeValues : {
':v_hash' : { S : 'hash' },
':v_eml' : { S : event.email }
}
}
ddb.query (paramsEMCheck, function(err, data) {
if (err) {
context.fail (JSON.stringify(err, null, 2))
} else {
if (data.Count === 0) {
// REGISTER IF IT PASSES ALL CHECKS
var paramsReg = {
TableName : table,
Item : {
'hash' : { S : 'hash' },
'username' : { S : event.username },
'password' : { S : event.password },
'email' : { S : event.email }
}
}
ddb.putItem (paramsReg, function (err, data) {
if (err) context.fail (JSON.stringify(err, null, 2));
else
context.succeed ({ done: 'yes' });
});
} else {
context.succeed ('Email already used, please try again.')
}
}
});
} else {
context.succeed ('Username taken, please try again.')
}
}
})
};
Now, the reason why I say this is a dirty way of doing it is because you do two queries.
With that said, running reads is significantly cheaper than running writes. The reason for this madness:
One read capacity unit = one strongly consistent read per second, or two eventually consistent reads per second, for items up 4 KB in size.
One write capacity unit = one write per second, for items up to 1 KB in size.
From here
Basically, it's way cheaper to run two read requests then one write request. PER PROVISIONAL THROUGHPUT the cost is ~$0.09/ read throughput and ~$0.48/ write throughput.
But wait, don't you have to do a write anyway? Well, yes you do, but it's a write that should always succeed. What if you have a user constantly hitting that lambda? You'll want a read on the front line of defense and not a write. Basically I'm as cheap as possible. I'll crank read throughput all day long if it means not 'uping' the write (within reason).
The other way to do it is Conditional Writes and you're going to want to check begins_with usernameofemail#gmail.com all the way up to the end - basically forcing a full string check, so disregard the "begins with".

Resources