AWS Update item with list on DynamoDB using lambda Nodejs - node.js

I have an item in my DynamoDB table that looks like this:
{
"qid": {
"S": "0"
},
"options": {
"L": [
{
"S": "Summer"
},
{
"S": "Winter"
}
]
},
"votes": {
"L": [
{
"N": "11"
},
{
"N": "13"
}
]
}
}
With a lambda function, I want to update one of the elements in the votes list. I want to update the index that matches the index of the option I get from the event (the event has qid, option and number of votes). So here is what I tried:
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event, context) => {
const qid = event.qid;
const option = event.option;
const vote = event.vote;
const params = {
TableName : 'QnV',
Key: {
qid: qid
}
}
try {
const data = await docClient.get(params).promise()
const options = data.Item.options;
const index = options.findIndex(option)
const updateParams = {
TableName: 'QnV',
Key: {
'qid': qid
},
UpdateExpression: 'set votes[:index] = :vote',
ExpressionAttributeValues: {
':index': index,
':vote': vote
},
ReturnValues: 'UPDATED_NEW'
};
const updateData = await docClient.update(updateParams).promise();
return {
statusCode: 200,
body: JSON.stringify(updateData)
};
} catch (err) {
return {
statusCode: 500,
body: JSON.stringify(err)
};
}
};
I tried to test it with this:
{
"qid": "0",
"option": "Winter",
"vote": 14
}
But I get an error of 500 and an empty body. It looks like the update params variable is empty, but I don't understand why.
I tried to search online but it looks like I'm doing everything correctly (but if that was the case I didn't have a problem). I also didn't forget to grant permission to the function.
Edit: As suggested, I change the body of the error to err.message, and got this:
{
"statusCode": 500,
"body": "\"Winter is not a function\""
}
Please help me find the bug, I would really appreciate it!
Thank you

After more debugging I found the problems. There were two.
Incorrect index method.
const index = options.findIndex(option)
Changed to:
const index = options.indexOf(option)
The way I tried to find the index in updateParams was incorrect. Instead of:
UpdateExpression: 'set votes[:index] = :vote',
ExpressionAttributeValues: {
':index': index,
':vote': vote
},
It should be:
UpdateExpression: `set votes[${index}] = :vote`,
ExpressionAttributeValues: {
':vote': {N: vote},
},

Related

NodeJS ValidationException: The provided key element does not match the schema

