DynamoDB - The provided key element does not match the schema - amazon-web-services

I have a dynamodb table with attributes: userId, propertyInfo, and propertyId. userId is primary index. When I use the following lambda code to update(PUT) the item in the table, I get "The provided key element does not match the schema".
const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies
const dynamoDb = new AWS.DynamoDB.DocumentClient();
module.exports.update = (event, context, callback) => {
const timestamp = new Date().getTime();
const data = JSON.parse(event.body);
const params = {
TableName: process.env.DYNAMODB_TABLE,
Key: {
propertyId: event.pathParameters.id,
},
ExpressionAttributeNames: {
'#new_propertyInfo': 'propertyInfo',
},
ExpressionAttributeValues: {
':propertyInfo': data.propertyInfo,
},
UpdateExpression: 'SET #new_propertyInfo = :propertyInfo',
ReturnValues: 'ALL_NEW',
};
dynamoDb.update(params, (error, result) => {
// handle potential errors
if (error) {
console.error(error);
callback(null, {
statusCode: error.statusCode || 501,
headers: { 'Content-Type': 'text/plain' },
body: 'Couldn\'t fetch the item.',
});
return;
}
// create a response
const response = {
statusCode: 200,
body: JSON.stringify(result.Attributes),
};
callback(null, response);
});
};
Body of my update request is:
{
"propertyInfo":
{
"houseNumber": 2000,
"street": "easy st"
}
}
The event.pathParameters.id is obtained from /property/{id}. I need this id to search DB's propertyId. The userId is needed for authorization purpose. Search and update I need to search by propertyId. Could someone help to explain to me what I need to do to set this up correctly please?

Related

How to put an item on DynamoDB table using AWS SDK for Node.js?

I have a DynamoDB table named MYTABLE
I'm trying to insert some data into this table via LambdaEdge.
My Lambda function is triggered via a CloudFront Distribution.
I have this function :
var AWS = require("aws-sdk");
AWS.config.update({region: 'us-east-1'});
var ddb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
exports.handler = async (event, context) => {
var params = {
TableName: 'MYTABLE',
Item: {
'id' : {'S': 'something'},
'value' : {'S': 'something'}
}
};
// Call DynamoDB to add the item to the table
await ddb.putItem(params, function(err, data) {
if (err) {
console.log("Error", err);
} else {
console.log("Success", data);
}
});
const response = {
status: '302',
statusDescription: 'Found',
headers: {
location: [{
key: 'Location',
value: 'https://www.google.com',
}],
},
};
return response;
};
The function runs properly (the redirection works) but no data is inserted into the table.
Does anyone know what I'm doing wrong please ?
Thanks.
Cheers,

Update item field in Amazon DynamoDB without overwriting other fields

I have an Amazon DynamoDB table called "users' that has one user with the fields -UserId, FirstName, LastName, SignedInAt, SignedOutAt, IsSigned in.
I want to only update certain fields, not all, but when I execute the following code, the fields that I am not updating disappear.
So, I am just left with the UserID, SignedInAt, and IsSignedIn feilds. How do I update without the other fields disappearing?
I am using JavaScript.
Thanks!!!
Before executing code:
await dynamo
.put({
TableName: "users",
Item: {
UserId: requestJSON.UserId,
SignedInAt: new Date().toLocaleTimeString(), // 11:18:48 AM
IsSignedIn: true
}
})
.promise();
After:
The problem with your code is you are using the put method. To update an Amazon DynamoDB item, you need to call the updateItem method. Here is an example in Java V2 API (you can port this code to your programming language) that updates an item based on the key.
public static void updateTableItem(DynamoDbClient ddb,
String tableName,
String key,
String keyVal,
String name,
String updateVal){
HashMap<String,AttributeValue> itemKey = new HashMap<String,AttributeValue>();
itemKey.put(key, AttributeValue.builder().s(keyVal).build());
HashMap<String,AttributeValueUpdate> updatedValues =
new HashMap<String,AttributeValueUpdate>();
// Update the column specified by name with updatedVal
updatedValues.put(name, AttributeValueUpdate.builder()
.value(AttributeValue.builder().s(updateVal).build())
.action(AttributeAction.PUT)
.build());
UpdateItemRequest request = UpdateItemRequest.builder()
.tableName(tableName)
.key(itemKey)
.attributeUpdates(updatedValues)
.build();
try {
ddb.updateItem(request);
} catch (ResourceNotFoundException e) {
System.err.println(e.getMessage());
System.exit(1);
} catch (DynamoDbException e) {
System.err.println(e.getMessage());
System.exit(1);
}
// snippet-end:[dynamodb.java2.update_item.main]
System.out.println("Done!");
}
SOLVED
const AWS = require("aws-sdk");
const dynamo = new AWS.DynamoDB.DocumentClient();
exports.handler = async(event, context) => {
let body;
let user;
let statusCode = 200;
const headers = {
"Content-Type": "application/json"
};
try {
let requestJSON = JSON.parse(event.body);
switch (event.routeKey) {
case "GET /items":
body = await dynamo.scan({ TableName: "users" }).promise();
break;
case "PUT /signIn":
await dynamo
.update({
TableName: "users",
Key: {
UserId: requestJSON.UserId
},
UpdateExpression: "set SignedInAt = :a, IsSignedIn = :b, SignedOutAt = :c",
ExpressionAttributeValues: {
":a": new Date().toLocaleTimeString(),
":b": true,
":c": ""
}
})
.promise();
body = `Put item ${requestJSON.UserId}`;
break;
case "PUT /signOut":
await dynamo
.update({
TableName: "users",
Key: {
UserId: requestJSON.UserId
},
UpdateExpression: "set SignedOutAt = :a, IsSignedIn = :b",
ExpressionAttributeValues: {
":a": new Date().toLocaleTimeString(),
":b": false,
}
})
.promise();
body = `Put item ${requestJSON.UserId}`;
break;
default:
throw new Error(`Unsupported route: "${event.routeKey}"`);
}
}
catch (err) {
statusCode = 400;
body = err.message;
}
finally {
body = JSON.stringify(body);
}
return {
statusCode,
body,
headers
};
};

