Adding data to DynamoDB from the browser (CodePen) fails - amazon-web-services

I am new to AWS and got the following error when I tried to input data to the dynamodb invoking the lambda function between the 'API gateway' and the 'DynamoDB'.
Error:
Expected params.Item['Age'].S to be a string........
Screenshot of the Error:
Code:
I tried in the browser (CodePen) (I used the correct Invoke URL from the API gateway):
var xhr = new XMLHttpRequest();
xhr.open('POST', 'The API Invoke URL');
xhr.onreadystatechange = function(event){
console.log(event.target.response);
}
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({age: 26, height: 71, income: 2400}));
The following lambda function is invoked when running the above code from CodePen:
There I have imported the aws-sdk and dynamodb correctly.
exports.fn = (event, context, callback) => {
const params = {
Item: {
"UserId": {
S: "user_" + Math.random()
},
"Age": {
N: event.age
},
"Height": {
N: event.height
},
"Income": {
N: event.income
}
},
TableName: "compare-yourself"
};
dynamodb.putItem(params, function(err, data) {
if (err) {
console.log(err);
callback(err);
} else {
console.log(data);
callback(null, data);
}
});
};
In the above lambda function you can observe that I have formatted the inputs as numbers but in the API gateway, in the POST integration request I have converted the inputs to strings. so the data that is passed via the lambda function is already a string. No need to format by the Lambda function, again.
Body mapper in 'POST Integration-Request':
#set($inputRoot = $input.path('$'))
{
"age" : "$inputRoot.age",
"height": "$inputRoot.height",
"income": "$inputRoot.income"
}
I need to know the reason for the above error and am happy to provide any additional information required.
Thank you in advance.

Change the params to indicate that the value of the age field is "String" and not "Numeric":
const params = {
Item: {
"UserId": {
S: "user_" + Math.random()
},
"Age": {
"S": event.age # This was previously set to "N" which causes the issue
},
"Height": {
N: event.height
},
"Income": {
N: event.income
}
},
TableName: "compare-yourself"
};

Related

Using sequelize with AWS Lambda

So I'm having some issues trying to use sequelize with AWS Lambda, when querying a table where id = 1 it sometimes returns me data and sometimes it doesn't. I read that Sequelize connections and AWS Lambda service don't get well each other because of the how Lambda executes a function.
My question is, is it not enough to open a connection at the top of a function and then close it at the bottom (before returning something)? If not, what else can I do?
Update:
const findCityByPk = async (pathParameters) => {
const { Postgresql: ps } = require('../libs/utils/potsgresql');
console.log(ps.connection.connectionManager);
const { id } = pathParameters;
try {
const city = await City.findByPk(id);
if (city) {
ps.connection.close();
return {
statusCode: 200,
body: JSON.stringify(city)
};
}
ps.connection.close();
return {
statusCode: 500,
body: JSON.stringify({ message: 'City not found`' })
};
} catch (err) {
console.log(err);
await ps.connection.close();
return {
statusCode: 500,
body: JSON.stringify(err)
};
}
}
This is the code I'm testing it sometimes returns me the correct object from my table
{"id":"1","city_name":"Lima2","zip_code":"12312","time_zone_utc":-5} -> this is what is supposed to be returning, and instead I'm getting this object
{
"requestTime": "06/Dec/2021:18:07:24 +0000",
"requestId": "8b5bf017-c180-41cc-9de6-b07599f0e9b8",
"apiId": "xx",
"resourceId": "xx",
"resourcePath": "/city/{id}",
"path": "/dev/city/1",
"httpMethod": "GET",
"status": "500",
"authLatency": "-",
"integrationLatency": "48",
"integrationStatus": "200",
"responseLatency": "50",
"responseLength": "2",
"errorMessage": "-",
"format": "SLS_ACCESS_LOG",
"version": "1.0.0"
}
And also, this is how it's being made the connection
const createConnection = () => {
console.info("[Postgresql] createConnection: start")
console.info("[Postgresql] createConnection: creating conection start")
let conn;
let string_connection;
try {
string_connection = `postgres://${config.DB_USER}:${config.DB_PASSWORD}#${config.DB_HOST}:5432/${config.DB_NAME}`
//console.debug(`[Postgresql] string_connection: ${string_connection}`)
conn = new Sequelize(string_connection, { logging: false, pool: { max: 1, min: 0, idle: 1000 } });
} catch (e) {
console.debug(`[Postgresql] createConnection: creating conection error ${string_connection}`)
throw e;
}
console.info("[Postgresql] createConnection:creating conection end")
return conn;
}

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();