I know this has been asked before but I've tried everything from previous posts and still couldn't find a solution for my problem. I can query the table from DynamoDB no problem but can't from lambda. I deleted and created the table with the same name to get rid of a sort key hoping to fix my issue but it did not fix it too. So here is my code
Params
const queryParams = {
TableName: tableName,
KeyConditionExpression: 'postId = :postId',
ExpressionAttributeValues: { ":postId": postId }
};
Schema
type LikeRelationship
#model(
mutations: {
create: "createLikeRelationship"
delete: "deleteLikeRelationship"
update: null
}
timestamps: null
)
#auth(
rules: [
{
allow: owner
ownerField: "likerId"
operations: [read, create, delete]
}
{ allow: public, provider: iam, operations: [read] }
]
)
#key(fields: ["postId"]) {
postId: ID!
postOwnerId: String!
likerType: String
likerName: String
likerId: ID!
timestamp: Int!
}
Full Lambda Code
/* Amplify Params - DO NOT EDIT
API_GROOVESECONDED_GRAPHQLAPIIDOUTPUT
API_GROOVESECONDED_LIKERELATIONSHIPTABLE_ARN
API_GROOVESECONDED_LIKERELATIONSHIPTABLE_NAME
ENV
REGION
Amplify Params - DO NOT EDIT */
exports.handler = async (event, context, callback) => {
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10' });
const tableName = 'LikeRelationship-76mphdcsf5cijbgq5kkcny3xpe-staging';
async function deleteItems(tableName, postId ) {
const queryParams = {
TableName: tableName,
KeyConditionExpression: 'postId = :postId',
ExpressionAttributeValues: { ":postId": postId }
};
const queryResults = await docClient.query(queryParams).promise();
if (queryResults.Items && queryResults.Items.length > 0) {
const batchCalls = chunks(queryResults.Items, 25).map( async (chunk) => {
const deleteRequests = chunk.map( item => {
return {
DeleteRequest : {
Key : {
'partitionId' : item.partitionId,
'sortId' : item.sortId,
}
}
};
}
);
const batchWriteParams = {
RequestItems : {
[tableName] : deleteRequests
}
};
await docClient.batchWrite(batchWriteParams).promise();
});
await Promise.all(batchCalls);
}
}
if (event.body !== null && event.body !== undefined) {
let data = JSON.parse(event.body);
if (typeof data.postId === 'undefined') {
return sendRes(404, '{ error: true, message: "postId undefined." }');
}
try {
await deleteItems(tableName, data.postId );
} catch (e) {
return sendRes(404, '{ error: true, message: " ' + e + data.postId + typeof data.postId + ' " }');
}
return sendRes(200, '{ "error": false, "message": "Success" }');
}
return sendRes(404, '{ error: true, message: "Event body null or undefined." }');
};
const sendRes = (status, body) => {
var response = {
statusCode: status,
headers: {
"Content-Type" : "application/json",
"Access-Control-Allow-Headers" : "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
"Access-Control-Allow-Methods" : "OPTIONS,POST",
"Access-Control-Allow-Credentials" : true,
"Access-Control-Allow-Origin" : "*",
"X-Requested-With" : "*"
},
body: body
};
return response;
};
// https://stackoverflow.com/a/37826698/3221253
function chunks(inputArray, perChunk) {
return inputArray.reduce((all,one,i) => {
const ch = Math.floor(i/perChunk);
all[ch] = [].concat((all[ch]||[]),one);
return all;
}, []);
}
IAM Role
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem",
"dynamodb:Delete*",
"dynamodb:Get*",
"dynamodb:Scan",
"dynamodb:Query"
],
"Resource": [
"arn:aws:dynamodb:eu-central-1:382762466424:table/LikeRelationship-76mphdcsf5cijbgq5kkcny3xpe-staging",
"arn:aws:dynamodb:eu-central-1:382762466424:table/LikeRelationship-76mphdcsf5cijbgq5kkcny3xpe-staging/index/*"
]
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": [
"dynamodb:List*",
"dynamodb:Describe*"
],
"Resource": [
"arn:aws:dynamodb:eu-central-1:382762466424:table/LikeRelationship-76mphdcsf5cijbgq5kkcny3xpe-staging",
"arn:aws:dynamodb:eu-central-1:382762466424:table/LikeRelationship-76mphdcsf5cijbgq5kkcny3xpe-staging/index/*"
]
}
]
}

How to remove an item from a StringSet in DynamoDB?