DynamoDB returning null nextToken when there are still results in database

I have a GraphQL API (AppSync) backed by a DynamoDB table keyed a specific id with timestamp as the range key. I want to retrieve all possible history for that id so I wrote a query in my GraphQL schema that would allow me to do so. Here's the request vtl:
{
"version": "2017-02-28",
"operation": "Query",
"query": {
"expression": "id = :id",
"expressionValues": {
":id": {
"S": "$context.args.id"
}
}
},
"nextToken": $util.toJson($util.defaultIfNullOrEmpty($context.args.nextToken, null))
}
There could be thousands of items in the ddb table for an id so I wrote a Lambda function to query for all of them and return the result in a list as such (I know the code can be simplified):
exports.handler = async function (event, context, callback) {
const graphqlClient = new appsync.AWSAppSyncClient({
url: process.env.APPSYNC_ENDPOINT,
region: process.env.AWS_REGION,
auth: {
type: 'AWS_IAM',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
sessionToken: process.env.AWS_SESSION_TOKEN
}
},
disableOffline: true
});
const query = gql`query GetAllItemsById(
$id: String!
$nextToken: String
) {
getAllItemsById(
id: $id
nextToken: $nextToken
) {
exampleField {
subField1
subField2
subField3
}
nextToken
}
}
`;
const initialResult = await graphqlClient.query({
query,
variables: {
id: event.id
}
});
var finalResult = initialResult.data.getAllItemsById.exampleField;
var nextToken = initialResult.data.getAllItemsById.nextToken;
while (nextToken !== null) {
const result = await graphqlClient.query({
query,
variables: {
id: event.id,
nextToken: nextToken
}
});
finalResult = finalResult.concat(result.data.getAllItemsById.exampleField);
nextToken = result.data.getAllItemsById.nextToken;
}
console.log("Total Results: " + finalResult.length);
return callback(null, finalResult);
};
For some reason, not all items are being returned. nextToken is null before all results are returned. I know DDB has a 1MB limit for query which is why I'm paginating using nextToken but why is it still not returning all the items in the table? Also, if there's a better way to implement this, I'm open to it.

AWS cognito users list : lambda