Failed use apigwManagementApi.postToConnection in $connect route

I want to return connectionId to a client after the client connect to aws websocket.
I'm using apigwManagementApi.postToConnection to send a response to a client, but I always get an absurd error message.
I already try to debug & search in google, but I can't find a solution for this.
patch.js
require('aws-sdk/lib/node_loader');
var AWS = require('aws-sdk/lib/core');
var Service = AWS.Service;
var apiLoader = AWS.apiLoader;
apiLoader.services['apigatewaymanagementapi'] = {};
AWS.ApiGatewayManagementApi = Service.defineService('apigatewaymanagementapi', ['2018-11-29']);
Object.defineProperty(apiLoader.services['apigatewaymanagementapi'], '2018-11-29', {
get: function get() {
var model = {
"metadata": {
"apiVersion": "2018-11-29",
"endpointPrefix": "execute-api",
"signingName": "execute-api",
"serviceFullName": "AmazonApiGatewayManagementApi",
"serviceId": "ApiGatewayManagementApi",
"protocol": "rest-json",
"jsonVersion": "1.1",
"uid": "apigatewaymanagementapi-2018-11-29",
"signatureVersion": "v4"
},
"operations": {
"PostToConnection": {
"http": {
"requestUri": "/#connections/{connectionId}",
"responseCode": 200
},
"input": {
"type": "structure",
"members": {
"Data": {
"type": "blob"
},
"ConnectionId": {
"location": "uri",
"locationName": "connectionId"
}
},
"required": [
"ConnectionId",
"Data"
],
"payload": "Data"
}
}
},
"shapes": {}
}
model.paginators = {
"pagination": {}
}
return model;
},
enumerable: true,
configurable: true
});
module.exports = AWS.ApiGatewayManagementApi;
index.js
const AWS = require('aws-sdk');
require('./patch.js');
exports.handler = async(event) => {
const connectionId = event.requestContext.connectionId;
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: '2018-11-29',
endpoint: event.requestContext.domainName + '/' + event.requestContext.stage
});
await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: connectionId }).promise();
return {};
};
client.js
const WebSocket = require('ws');
const ws = new WebSocket('wss://****');
ws.on('open', () => {
console.log('connected ===================>')
ws.on('message', data => console.warn(`From server: ${data}`));
});
Error in cloudwatch
{
"errorMessage": "410",
"errorType": "UnknownError",
"stackTrace": [
"Object.extractError (/var/runtime/node_modules/aws-sdk/lib/protocol/json.js:48:27)",
"Request.extractError (/var/runtime/node_modules/aws-sdk/lib/protocol/rest_json.js:52:8)",
"Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:105:20)",
"Request.emit (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:77:10)",
"Request.emit (/var/runtime/node_modules/aws-sdk/lib/request.js:683:14)",
"Request.transition (/var/runtime/node_modules/aws-sdk/lib/request.js:22:10)",
"AcceptorStateMachine.runTo (/var/runtime/node_modules/aws-sdk/lib/state_machine.js:14:12)",
"/var/runtime/node_modules/aws-sdk/lib/state_machine.js:26:10",
"Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:38:9)",
"Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:685:12)"
]
}
I don't know why, but if I'm trying in a custom route, this code can work.
Does anyone know how to solve this?
I'd suggest to look into this example from AWS, there is on connect response for subprotocol confirmation, but I think any payload can be provided.
The most important bit is the route integration settings in the template, basically, the following two lines in the route integration properties:
IntegrationMethod: POST
ConnectionType: INTERNET
then response will be sent to the connected client.
The only way I've found to make this work is to use a DynamoDB table to store connections, then set up a trigger from the table back to a Lambda function.
There are a few catches though. This Lambda function wont work like your index.js file above. You'll have to use NPM install --save aws-sdk on a folder with your index.js file, zip it and upload it to the lambda function, so that the SDK is localized.
You will also need to set up a user with proper access and put the credentials into a your Lambda function.
Note, if you see a 410 error, that means the connection is no longer there, so you're going in the right direction at that point.
const AWS = require('aws-sdk');
require('./patch.js');
var log = console.log;
AWS.config.update({
accessKeyId: "YOURDATAHERE",
secretAccessKey: "YOURDATAHERE"
});
let send = undefined;
function init() {
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: '2018-11-29',
endpoint: "HARDCODEYOURENDPOINTHERE"
});
send = async (connectionId, data) => {
await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: `${data}` }).promise();
}
}
exports.handler = async (event, context) => {
init();
console.log('Received event:', JSON.stringify(event, null, 2));
for (const record of event.Records) {
//console.log(record.eventID);
console.log(record.eventName);
console.log('DynamoDB Record: %j', record.dynamodb);
if(record.eventName == "INSERT"){
var connectionId = record.dynamodb.NewImage.connectionId.S;
try{
await send(connectionId, connectionId);
}catch(err){
log("Error", err);
}
log("sent");
}
}
return `Successfully processed ${event.Records.length} records.`;
};