I have this table in DynamoDB:
{
"lectures": {
"L": [
{
"M": {
"lecture_id": {
"S": "CC0"
},
"students": {
"SS": [
"",
"test"
]
}
}
}
]
}
I want to remove "test" from the StringSet students.
Looking at the docs, I tried this code:
function removeLecture(event){
const students = 'lectures[' + Number.parseInt(event.lecture_id) + '].students';
console.log('test: ' + students);
const params = {
TableName: 'TableName',
Key: {
'Key': key
},
UpdateExpression: `DELETE ${students} :student`,
ExpressionAttributeValues: {
':student' : {
'SS': event.student
}
},
ReturnValues : 'UPDATED_NEW'
}
return ddb.update(params).promise();
}
Anyway, I receive a ValidationException:
START RequestId: 3857a2c2-23a8-4c0c-b080-516669c6e615 Version: $LATEST
2021-08-18T10:21:39.078Z 3857a2c2-23a8-4c0c-b080-516669c6e615 INFO test: lectures[0].students
2021-08-18T10:21:39.831Z 3857a2c2-23a8-4c0c-b080-516669c6e615 INFO ValidationException: Invalid UpdateExpression: Incorrect operand type for operator or function; operator: DELETE, operand type: MAP, typeSet: ALLOWED_FOR_DELETE_OPERAND
I have also tried with ExpressionAttributeValues: {':student' : event.student}, but still I receive the same error with STRING instead of MAP.
How can I remove the String from the StringSet?
After long trying, I discovered the function createSet() of the AWS.DynamoDB.DocumentClient class. I tried to use it, and it works. So the answer is: if you want to remove a String from a StringSet, you have to create another StringSet and remove the latter from the original.
Here is the code:
function removeLecture(event){
const students = 'lectures[' + Number.parseInt(event.lecture_id) + '].students';
console.log('test: ' + students);
const params = {
TableName: 'TableName',
Key: {
'Key': key
},
UpdateExpression: `DELETE ${students} :student`,
ExpressionAttributeValues: {
':student' : ddb.createSet(event.student)
},
ReturnValues : 'UPDATED_NEW'
}
return ddb.update(params).promise();
}

How to remove an object from a list in DynamoDB using Lambda

I'm trying to write a Lambda function to remove an object from a list in DynamoDB. In this case I am passing in "author": "J.K. Rowling", "title": "Harry Potter", and "userid": "041c9004" as parameters. I want to delete the matching object from the books list. What is the correct syntax for the UpdateExpression statement? There may be some other errors within params{} as well.
The DynamoDB table looks like this. userid is the primary key:
{
"books": [
{
"author": "J.R.R. Tolkien",
"title": "Lord of the Rings"
},
{
"author": "J.K Rowling",
"title": "Harry Potter"
},
{
"author": "George RR Martin",
"title": "A Song of Ice and Fire"
}
],
"isactive": true,
"ispublic": true,
"lastupdated": 1597690265,
"userid": "041c9004"
}
Here is the Lambda function:
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient({region: 'us-east-1'});
exports.handler = function(event, context, callback){
let params = {
ExpressionAttributeValues: {
":attrValue": [{
"author": event.author,
"title": event.title
}]
},
ExpressionAttributeNames : {
"#books" : "books"
},
Key: {
userid: event.userid
},
TableName: 'Users',
UpdateExpression: "REMOVE #books[:attrValue]", //this is incorrect
ReturnValues:"ALL_NEW",
};
docClient.update(params, function(err,data){
if(err) {
callback(err, null)
}else{
callback(null, data)
}
});
}
This is a great question!
Unfortunately, the UpdateExpression will only allow you to REMOVE from the list if you specify an index (docs here).
You'll need to read the item from the DB, find the indexes you want to remove, and REMOVE that specific index.
In case anyone is wondering, here is the full solution:
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient({region: 'us-east-1'});
exports.handler = function(event, context, callback){
var params = {
TableName: 'Users',
Key: {
userid: event.userid
}
};
docClient.get(params, function(err, data){
if(err) {
callback(err,null);
} else {
var indexOfAuthor = data.Item.books.findIndex(i => i.author === event.author);
console.log('The index of the author is ' + indexOfAuthor);
var updateExpressionString = "REMOVE #books[" + indexOfAuthor + "]"
let paramsdelete = {
ExpressionAttributeNames : {
"#books" : "books"
},
Key: {
userid: event.userid
},
TableName: 'Users',
UpdateExpression: updateExpressionString,
ReturnValues:"ALL_NEW",
};
docClient.update(paramsdelete, function(err,data){
if(err) {
callback(err, null);
}else{
callback(null, data);
}
});
}
});
};

node.js passing a parameter to DynamoDB updateItem method

I want to write a function that updates given parameter in dynamodb.
For example in a dynamodb table where each userId is the key I have values like
{
"categoryname": "a",
"skillState": "a",
"skipcount": 1,
"userId": "amzn1.ask.account.xxx”
}
I wanna set the "categoryname": "b" although there might be 10-15 fields like this so I dont wanna hard code the field name.
function (userId,itemToUpdate,itemValue,callback) {
var updateExpressionString = "SET #"+itemToUpdate+" =:val1";
var expressionAtt = '#'+itemToUpdate + '';
console.log(updateExpressionString)
console.log(expressionAtt)
this.dynamodb.updateItem({
TableName: constants.dynamoDBDetailTableName,
Key: {
userId: {
S: userId
}
},
UpdateExpression: updateExpressionString,
ExpressionAttributeNames : {
expressionAtt : itemToUpdate
},
ExpressionAttributeValues : {
':val1': {'S':itemValue}
}
}, function (err, data) {
if (err) {
console.log(err)
console.log('Error ')
} else if (data.Item === undefined) {
}else {
console.log(data)
}
});
}
In ExpressionAttributeNames:
{ ValidationException: ExpressionAttributeNames contains invalid key: Syntax error; key: "expressionAtt"
This throws error obviously thinking that expressionAtt is the key while it is a local variable.
I am new to node.js , how can pass the local variable in to ExpressionAttributeNames and ExpressionAttributeValues
One way of dealing with this could be to pull the object out of updateItem, put it into its own variable like so:
var item = {
TableName: constants.dynamoDBDetailTableName,
Key: {
userId: {
S: userId
}
},
UpdateExpression: updateExpressionString,
ExpressionAttributeNames: {},
ExpressionAttributeValue: {
':val1': {'S': itemValue}
}
};
item.ExpressionAttributeNames[expressionAtt] = itemToUpdate;
this.dynamodb.updateItem(item);
I believe that will fix your problem

DynamoDB putitem in NodeJs - arrays of objects

I'm trying to set up a small api from AWS Lambda to DynamoDB and I am having trouble figuring out if and how I can write an array of objects into a key.
I have an object like
{
"teamName": "Team Awesome",
"members": [
{
"email": "person-1#example.com",
"name": "Bob"
},
{
"email": "person-2#example.com",
"name": "Alice"
}
]
}
The members array is giving me issues, in the docs it looks like it can be done considering the list types, but there is just no example how HOW to do it, and I am running out of ways to try it.
So is it possible to write something in this format at all and how do you in that case do it?
Example code - what do I put at ???
var AWS = require('aws-sdk');
var dynamodb = new AWS.DynamoDB();
exports.handler = function(event, context) {
var tableName = "GDCCompetition";
var datetime = new Date().getTime().toString();
DynamoDB.putItem({
"TableName": tableName,
"Item": {
"datetime": {
"N": datetime
},
"teamName": {
"S": event.teamName
},
"members": ???
}
});
}
The documentation is not really obvious, but there is a thing called DocClient, you can pass a usual JS object to it and it will do all the parsing and transformation into AWS object (with all the types). You can use it like this:
var AWS = require("aws-sdk");
var DynamoDB = new AWS.DynamoDB.DocumentClient();
var params = {
TableName: "MyTable",
Item: {
"teamName": "Team Awesome",
"members": [
{
"email": "person-1#example.com",
"name": "Bob"
},
{
"email": "person-2#example.com",
"name": "Alice"
}
]
}
};
DynamoDB.put(params, function (err) {
if (err) {
return throw err;
}
//this is put
});
You could convert the object to DynamoDb record first
const AWS = require('aws-sdk');
var tableName = "GDCCompetition";
var datetime = new Date().getTime().toString();
const members = [
{
"email": "person-1#example.com",
"name": "Bob"
},
{
"email": "person-2#example.com",
"name": "Alice"
}
];
const marshalled = AWS.DynamoDB.Converter.marshall({ members });
const params = {
"TableName": tableName,
"Item": {
"datetime": {
"N": datetime
},
"teamName": {
"S": event.teamName
},
"members": marshalled.members,
},
}
AWS.DynamoDB.putItem(params, function (err) {
if (err) {
return throw err;
}
});

Resources