Lambda error: ExpressionAttributeValues must not be empty when using API Gateway - javascript

Using the test in the lambda function, the update method succesfully inserts the data in the correspondent column of the database. However when I try it in API Gateway as a PUT request triggering this lamdba function, the following error appears:
Lambda execution failed with status 200 due to customer function error: ExpressionAttributeNames must not be empty
Here´s the code of the function:
exports.handler = function(event, context, callback) {
// manually id:
let scanningParameters = {
Key: {
"email": event.email
},
UpdateExpression: "set sites = :sites",
ExpressionAttributeValues: {
":sites":event.sites
},
TableName: 'Users'
}
return docClient
.update(scanningParameters)
.promise()
.then(() => {
return {
"statusCode": 200,
'headers': { 'Content-Type': 'application/json' }
}})
}
I have tried changing ExpressionAttributeValues by ExpressionAttributeNames but it hasn´t worked either.

When you use API gateway for lambda proxy integration, recommended way, the event object send to your function has special format:
{
"resource": "Resource path",
"path": "Path parameter",
"httpMethod": "Incoming request's method name"
"headers": {String containing incoming request headers}
"multiValueHeaders": {List of strings containing incoming request headers}
"queryStringParameters": {query string parameters }
"multiValueQueryStringParameters": {List of query string parameters}
"pathParameters": {path parameters}
"stageVariables": {Applicable stage variables}
"requestContext": {Request context, including authorizer-returned key-value pairs}
"body": "A JSON string of the request payload."
"isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encode"
}

Related

URL parse vs constructor: path missing

I am total beginner in Node.js but I am trying to fix what I thought was a simple issue. I am using the following code example for an AWS Lambda function using Node.js 12 runtime:
function respond(event, context, responseStatus, responseData, physicalResourceId, noEcho) {
var responseBody = JSON.stringify({
Status: responseStatus,
Reason: "See the details in CloudWatch Log Stream: " + context.logStreamName,
PhysicalResourceId: physicalResourceId || context.logStreamName,
StackId: event.StackId,
RequestId: event.RequestId,
LogicalResourceId: event.LogicalResourceId,
NoEcho: noEcho || false,
Data: responseData
});
var https = require("https");
var url = require("url");
var parsedUrl = url.parse(event.ResponseURL);
var options = {
hostname: parsedUrl.hostname,
port: 443,
path: parsedUrl.path,
method: "PUT",
headers: {
"content-type": "",
"content-length": responseBody.length
}
};
var request = https.request(options, function(response) {
context.done();
});
request.on("error", function(error) {
console.log("send(..) failed executing https.request(..): " + error);
context.done();
});
request.write(responseBody);
request.end();
}
Full source code can be found here: https://github.com/aws-samples/amazon-cloudfront-secure-static-site/blob/7f96cdbcfbd7f94c3ab5a4c028b6bafd10744c83/source/witch/witch.js#L70
My IDE gives me a warning that the URL.parse() method is deprecated and so that I should use the URL constructor instead. So the only change I made is replacing:
var parsedUrl = url.parse(event.ResponseURL);
with
var parsedUrl = new url.URL(event.ResponseURL);
But when I do that, the options.path field ends up missing. What is even more confusing to me is that if I log the parsedUrl variable (passing it through JSON.stringify()), I can see that when I use url.parse(), I get a simple string in parsedUrl:
"https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A123456789012%3Astack/AcmCertificateStack-ABCDEFGHIJKL/00112233-4455-6677-8899-aabbccddeeff%7CCopyCustomResource%7C00112233-4455-6677-8899-aabbccddeeff?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20220101T000000Z&X-Amz-SignedHeaders=host&X-Amz-Expires=7199&X-Amz-Credential=ABCDEFGHIJKLMNOPQRST%2F20220101%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
But when using the constructor, I can see in the log an object structure with all the expected fields (protocol, hostname, port, even path):
{
"protocol": "https:",
"slashes": true,
"auth": null,
"host": "cloudformation-custom-resource-response-useast1.s3.amazonaws.com",
"port": null,
"hostname": "cloudformation-custom-resource-response-useast1.s3.amazonaws.com",
"hash": null,
"search": "?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20220101T000000Z&X-Amz-SignedHeaders=host&X-Amz-Expires=7199&X-Amz-Credential=ABCDEFGHIJKLMNOPQRST%2F20220101%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
"query": "XX-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20220101T000000Z&X-Amz-SignedHeaders=host&X-Amz-Expires=7199&X-Amz-Credential=ABCDEFGHIJKLMNOPQRST%2F20220101%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
"pathname": "/arn%3Aaws%3Acloudformation%3Aus-east-1%3A123456789012%3Astack/AcmCertificateStack-ABCDEFGHIJKL/00112233-4455-6677-8899-aabbccddeeff%7CCopyCustomResource%7C00112233-4455-6677-8899-aabbccddeeff",
"path": "/arn%3Aaws%3Acloudformation%3Aus-east-1%3A123456789012%3Astack/AcmCertificateStack-ABCDEFGHIJKL/00112233-4455-6677-8899-aabbccddeeff%7CCopyCustomResource%7C00112233-4455-6677-8899-aabbccddeeff?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20220101T000000Z&X-Amz-SignedHeaders=host&X-Amz-Expires=7199&X-Amz-Credential=ABCDEFGHIJKLMNOPQRST%2F20220101%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
"href": "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A123456789012%3Astack/AcmCertificateStack-ABCDEFGHIJKL/00112233-4455-6677-8899-aabbccddeeff%7CCopyCustomResource%7C00112233-4455-6677-8899-aabbccddeeff?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20220101T000000Z&X-Amz-SignedHeaders=host&X-Amz-Expires=7199&X-Amz-Credential=ABCDEFGHIJKLMNOPQRST%2F20220101%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
}
So if anything the constructor seems to provide a better break down of the URL. I don't why, when I try to copy the parsedUrl.path field to options.path it works when parsedUrl comes from the parse() method but not when it comes from the constructor. The hostname field on the other hand works in both cases.
Any idea what's the issue here?
As indicated by the OP, the use of the url.parse method is discouraged in favor of the WATWG URL API. The legacy .path attribute returns the combined pathname and search components. Although the preferred WATWG URL API does not have the path attribute, the value required by options.path can be constructed by combining the .pathname and .search attributes.