DynamoDB and Lambda connection

Consider the following code in Lambda:
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB({region: 'us-east-2', apiVersion: '2012-08-10'});
exports.fn = (event, context, callback) => {
const params = {
Item: {
"UserId": {
S:"dsafsgdhf"
},
"Age": {
N: "28"
},
"Height": {
N: "72"
},
"Income": {
N: "33"
}
},
TableName: "compare-yourself"
};
dynamodb.putItem(params, function(err, data){
if(err){
console.log(err);
callback(err);
} else {
console.log(data);
callback(null, data);
}
});
When I run it, I get the following error:
Response:
{
"errorMessage": "Handler 'handler' missing on module 'index'"
}
Kindly let me know where I must have gone wrong.
As error states, you are missing handler.
You should change this line of code:
exports.fn = (event, context, callback) => {
to
exports.handler = (event, context, callback) => {
The thing is that Lambda function looks for handler as entry point, so you can't just rename that function.
Also, from the code you posted here, you are missing parenthesis at the end ( } ) to close the function definition.
You should use the Handler portion shown in the capture below. You should choose whether to use the Handler as index.fn or exports.hander in your code.

Searching DynamoDB for non primary keys and integrating into Alexa Skills

I am trying to search a non primary key using AWS Lambda and integrating it into the Alexa Skills Kit. I am very new to using DynamoDB and Alexa Skills Kit and I'm struggling to find any solutions to this online. The basic premise for what I am trying to do is querying the table yesno with two columns, id and message. Only looking through the message column to find a match with the text i specify in params.
Here is the Lambda code I am working with:
const AWSregion = 'eu-west-1';
const Alexa = require('alexa-sdk');
const AWS = require('aws-sdk');
//params for searching table
const params = {
TableName: 'yesno',
Key:{ "message": 'Ben Davies' }
};
AWS.config.update({
region: AWSregion
});
exports.handler = function(event, context, callback) {
var alexa = Alexa.handler(event, context);
// alexa.appId = 'amzn1.echo-sdk-ams.app.1234';
// alexa.dynamoDBTableName = 'YourTableName'; // creates new table for session.attributes
alexa.registerHandlers(handlers);
alexa.execute();
};
const handlers = {
'LaunchRequest': function () {
this.response.speak('welcome to magic answers. ask me a yes or no question.').listen('try again');
this.emit(':responseReady');
},
'MyIntent': function () {
var MyQuestion = this.event.request.intent.slots.MyQuestion.value;
console.log('MyQuestion : ' + MyQuestion);
readDynamoItem(params, myResult=>{
var say = MyQuestion;
say = myResult;
say = 'you asked, ' + MyQuestion + '. I found a reckord for: ' + myResult;
this.response.speak(say).listen('try again');
this.emit(':responseReady');
});
},
'AMAZON.HelpIntent': function () {
this.response.speak('ask me a yes or no question.').listen('try again');
this.emit(':responseReady');
},
'AMAZON.CancelIntent': function () {
this.response.speak('Goodbye!');
this.emit(':responseReady');
},
'AMAZON.StopIntent': function () {
this.response.speak('Goodbye!');
this.emit(':responseReady');
}
};
// END of Intent Handlers {} ========================================================================================
// Helper Function =================================================================================================
function readDynamoItem(params, callback) {
var AWS = require('aws-sdk');
AWS.config.update({region: AWSregion});
var dynamodb = new AWS.DynamoDB();
console.log('reading item from DynamoDB table');
dynamodb.query(params, function (err, data) {
if (err) console.log(err, err.stack); // an error occurred
else{
console.log(data); // successful response
callback(data.Item.message);
}
});
}
I know I am probably doing this completely wrong but there isn't much online for integrating DynamoDB with an Alexa Skill and the only thing i was able to find was searching by ID. This doesn't work for what i want to do without pulling all the items from the table into a map or a list, and seeing as I want to create a big database it seems quite inefficient.
On the Alexa side of things I am receiving the following service request when testing the code:
{
"session": {
"new": true,
"sessionId": "SessionId.f9558462-6db8-4bf5-84aa-22ee0920ae95",
"application": {
"applicationId": "amzn1.ask.skill.9f280bf7-d506-4d58-95e8-b9e93a66a420"
},
"attributes": {},
"user": {
"userId": "amzn1.ask.account.AF5IJBMLKNE32GEFQ5VFGVK2P4YQOLVUSA5YPY7RNEMDPKSVCBRCPWC3OBHXEXAHROBTT7FGIYA7HJW2PMEGXWHF6SQHRX3VA372OHPZZJ33K7S4K7D6V3PXYB6I72YFIQBHMJ4QGJW3NS3E2ZFY5YFSBOEFW6V2E75YAZMRQCU7MNYPJUMJSUISSUA2WF2RA3CIIDCSEY35TWI"
}
},
"request": {
"type": "IntentRequest",
"requestId": "EdwRequestId.7310073b-981a-41f8-9fa5-03d1b28c5aba",
"intent": {
"name": "MyIntent",
"slots": {
"MyQuestion": {
"name": "MyQuestion",
"value": "erere"
}
}
},
"locale": "en-US",
"timestamp": "2018-01-25T14:18:40Z"
},
"context": {
"AudioPlayer": {
"playerActivity": "IDLE"
},
"System": {
"application": {
"applicationId": "amzn1.ask.skill.9f280bf7-d506-4d58-95e8-b9e93a66a420"
},
"user": {
"userId": "amzn1.ask.account.AF5IJBMLKNE32GEFQ5VFGVK2P4YQOLVUSA5YPY7RNEMDPKSVCBRCPWC3OBHXEXAHROBTT7FGIYA7HJW2PMEGXWHF6SQHRX3VA372OHPZZJ33K7S4K7D6V3PXYB6I72YFIQBHMJ4QGJW3NS3E2ZFY5YFSBOEFW6V2E75YAZMRQCU7MNYPJUMJSUISSUA2WF2RA3CIIDCSEY35TWI"
},
"device": {
"supportedInterfaces": {}
}
}
},
"version": "1.0"
}
And I am receiving a service response error simply saying 'The response is invalid'
Any help with this would be greatly appreciated
I would like to help you in dynamo db part.
In order to access non primary key columns in dynamodb you should perform scan operation.
For your table (yesno), id is a primary key and message is an additional column.
Snippet to access non primary key column [Message]
var dynamodb = new AWS.DynamoDB();
var params = {
TableName: 'yesno',
FilterExpression: 'message = :value',
ExpressionAttributeValues: {
':value': {"S": "Ben Davies"}
}
};
dynamodb.scan(params, function(err, data) {
if (err) // an error occurred
else console.log(data); // successful response
});
Snippet to access primary key column [Id]
var docClient = new AWS.DynamoDB.DocumentClient();
//Get item by key
var params = {
TableName: 'sis_org_template',
Key: { "id": "1"}
};
docClient.get(params, function(err, data) {
if (err) // an error occurred
else console.log(data); // successful response
});