I am working on one node application which is using AWS. now i want to get all cognito users but as per doc it returns first 60 users but i want all users. can you assist me with this? In doc, they mentioned that pass PaginationToken (string) . but i don't know what to pass in it.
Here what i have done so far :
exports.handler = (event, context, callback) => {
const requestBody = JSON.parse(event.body);
var params = {
"UserPoolId": "****************",
"Limit": 60,
"PaginationToken" : (what to pass here????),
}
const cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
cognitoidentityserviceprovider.listUsers(params, (err, data) => {
if (err) {
callback(null, { headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }, body: JSON.stringify({ statusCode: 405, data: err }) });
} else {
console.log(data);
let userdata = [];
for(let i=0; i<data.Users.length;i++){
// console.log(data.Users[i].Attributes);
userdata.push(getAttributes(data.Users[i].Attributes));
}
callback(null, { headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }, body: JSON.stringify({ statusCode: 200, data: userdata }) });
}
});
};
function getAttributes(attributes){
let jsonObj = {};
attributes.forEach((obj) => {
jsonObj[obj.Name] = obj.Value;
});
return jsonObj;
}
In your response you should see a property called PaginationToken. If you make the same call but include this value in your params you will receive the next 60 users. Here's the concept:
cognitoidentityserviceprovider.listUsers(params, (err, data) => {
// data.Users is the first 60 users
params.PaginationToken = data.PaginationToken;
cognitoidentityserviceprovider.listUsers(params, (err, data) => {
// data.Users is the next 60 users
});
});
You might want to consider switching to promises and async/await if your environment supports it. That would make this code easier to read and write.
const data = await cognitoidentityserviceprovider.listUsers(params).promise();
params.PaginationToken = data.PaginationToken;
const data2 = await cognitoidentityserviceprovider.listUsers(params).promise();

Getting AWS Dynamodb query result as internel server error code : 502

This is my table "odo":
I want to retrive data where deviceId == 'A233' Between two timestamps. I run query inside Lamda Function and testing with API Gateway.
This is query I ran to get the result:
var params = {
TableName: "odo",
KeyConditionExpression: "#deviceId = :deviceIdVal AND #timestamp BETWEEN :sdate AND :edate",
ExpressionAttributeNames: {
"#deviceId": "deviceId",
"#timestamp": "timestamp"
},
ExpressionAttributeValues: {
":deviceIdVal": 'A233',
":sdate": 1110601808,
":edate": 1522902606
}
};
But I get a error as "Internal Server Error" and Error Code : 502
Why this query won't work? What am I missing?
When I ran another query using id field,it work.
module.exports.handler = function (event, context, callback) {
console.log(event);
let _response = "";
let invalid_path_err = {
"Error": "Invalid path request " + event.resource + ', ' +
event.httpMethod
};
if(event.resource === '/odos' && event.httpMethod === "GET"){
var params = {
TableName: "odo",
KeyConditionExpression: "#id = :id",
ExpressionAttributeNames: {
"#id": "id"
},
ExpressionAttributeValues: {
":id": 7
}
};
docClient.query(params, function(err, data) {
if (err) {
console.error("Unable to query. Error:", JSON.stringify(err, null, 2));
} else {
console.log("Query succeeded.",data);
_response = buildOutput(200, data);
return callback(null, _response);
}
});
}
else {
_response = buildOutput(500, {"error 500" : "invalid_path_err"});
return callback(_response, null);
}
};
/* Utility function to build HTTP response for the microservices output */
function buildOutput(statusCode, data) {
let _response = {
statusCode: statusCode,
headers: {
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify(data)
};
return _response;
};
This is the success result in test method execution in API Gateway:
The problem is that your query is trying to use a table partition key of deviceid and a range key of timestamp. In fact you have a parition key called id and no range key.
You can only use KeyConditionExpression on attributes that are a key, which in your case is the attribute id.
To do your 'query' you need to change KeyConditionExpression to FilterExpression and change query to scan
EDIT:
module.exports.handler = function (event, context, callback) {
console.log(event);
let _response = "";
let invalid_path_err = {
"Error": "Invalid path request " + event.resource + ', ' +
event.httpMethod
};
if(event.resource === '/odos' && event.httpMethod === "GET"){
var params = {
TableName: "odo",
FilterExpression: "#deviceId = :deviceIdVal AND #timestamp BETWEEN :sdate AND :edate",
ExpressionAttributeNames: {
"#deviceId": "deviceId",
"#timestamp": "timestamp"
},
ExpressionAttributeValues: {
":deviceIdVal": 'A233',
":sdate": 1110601808,
":edate": 1522902606
}
};
docClient.scan(params, function(err, data) {
if (err) {
console.error("Unable to query. Error:", JSON.stringify(err, null, 2));
} else {
console.log("Query succeeded.",data);
_response = buildOutput(200, data);
return callback(null, _response);
}
});
}
else {
_response = buildOutput(500, {"error 500" : "invalid_path_err"});
return callback(_response, null);
}
};
/* Utility function to build HTTP response for the microservices output */
function buildOutput(statusCode, data) {
let _response = {
statusCode: statusCode,
headers: {
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify(data)
};
return _response;
};