I want to pass a dynamic json body to the cypress request() function & define payload values

I'm new to cypress, so I apologize if I make no sense here.
i have a cypress script that does a POST request. My end goal is to check API validations. whether API responds with correct error messages for a given JSON body. for that, I want to pass the same JSON body with different values to the cypress request function.
I have my JSON object in a different js file. (channel_query.js)
export const CreateChannel = {
"name": "channe Name",
"tagline": "tasdsadsaest",
"date": "03 Mar 2021",
"time": "2.00 p.m",
"beginsOn": "2021-03-04T13:59:08.700",
"expiresOn": "2021-05-28T14:54:08.700",
"description": "sample Descritptin",
"url": "www.google.com"}
I have my cypress request in the integration folder (channel.js)
import { CreateChannel } from '../queries/channel_query';
it('Create a channel',function() {
cy.request({
method: 'POST',
url: '{my URL}',
body: CreateChannel ,
headers: headers
}).then((response) => {
expect(response.status).to.eq(201)
expect(response.body.name).to.eq(CreateChannel.name)
})
}) })
My question is,
How to make values in JSON object dynamic & then define them in the cypress request function? so I can pass the same JSON to check different validations.
#Mr. Gleb Bahmutov
Help is much appreciated guys!
The simplest way might be to place an array of channels in the JSON file and make the test data-driven.
export const channelData = [
{
"name": "channe Name",
... // plus other properties
},
{
"name": "name2",
... // plus other properties
},
]
The test
import { channelData } from '../queries/channel_query';
describe('Test all channels', () => {
channelData.forEach((channel, index) => {
it(`Testing channel "${channel.name}"`, function() {
cy.request({
method: 'POST',
url: '{my URL}',
body: channel,
headers: headers
}).then((response) => {
expect(response.status).to.eq(201)
expect(response.body.name).to.eq(channel.name)
})
})
})

Cannot get response header fetch request even with Access-Control-Expose-Headers

I am trying to get the x-total-count response header from a fetch JS request, I have included Access-Control-Expose-Headers as advised in other previous posts and I am still unable to get the response header, I can see it is being received in chrome dev tools and have tried response.headers.get('x-total-count'); and iterating over response.headers with no luck.
What am I missing here?
async function postData() {
const myPost = {
"listingType":"Sale",
"propertyTypes": [
"townhouse",
"duplex",
"semiDetached",
"studio",
"townhouse",
"villa",
"ApartmentUnitFlat",
"Rural",
"house"
],
"geoWindow": {
"box": {
"topLeft": {
"lat": aNorth,
"lon": aWest
},
"bottomRight": {
"lat": aSouth,
"lon": aEast
}
},
},
"pageNumber": pageNumber,
"pageSize": 100
}
const options = {
method: 'POST',
body: JSON.stringify(myPost),
headers: {
'Content-Type': 'application/json',
"X-API-Key": "API-KEY",
'Access-Control-Expose-Headers': 'x-total-count'
}
};
const response = await fetch('https://api.domain.com.au/v1/listings/residential/_search', options)
if (!response.ok) {
const message = `An error has occured: ${response.status}`;
throw new Error(message);
}
const totalCount = response.headers.get('x-total-count');
for (var pair of response.headers.entries()) { console.log(pair[0]+ ': '+ pair[1]); }
console.log(totalCount)
As an updated I contacted the team that runs this API and received the following response below, turns out it was an issue on their end.
It would appear you've run into an issue with our early support for
CORS requests, the header you are trying to set,
Access-Control-Expose-Headers, is actually one that we specify from
our side.
Presently we don't specify it completely, so the x-total-count header
is not exposed to browsers which follow the CORS rules.
I have added this issue to our backlog to be rectified as soon as
possible, and it will most likely will be part of our first planned
update in early January 2021.

When using PUT method body is not passed using Koa

I am trying to make update function, where a user can put the new data and the data in the server would get updated, a simple task, however, when I try to PUT new data, the body is always undefined.
The data which gets sent:
request: {
method: 'PUT',
url: '/api/v1.0/articles/1',
header: {
'user-agent': 'PostmanRuntime/7.17.1',
accept: '*/*',
'cache-control': 'no-cache',
host: 'localhost:3000',
'accept-encoding': 'gzip, deflate',
'content-length': '98',
connection: 'keep-alive'
}
},
response: {
status: 404,
message: 'Not Found',
header: [Object: null prototype] {}
},
Now I tried passing it as keys using other methods and not RAW method, this is what is inside of the body I am trying to pass:
{
"title": "another article",
"fullText": "again here is some text hereto fill the body"
}
This is the function which should update the data, but it gets undefined from the put request.
router.put("/:id", updateArticle);
function updateArticle(cnx, next) {
let id = parseInt(cnx.params.id);
console.log(cnx);
if (articles[id - 1] != null) {
//articles[id - 1].title = cnx.request.body.title;
cnx.body = {
message:
"Updated Successfully: \n:" + JSON.stringify(updateArticle, null, 4)
};
} else {
cnx.body = {
message:
"Article does not exist: \n:" + JSON.stringify(updateArticle, null, 4)
};
}
}
I am using postman, Body -> Raw | JSON, I do have to mention all other methods work perfectly - delete, create, getAll, getById
With a PUT or POST, the data is in the body of the request. You have to have some code (in your request handler or some middleware) that actually reads the body from the stream and populates the body property for you. If you don't have that, then the data is still sitting in the request stream waiting to be read.
You can see an example of reading it yourself here: https://github.com/koajs/koa/issues/719 or there is pre-built middleware that will do that for you.
Here's are a couple modules that will do that middleware for you:
https://github.com/dlau/koa-body
https://www.npmjs.com/package/koa-body-parser

How can I send email notifications with Parse and Mandrill?

I am trying to use Mandrill to send an event-based email notification to the users of my web app. I am using Parse with Back4App.
In this tutorial (https://docs.back4app.com/docs/integrations/parse-server-mandrill/), the hosting providers suggest using the following method to call the Mandrill cloud code from an Android application:
public class Mandrill extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
Parse.initialize(new Parse.Configuration.Builder(this)
.applicationId("your back4app app id”)
.clientKey(“your back4app client key ")
.server("https://parseapi.back4app.com/").build()
);
Map < String, String > params = new HashMap < > ();
params.put("text", "Sample mail body");
params.put("subject", "Test Parse Push");
params.put("fromEmail", "someone#example.com");
params.put("fromName", "Source User");
params.put("toEmail", "other#example.com");
params.put("toName", "Target user");
params.put("replyTo", "reply-to#example.com");
ParseCloud.callFunctionInBackground("sendMail", params, new FunctionCallback < Object > () {
#Override
public void done(Object response, ParseException exc) {
Log.e("cloud code example", "response: " + response);
}
});
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mandrill);
}
}
How can I implement this in JavaScript with the Parse JavaScript SDK?
This is what I've done so far but it won't send an email. I have Mandrill set up, as well as a verified email domain and valid DKIM and SPF.
// Run email Cloud code
Parse.Cloud.run("sendMail", {
text: "Email Test",
subject: "Email Test",
fromEmail: "no-reply#test.ca",
fromName: "TEST",
toEmail: "test#gmail.com",
toName: "test",
replyTo: "no-reply#test.ca"
}).then(function(result) {
// make sure to set the email sent flag on the object
console.log("result :" + JSON.stringify(result));
}, function(error) {
// error
});
I don't even get a result in the console, so I figure the cloud code is not even executing.
You have to add the Mandrill Email Adapter to the initialisation of your Parse Server, as described on their Github page. Also check the Parse Server Guide for how to initialise or use their example project.
Then set up Cloud Code by following the guide. You'll want to either call a Cloud Code function using your Android app or from any Javascript app, or use beforeSave or afterSave hooks of a Parse Object directly in Cloud Code, which allow you to send Welcome Emails when a user signs up. That could come in handy if you want to implement behaviour based emails based on object updates. Plus, because it is on the server and not the client, it is easier to maintain and scale.
To make the Cloud Code function actually send an email via Mandrill, you need to add some more code to your Cloud Code function. First, add a file with these contents:
var _apiUrl = 'mandrillapp.com/api/1.0';
var _apiKey = process.env.MANDRILL_API_KEY || '';
exports.initialize = function(apiKey) {
_apiKey = apiKey;
};
exports.sendTemplate = function(request, response) {
request.key = _apiKey;
return Parse.Cloud.httpRequest({
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
url: 'https://' + _apiUrl + '/messages/send-template.json',
body: request,
success: function(httpResponse) {
if (response) {
response.success(httpResponse);
}
return Parse.Promise.resolve(httpResponse);
},
error: function(httpResponse) {
if (response) {
response.error(httpResponse);
}
return Parse.Promise.reject(httpResponse);
}
});
};
Require that file in your Cloud Code file, and use it like any other Promise.
var Mandrill = require("./file");
Mandrill.sendTemplate({
template_name: "TEMPLATE_NAME",
template_content: [{}],
key: process.env.MANDRILL_API_KEY,
message: {
global_merge_vars: [{
name: "REPLACABLE_CONTENT_NAME",
content: "YOUR_CONTENT",
}],
subject: "SUBJECT",
from_email: "YOUR#EMAIL.COM",
from_name: "YOUR NAME",
to: [{
email: "RECIPIENT#EMAIL.COM",
name: "RECIPIENT NAME"
}],
important: true
},
async: false
})
.then(
function success() {
})
.catch(
function error(error) {
});
Make sure you create a template on Mailchimp, right click it and choose "Send to Mandrill", so that you can use that template's name when sending via the API.
It's a bit involved, but once set up, it works like a charm. Good luck!

Categories

Resources