Vanilla NodeJS Appsync client error while making queries/mutations - node.js

I'm trying to hit my Appsync GraphQL endpoint with a plain nodejs client that signs and sends the requests. There it is:
var https = require("https");
let AWS = require("aws-sdk");
let urlParse = require("url").URL;
require("dotenv").config();
const throwOnErrors = ({ query, variables, errors }) => {
if (errors) {
const errorMessage = `
query: ${query.substr(0, 100)}
variables: ${JSON.stringify(variables, null, 4)}
error: ${JSON.stringify(errors, null, 4)}
`;
throw new Error(errorMessage);
}
};
module.exports = async (
url,
credentials,
query,
operationName,
variables = {}
) => {
AWS.config.update({
region: process.env.AWS_REGION,
credentials: new AWS.Credentials(
credentials.accessKeyId,
credentials.secretAccessKey,
credentials.sessionToken
),
});
let endpoint = new urlParse(url).hostname.toString();
const item = {
input: variables
};
let req = new AWS.HttpRequest(url, process.env.AWS_REGION);
req.method = "POST";
req.headers.host = endpoint;
req.headers["Content-Type"] = "application/json";
req.body = JSON.stringify({
query: query,
operationName: operationName,
variables: item
});
let signer = new AWS.Signers.V4(req, "appsync", true);
signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate());
const data = await new Promise((resolve, reject) => {
const httpRequest = https.request({ ...req, host: endpoint }, (result) => {
result.on("data", (data) => {
resultdata = JSON.parse(data.toString());
if (resultdata.errors != null) {
throwOnErrors(query, variables, data);
reject(resultdata);
} else {
resolve(resultdata);
}
});
result.on("errors", (data) => {
reject(() => {
throwOnErrors(query, variables, data)
return JSON.parse(data.toString())
});
});
});
httpRequest.write(req.body);
httpRequest.end();
});
return data.data
};
Unfortunately, sometimes, the response does get rejected and the error is always something like this:
SyntaxError: Unexpected token , in JSON at position 0
at JSON.parse (<anonymous>)
56 | result.on("data", (data) => {
57 |
> 58 | resultdata = JSON.parse(data.toString());
| ^
59 | if (resultdata.errors != null) {
60 | throwOnErrors(query, variables, data);
61 | reject(resultdata);
at IncomingMessage.<anonymous> (_tests/lib/graphql.js:58:27)
I really have no clue on how to solve this, because sometimes the promise gets resolved and sometimes not.
Do you guys have any tips for me?
Thanks in advance!

Your 'data' callback can run many times, result is a stream, so
const httpRequest = https.request({ ...req, host: endpoint }, (result) => {
result.on("data", (data) => {
resultdata = JSON.parse(data.toString());
if (resultdata.errors != null) {
throwOnErrors(query, variables, data);
reject(resultdata);
} else {
resolve(resultdata);
}
});
result.on("errors", (data) => {
reject(() => {
throwOnErrors(query, variables, data)
return JSON.parse(data.toString())
});
});
});
needs to be more like
const httpRequest = https.request({ ...req, host: endpoint }, (response) => {
const chunks = [];
response.on("data", (data) => { chunks.push(data); });
response.on("end", () => {
resultdata = JSON.parse(Buffer.concat(chunks).toString());
if (resultdata.errors != null) {
throwOnErrors(query, variables, data);
reject(resultdata);
} else {
resolve(resultdata);
}
});
response.on("error", err => {
reject(() => {
throwOnErrors(query, variables, data)
return JSON.parse(data.toString())
});
});
});

Related

How can I Convert code from Nodejs to Angular including API features [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 days ago.
This post was edited and submitted for review 4 days ago.
Improve this question
I need to convert Nodejs code into Angular. Please suggest me all the steps. I have Nodejs code which is for Get Data using API. That I need to convert into Angular.
I have a task to get Data from Autodesk using their API and Post into MySQL Database (Workbench). This task has already been done in NodeJs. I want to apply same functionality in Angular.
Controller Code : UpdateController.js
const fs = require('fs');
const path = require('path');
const csvtojson = require('csvtojson');
const xlsx = require('xlsx');
const mongoose = require('mongoose');
const openssl = require('openssl-nodejs');
const extract = require('extract-zip');
const SubscriptionsModel = require('../models/subscription');
const PriceListModel = require('../models/pricelist');
const AuthModel = require('../models/auth');
var SubscriptionController = require('../controllers/subscriptioncontroller');
const { sendPasswordNotificationAfterBatch } = require('../models/notification');
var auth = new AuthModel();
class UploadController {
getCSVData(file){
console.log("In getCSVData");
return new Promise((resolve, reject) => {
csvtojson()
.fromFile(file)
.then(csvData => {
//console.log(csvData);
resolve(csvData);
}).catch(err => {
reject(err);
});
});
}
getXLSXData(filename){
console.log("In getXLSData");
return new Promise((resolve, reject) => {
var workbook = xlsx.readFile(filename);
var sheet_list = workbook.SheetNames;
var sheet = workbook.Sheets[sheet_list[0]];
var jsonObjects = xlsx.utils.sheet_to_json(sheet, { range: 7, raw: true, default: null });
var response = [];
response = jsonObjects.filter(item => {
return item.SRP !== undefined && item.SRP !== null && item.DTP !== undefined && item.DTP !== null
});
resolve(response);
}).catch(error => {
throw error;
});
}
updatePricelist(filePath){
return new Promise((resolve, reject) => {
this.getXLSXData(filePath).then(res => {
if(res){
// Delete backup
//PriceListModel.collection.drop();
// Create collection for updated data
PriceListModel.insertMany(res, err => {
if(err){
reject(err);
}
resolve();
})
}
}).catch(error => {
reject(error);
})
});
}
importSubscriptions()
{
console.log("In Import Subscriptions");
var url = `v1/export/subscriptions`;
console.log(`Importing subscription data...`);
return new Promise((resolve,reject) =>{
var body = {
'startDateSince': '2000-01-01'
}
auth.post(url, body).then(function(res)
{
var d = JSON.parse(res);
console.log(d);
resolve(d);
}).catch(err => {
reject(err);
});
});
}
checkImportSubscriptionJobStatus(jobId, count){
var url = `/v1/export/subscriptions/${jobId}`;
console.log(`Checking import subscription job status ${++count}...`);
return new Promise((resolve,reject) =>{
auth.getUsingHttpPlugin(url).then((res) => {
if (res.statusCode == 303) {
console.log("Current status is " + res.statusCode);
resolve(res.headers["location"]);
}
else {
console.log(`Current status is ${res.statusCode}, will retry in 60 seconds again `);
setTimeout(() => {
this.checkImportSubscriptionJobStatus(jobId, count).then(fileUrl => {
resolve(fileUrl);
});
}, '60000');
}
}).catch(function(err){
reject(err);
});
});
}
downloadFile(jobId, fileUrl) {
console.log("Downloading file...");
return new Promise((resolve, reject) => {
var file = fs.createWriteStream(`uploads/subscriptions/${jobId}.csv.zip.enc`);
//console.log(file);
console.log(fileUrl);
auth.getPlainHttp(fileUrl).then(function (response) {
response.pipe(file);
console.log(file.path);
resolve();
}).catch(err => {
console.log("In Catch");
reject(err);
});
});
}
decriptFieUsingOpenSSL(fileName, password) {
console.log("Decrypting file...");
return new Promise((resolve, reject) => {
var encFile = `../uploads/subscriptions/${fileName}.zip.enc`;
var zipFile = `../uploads/subscriptions/${fileName}.zip`;
var openSSLCmd = `enc -aes-256-cbc -md sha512 -d -in ${encFile} -out ${zipFile} -k ${password}`;
console.log('start running openssl command ' + openSSLCmd);
setTimeout(function () {
openssl(openSSLCmd, () => {
resolve();
})
}, 5000);
});
}
updateSubscriptionData(){
return new Promise((resolve, reject) => {
this.importSubscriptions().then(response => {
if(response.error !== undefined){
console.log(`Error Code: ${response.error.code}`);
console.log(`Message: ${response.error.message}`);
reject();
}else{
var id = response.id;
var password = response.password;
this.checkImportSubscriptionJobStatus(id, 0).then(fileUrl => {
this.downloadFile(id, fileUrl).then(() => {
var fileName = `${id}.csv`;
this.decriptFieUsingOpenSSL(fileName, password).then(() => {
console.log("File decrypted successfully...");
var targetFolder = path.join(__dirname, '..', 'uploads', 'subscriptions');
var zipFile = path.join(targetFolder, `${fileName}.zip`);
extract(zipFile, { dir : targetFolder }).then(() => {
console.log("Extracted file successfully");
var fullFileName = path.join(targetFolder, fileName);
this.getCSVData(fullFileName).then(res => {
if(res){
SubscriptionController.updateSubscriptions(res).then(data => {
resolve('Subscriptions data replicated successfully!!!');
console.log("CHECK NOTIFICATION");
sendPasswordNotificationAfterBatch();
}).catch(err => {
throw err;
});
}
}).catch(error => {
reject(error);
})
})
});
});
});
}
}).catch(err => {
reject(err);
})
});
}
}
module.exports = new UploadController();
Model Code : auth.js
var CryptoJS = require("crypto-js");
var request = require("request");
var httpRequest = require("http");
var httpsRequest = require("https");
var config = require('../config.json');
class AuthModel {
constructor() {
this.timestamp = Math.floor((new Date()).getTime() / 1000);
this.consumer_key = config.app.consmer_key;
this.consumer_secret = config.app.consumer_secret;
this.callback_url = config.app.callback_url;
this.partner_csn = config.app.partner_csn;
this.environment_url_stg = "enterprise-api-stg.autodesk.com"; //STG Environment
this.environment_url_prd = "enterprise-api.autodesk.com"; //prd Environment
this.access_token = '';
this.api_timestamp = '';
}
getbaseUrl()
{
var env = config && config.app.env;
console.log("ENVIRONMENT");
console.log(env);
if(env == 'prd')
{
return this.environment_url_prd;
console.log("ENVIRONMENT URL PID");
console.log(this.environment_url_prd);
}
else{
return this.environment_url_stg;
console.log("ENVRIONMENT URL STG");
console.log(this.environment_url_stg);
}
}
get(url, headerData){
var self = this;
return new Promise((resolve, reject) =>{
self.getAccessToken().then(function(token){
var time = Math.floor(Date.now() / 1000);
var header = {
'CSN': self.partner_csn,
'signature': self.getAPISignature(token),
'timestamp': self.api_timestamp,
'Authorization': "Bearer " + token
};
var options = {
method: 'GET',
url: `https://${ self.getbaseUrl() }/${url}`,
headers:header
};
request(options, function (error, response, body) {
if (error) {
reject(error);
}
resolve(body);
});
}).catch(function(err){
reject(err);
});
});
}
post(url, body){
console.log(url);
var self = this;
return new Promise((resolve, reject) =>{
self.getAccessToken().then(function(token){
var time = Math.floor(Date.now() / 1000);
var headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Bearer ${token}`,
'signature': self.getAPISignature(token),
'timestamp': self.api_timestamp,
'CSN': self.partner_csn
};
var options = {
method: 'POST',
url: `https://${ self.getbaseUrl() }/${url}`,
headers,
form: body
};
request(options, function (error, response) {
if (error){
reject(error);
}
resolve(response.body);
});
}).catch(function(err){
reject(err);
});
});
}
getAPISignature(token)
{
this.api_timestamp = Math.floor(Date.now() / 1000);
var message = this.callback_url + token + this.api_timestamp;
var hash = CryptoJS.HmacSHA256(message, this.consumer_secret);
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
return hashInBase64;
}
createSignature(time)
{
var message = this.callback_url + this.consumer_key + time;
console.log(message);
var hash = CryptoJS.HmacSHA256(message, this.consumer_secret);
console.log(this.consumer_key);
console.log(this.consumer_secret);
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
return hashInBase64;
}
createAuthorization(){
var passwordSignature = this.consumer_key + ":" + this.consumer_secret;
console.log(passwordSignature);
var authorization = Buffer.from(passwordSignature).toString('base64')
console.log("Authorization");
console.log(authorization);
return "Basic " + authorization;
}
getAccessToken(){
console.log("IN GetAccessToken");
var self = this;
var time = Math.floor((new Date()).getTime() / 1000);
return new Promise((resolve, reject) =>{
var options = {
method: 'POST',
url:`https://${self.getbaseUrl()}/v2/oauth/generateaccesstoken?grant_type=client_credentials`,
headers: {
timestamp: time,
signature: self.createSignature(time),
Authorization : self.createAuthorization()
}
};
request(options, function (error, response, body) {
if (error) {
reject(error);
}
resolve(JSON.parse(body).access_token);
console.log(JSON.parse(body).access_token);
});
});
}
getUsingHttpPlugin(url, headerData) {
var self = this;
return new Promise((resolve, reject) => {
self.getAccessToken().then(function (token) {
console.log("Get Access Token");
console.log(token);
var time = Math.floor(Date.now() / 1000);
var header = {
'CSN': self.partner_csn,
'signature': self.getAPISignature(token),
'timestamp': self.api_timestamp,
'Authorization': "Bearer " + token
};
var options = {
method: 'GET',
host: `${self.getbaseUrl()}`,
path: `${url}`,
headers: header
};
var s = httpRequest.request(options, (res) => {
resolve(res);
});
s.end();
}).catch(function (err) {
reject(err);
});
});
}
getPlainHttp(url) {
console.log("URL");
console.log(url);
return new Promise((resolve, reject) => {
console.log("In 1");
var s = httpsRequest.get(url, (res) => {
console.log("RESPONSE STATUS CODE");
console.log(res.statusCode);
if (res.statusCode > 200) {
reject(res);
}
resolve(res);
})
s.end();
});
}
}
module.exports = AuthModel;
If any other Node code needed from my side, I am happy to share.

AWS S3 Angular 14 with Nodejs - Multi Part Upload sending the same ETag for every part

AWS S3 Angular 14 with Nodejs - Multi Part Upload sending the same ETag for every part
Backend Nodejs Controller Looks Like -
const AWS = require('aws-sdk');
const S3 = new AWS.S3({
// endpoint: "http://bucket.analysts24x7.com.s3-website-us-west-1.amazonaws.com",
// accessKeyId: S3_KEY,
// secretAccessKey: S3_SECRET,
// region: process.env.POOL_REGION,
apiVersion: '2006-03-01',
signatureVersion: 'v4',
// maxRetries: 10
});
exports.startUpload = (req, res) => {
try {
const filesData = JSON.parse(JSON.stringify(req.files));
const eachFiles = Object.keys(filesData)[0];
console.log(filesData[eachFiles]);
let params = {
Bucket: process.env.STORE_BUCKET_NAME,
Key: filesData[eachFiles].name,
// Body: Buffer.from(filesData[eachFiles].data.data, "binary"),
ContentType: filesData[eachFiles].mimetype
// ContentType: filesData[eachFiles].data.type
};
return new Promise((resolve, reject) => {
S3.createMultipartUpload(params, (err, uploadData) => {
if (err) {
reject(res.send({
error: err
}));
} else {
resolve(res.send({ uploadId: uploadData.UploadId }));
}
});
});
} catch(err) {
res.status(400).send({
error: err
})
}
}
exports.getUploadUrl = async(req, res) => {
try {
let params = {
Bucket: process.env.STORE_BUCKET_NAME,
Key: req.body.fileName,
PartNumber: req.body.partNumber,
UploadId: req.body.uploadId
}
return new Promise((resolve, reject) => {
S3.getSignedUrl('uploadPart', params, (err, presignedUrl) => {
if (err) {
reject(res.send({
error: err
}));
} else {
resolve(res.send({ presignedUrl }));
}
});
})
} catch(err) {
res.status(400).send({
error: err
})
}
}
exports.completeUpload = async(req, res) => {
try {
let params = {
Bucket: process.env.STORE_BUCKET_NAME,
Key: req.body.fileName,
MultipartUpload: {
Parts: req.body.parts
},
UploadId: req.body.uploadId
}
// console.log("-----------------")
// console.log(params)
// console.log("-----------------")
return new Promise((resolve, reject) => {
S3.completeMultipartUpload(params, (err, data) => {
if (err) {
reject(res.send({
error: err
}));
} else {
resolve(res.send({ data }));
}
})
})
} catch(err) {
res.status(400).send({
error: err
})
};
}
FrontEnd Angular 14 Code --
uploadSpecificFile(index) {
const fileToUpload = this.fileInfo[index];
const formData: FormData = new FormData();
formData.append('file', fileToUpload);
this.shared.startUpload(formData).subscribe({
next: (response) => {
const result = JSON.parse(JSON.stringify(response));
this.multiPartUpload(result.uploadId, fileToUpload).then((resp) => {
return this.completeUpload(result.uploadId, fileToUpload, resp);
}).then((resp) => {
console.log(resp);
}).catch((err) => {
console.error(err);
})
},
error: (error) => {
console.log(error);
}
})
}
multiPartUpload(uploadId, fileToUpload) {
return new Promise((resolve, reject) => {
const CHUNKS_COUNT = Math.floor(fileToUpload.size / CONSTANTS.CHUNK_SIZE) + 1;
let promisesArray = [];
let params = {};
let start, end, blob;
for (let index = 1; index < CHUNKS_COUNT + 1; index++) {
start = (index - 1) * CONSTANTS.CHUNK_SIZE
end = (index) * CONSTANTS.CHUNK_SIZE
blob = (index < CHUNKS_COUNT) ? fileToUpload.slice(start, end) : fileToUpload.slice(start);
// blob.type = fileToUpload.type;
params = {
fileName: fileToUpload.name,
partNumber: index,
uploadId: uploadId
}
console.log("Start:", start);
console.log("End:", end);
console.log("Blob:", blob);
this.shared.getUploadUrl(params).subscribe({
next: (response) => {
const result = JSON.parse(JSON.stringify(response));
// Send part aws server
const options = {
headers: { 'Content-Type': fileToUpload.type }
}
let uploadResp = axios.put(result.presignedUrl, blob, options);
promisesArray.push(uploadResp);
if(promisesArray.length == CHUNKS_COUNT) {
resolve(promisesArray)
}
},
error: (error) => {
console.log(error);
reject(error);
}
})
}
})
}
async completeUpload(uploadId, fileToUpload, resp) {
let resolvedArray = await Promise.all(resp)
let uploadPartsArray = [];
console.log("I am etag -----");
console.log(resolvedArray);
resolvedArray.forEach((resolvedPromise, index) => {
uploadPartsArray.push({
ETag: resolvedPromise.headers.etag,
PartNumber: index + 1
})
})
// Complete upload here
let params = {
fileName: fileToUpload.name,
parts: uploadPartsArray,
uploadId: uploadId
}
return new Promise((resolve, reject) => {
this.shared.completeUpload(params).subscribe({
next: (response) => {
resolve(response);
},
error: (error) => {
reject(error);
}
})
})
}
What I am trying to do --
Initiate a multipart upload ( API - /start-upload ) --> to get the uploadId
Upload the object’s parts ( API - /get-upload-url ) --> to get the presignedUrl
Call the Presigned URL and put blob as part --- To get the Etag
Complete multipart upload ( API - /complete-upload ) --> to send the complete parts.
**Sample Example of code --- **
FrontEnd --
https://github.com/abhishekbajpai/aws-s3-multipart-upload/blob/master/frontend/pages/index.js
BackEnd --
https://github.com/abhishekbajpai/aws-s3-multipart-upload/blob/master/backend/server.js
Attach the screenshot below how the API call looks like --
Now the problem here, Each and everytime I am getting same Etag from the -- above 3 steps while I am calling presignedURL using Axios. For that reason, I am getting the error in the final upload ---
Your proposed upload is smaller than the minimum allowed size
**Note --
**
Each and every chuck size I am uploading
CHUNK_SIZE: 5 * 1024 * 1024, // 5.2 MB
Apart from last part.
Also all the API are giving success response, apart from /complete-upload. Because all the API giving same Etag.
Same question also asked here, but there are no solutions --
https://github.com/aws/aws-sdk-java/issues/2615
Any idea about this ? How to resolve it ?
This is so uncommon problem, Provide me the solution of the problem.

How do I use spies for Chai assertion library?

I want to use Chai on a project to test login functionality, here is the login function:
public async login(request: Request, response: Response): Promise<Response | any> {
const staging = request.body.staging;
const coreUri = (staging) ? 'apiurl' : 'testapiurl';
const email = request.body.email;
const password = request.body.password;
return new Promise((resolve, reject) => {
req({
uri: `${coreUri}/tokens`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
json: {
email: email,
password: password,
},
}, (error, resp, body) => {
if (error) {
return reject(error);
}
let statusCode: number;
let newBody: JsonData = null;
if (resp.statusCode === 403) {
statusCode = 401;
newBody = {
error: 'Invalid user credentials',
};
} else {
if (resp.statusCode === 200) {
const roles = (((body || {}).user || {}).roles || []);
if (AUTHORIZED_ROLES.some(v => roles.indexOf(v) >= 0)) {
statusCode = resp.statusCode;
newBody = body;
appConfigurationService.setLastAuthorization(body, staging);
const token = body.token;
HttpServer.saveSessionToken(request, token);
logger.info(`User ${email} logged in.`);
} else {
statusCode = 403;
newBody = {
error: 'User is not allowed to perform this action',
};
}
} else {
statusCode = resp.statusCode;
newBody = resp.body;
}
}
This is how I've written the test:
const spies = require('chai-spies');
chai.use(spies);
describe('API Endpoints tests', () => {
beforeEach(() => {
chai.spy.restore();
});
describe('/POST login', () => {
it('it should login a user and return an object', async () => {
chai.spy.on(apiController, 'login', () => true);
const success = await (<any>apiController).login(loginUserDetails);
console.log(success);
chai.assert.equal(success, 200);
});
});
});
But I always get a true on const success, even when I'm passing wrong credentials. I'm sure there's something I'm not getting right, but I don't really know where exactly in the code.

Request body sometimes null

The request body sometimes (less than 1% of the time) is null when pulled into lambda. I am processing on the order of 14,000 request bodies one at a time. Any request bodies erring out have to be handled manually. Why is the body randomly coming in null?
Sample code ran from prompt (node index):
const async = require('async');
const _ = require('lodash');
const moment = require('moment');
const Client = require('node-rest-client').Client;
const fs = require('fs');
const input = require('./TestFull.json');
module.exports = () => {
const filename = `./eventfulFails-${new moment().format("YYYY-MM-DD-HHmmss")}.json`;
console.log('Start Time: ', new moment().format("HH:mm:ss"));
let failedObjects = {
events: [],
venues: [],
performers: []
};
async.parallel([
async.apply(processVenues, input.venues, failedObjects),
async.apply(processPerformers, input.performers, failedObjects)
], (lookupErr) => {
if (lookupErr) {
return console.error('Error processing venues and performers.', lookupErr);
}
console.log('Start Events: ', new moment().format("HH:mm:ss"));
async.waterfall([
async.apply(processEvents, input.events, failedObjects)
], (eventErr) => {
if (eventErr) {
console.log('Time of Failure: ', new moment().format("HH:mm:ss"));
return console.error('Error processing events.', eventErr);
}
console.log('End Time: ', new moment().format("HH:mm:ss"));
if (failedObjects.events.length || failedObjects.venues.length || failedObjects.performers.length) {
const stream = fs.createWriteStream(filename);
stream.once('open', function(fd) {
stream.write(JSON.stringify(failedObjects));
stream.end();
});
}
});
});
};
function processVenues(venues, failedObjects, callback) {
const calls = [];
for (let i = 0; i < venues.length; i++) {
const v = venues[i];
calls.push(async.apply((venue, postCallback) => {
const client = new Client();
const args = {
data: venue,
headers: {"Content-Type": "application/json"}
};
client.post('https://hm1br4yo34.execute-api.us-west-2.amazonaws.com/dev/eventful-venue', args, (data, response) => {
if (response.statusCode !== 200 && response.statusCode !== 201) {
failedObjects.venues.push({
venue,
response
});
console.log('venue status code: ', response);
console.log('venue data: ', venue);
}
return postCallback(null);
});
}, v));
}
async.waterfall(calls, callback);
}
function processPerformers(performers, failedObjects, callback) {
const calls = [];
for (let i = 0; i < performers.length; i++) {
const v = performers[i];
calls.push(async.apply((performer, postCallback) => {
const client = new Client();
const args = {
data: performer,
headers: {"Content-Type": "application/json"}
};
client.post('https://hm1br4yo34.execute-api.us-west-2.amazonaws.com/dev/eventful-performer', args, (data, response) => {
if (response.statusCode !== 200 && response.statusCode !== 201) {
failedObjects.performers.push({
performer,
response
});
console.log('performer status code: ', response);
console.log('performer data: ', performer);
}
return postCallback(null);
});
}, v));
}
async.waterfall(calls, callback);
}
function processEvents(events, failedObjects, callback) {
const calls = [];
for (let i = 0; i < events.length; i++) {
const v = events[i];
calls.push(async.apply((event, postCallback) => {
const client = new Client();
const args = {
data: event,
headers: {"Content-Type": "application/json"}
};
client.post('https://hm1br4yo34.execute-api.us-west-2.amazonaws.com/dev/eventful', args, (data, response) => {
if (response.statusCode !== 200 && response.statusCode !== 201) {
failedObjects.events.push({
event,
response
});
console.log('event status code: ', response);
console.log('event data: ', event);
}
return postCallback(null);
});
}, v));
}
async.waterfall(calls, callback);
}
if (!module.parent) {
module.exports();
}
Code of function processVenues (eventful-venue-load) that is called:
const _ = require('lodash');
const AWS = require('aws-sdk');
const async = require('async');
const sdk = require('#consultwithmikellc/withify-sdk');
const host = process.env.aurora_host;
const user = process.env.aurora_user;
const database = process.env.aurora_database;
let decryptedPassword;
const lambda = new AWS.Lambda({
region: 'us-west-2' //your region
});
class WithifyEventCreate extends sdk.Lambda {
constructor(event, context, keysToDecrypt) {
super(event, context, keysToDecrypt);
this.getLocation = this.getLocation.bind(this);
this.insertLocations = this.insertLocations.bind(this);
this.insertLocationImages = this.insertLocationImages.bind(this);
}
decryptedKey(key, value) {
switch (key) {
case 'aurora_password':
decryptedPassword = value;
break;
}
}
initializeComplete() {
this.connect(host, user, decryptedPassword, database, true);
}
connectComplete() {
async.waterfall(
[
this.getLocation,
this.insertLocations,
this.insertLocationImages
]
);
}
getLocation(callback) {
const {id: eventfulLocationID} = this.body;
this.connection.query('SELECT * FROM `Location` WHERE `eventfulLocationID` = ?',
[eventfulLocationID],
(err, results) => {
if (err) {
// error call block
return this.sendResponse(err, this.createResponse(500));
} else if (results.length === 1) {
console.log('Invoking withify-eventful-venue-update...');
lambda.invoke({
FunctionName: 'withify-eventful-venue-update',
Payload: JSON.stringify(this.event)
}, (error, data) => {
return this.sendResponse(null, JSON.parse(data.Payload));
});
} else if (results.length > 1) {
return this.sendResponse(`The location lookup produced multiple results. event:${JSON.stringify(this.body)}`, this.createResponse(500));
} else {
return callback(null);
}
}
);
}
insertLocations(callback) {
const {name: locationName, address: street, city, region_abbr: state, postal_code,
description, id: eventfulLocationID, latitude: lat, longitude: lng, withdrawn: locationWithdrawn} = this.body;
let addresses = street.concat(', ', city, ', ', state, ', ', postal_code);
if (!description.length){
var phones = "";
}else{
var re = /(([\(][0-9]{3}[\)][\s][0-9]{3}[-][0-9]{4})|([0-9]{3}[-][0-9]{3}[-][0-9]{4})|([0-9]{3}[\.][0-9]{3}[\.][0-9]{4}))/i;
this.body.found = description.match(re);
if (!this.body.found){
var phone = "";
}else{
if (!this.body.found.length){
var phone = "";
}else{
var phone = this.body.found[0];
}
}
}
this.connection.query('INSERT IGNORE INTO `Location` (`locationName`, `address`, ' +
'`phone`, `lat`, `lng`, `eventfulLocationID`, `locationWithdrawn`) VALUES (?, ?, ?, ?, ?, ?, ?)',
[locationName, addresses, phone, lat, lng, eventfulLocationID, locationWithdrawn],
(err, results) => {
if (err) {
return this.sendResponse(err, this.createResponse(500));
}
this.body.locationID = results.insertId;
return callback(null);
}
);
}
insertLocationImages(callback) {
var altText = "";
const images = _.flatten(this.body.images.map(im => {
return _.map(im.sizes, (ims, idx) => {
const title = `Image ${idx}`;
return [
this.body.locationID,
this.body.name,
ims.url,
null,
null,
this.body.id,
ims.width,
ims.height
];
});
}));
if(!images[0]){
return this.sendResponse(null, this.createResponse(201, this.body));
}
this.connection.query('INSERT IGNORE INTO `LocationImage` (`locationID`, `imageTitle`, `imageUrl`, ' +
'`imageName`, `altText`, `eventfulLocationID`, `width`, `height`) VALUES ?',
[images],
(err, results) => {
if (err) {
return this.sendResponse(err, this.createResponse(500));
} else if (results.affectedRows !== images.length) {
return this.sendResponse('The image inserts did not affect the right number' +
' of rows.', this.createResponse(500));
}
return this.sendResponse(null, this.createResponse(201, this.body));
}
);
}
}
exports.handler = (event, context) => {
const withifyEventCreate = new WithifyEventCreate(event, context, ['aurora_password']);
withifyEventCreate.initialize([decryptedPassword]);
};

Nodejs promise all not running as expected

I have a series of promises which I have chained in testCard. This method takes a stripe card number, get the token from stripe and then talks to a third party API which tries to perform purchases with that card.
I need to run testCard by looping through an array of card numbers. To do this I have a controller object with a method testAllCards which takes the array of numbers. The array is stored in a config file.
I then run the code from the command line with node cli.js testAllCards.
However when I run it, I get testAllCards has been run before all most promises have resolved.
I am obviously missing something here, but can't seem to figure out what it is.
cli.js
const testAllCards = () => {
return controller.testAllCards(config.get('CARD_NUMBERS'))
.then((obj) => {
console.log('testAllCards has been run');
})
.catch((e) => {
console.log('testCards has been run with an error!');
const _err = new ErrHandler(e, eTopicName, eSnsSubject);
_err.handle()
.then(() => {
console.log('Error has been sent with success to sns');
});
});
};
switch(process.argv[2]) {
case 'testAllCards':
testAllCards();
break;
default:
console.log('Please run with `testAllCards`');
controller.js
//Tests response from API for different cards
const testCard = (cardNum) => {
return new Promise((resolve, reject) => {
const expMonth = new Date().getMonth() + 1;
const expYear = new Date().getFullYear() + 2;
const cardObj = {
cardNum: cardNum,
expMonth: expMonth,
expYear: expYear
};
let apiCardItem = '';
return testRequestToApi('getStripeToken', 200, 299, cardObj)
.then((cardItem) => {
return testRequestToApi('postNewCard', 200, 299, JSON.parse(cardItem.body));
})
.then((apiCard) => {
apiCardItem = apiCard.body;
try {
apiCardItem = JSON.parse(apiCardItem);
} catch(e) {
console.log(e);
}
return testRequestToApi('sampleAddToCart', 200, 299);
})
.then(() => {
return testRequestToApi('useFailingStripeCards', 400, 499, apiCardItem.id);
})
.then(() => {
return testRequestToApi('deleteCard', 200, 299, apiCardItem.id);
})
.then(() => {
resolve();
})
.catch((e) => {
reject(e);
});
});
};
//Loops through the card numbers and runs the test command against them
Controller.testAllCards = (cardsArray) => {
const items = cardsArray.map((cardNum) => {
return testCard(cardNum);
});
return Promise.all(items);
};
module.exports = Controller;
test-request-to-api.js
'use strict';
const checkStatus = require('./../utils/status-code-checker');
const formHeaders = require('./../utils/form-req-headers');
const request = require('request');
const expObj = {};
//#requestType {string} - defines which headers and function name to use
//#item {object} - defines item that is being used
expObj.testRequestToApi = (requestType, lowerLimit, upperLimit, item) => {
return new Promise((resolve, reject) => {
const reqOps = formHeaders[requestType](item);
request(reqOps, (err, response, body) => {
if (err) {
const badRequest = {
ErrorMessage: err,
FuncName: requestType,
InternalError: true
};
return reject(badRequest);
}
if (!checkStatus.checkRangeStatusCode(response.statusCode, lowerLimit, upperLimit)) {
console.log(JSON.stringify(body, null, 2));
// Set a bad Status error object
let badStatus = {
StatusCode: response.statusCode,
ErrorMessage: body,
FuncName: requestType,
InternalError: false
};
return reject(badStatus);
}
// console.log(response.headers);
// console.log(body);
const resObj = {
headers: response.headers,
body: body
};
// console.log(`******** ${requestType} *********`);
// console.log(resObj);
// console.log('----------------------------------');
return resolve(resObj);
});
});
};
module.exports = expObj;
Understanding that new Promise() is used only ever necessary when promisifying a callback based API, changing to request-promise and returning my promises in cli.js solved my issue. The execution flow was correctly maintained in this manner.
Changes to the following files are as followed:
cli.js
const testAllCards = () => {
return controller.testAllCards(config.get('CARD_NUMBERS'))
.then((obj) => {
console.log('testAllCards has been run');
})
.catch((e) => {
console.log(e)
console.log('testCards has been run with an error!');
const _err = new ErrHandler(e, eTopicName, eSnsSubject);
return _err.handle()
.then(() => {
console.log('Error has been sent with success to sns');
})
.catch((e) => {
console.log('Failed to publish to sns');
console.log(e);
});
});
};
test-request-to-api
'use strict';
const checkStatus = require('./../utils/status-code-checker');
const formHeaders = require('./../utils/form-req-headers');
const rqp = require('request-promise');
const expObj = {};
//#requestType {string} - defines which headers and function name to use
//#item {object} - defines item that is being used
expObj.testRequestToApi = (requestType, lowerLimit, upperLimit, item) => {
const reqOps = formHeaders[requestType](item);
return rqp(reqOps)
.then((response) => {
if (!checkStatus.checkRangeStatusCode(response.statusCode, lowerLimit, upperLimit)) {
console.log(JSON.stringify(response.body, null, 2));
// Set a bad Status error object
return {
StatusCode: response.statusCode,
ErrorMessage: response.body,
FuncName: requestType,
InternalError: false
};
}
// console.log(response.headers);
// console.log(response.body);
const resObj = {
headers: response.headers,
body: response.body,
previousItem: item
};
// console.log(`******** ${requestType} *********`);
// console.log(resObj);
// console.log('----------------------------------');
return resObj;
})
.catch((e) => {
return {
ErrorMessage: e,
FuncName: requestType,
InternalError: true
};
});
};
module.exports = expObj;
controller.js
//Tests response from API for different cards
Controller.testCard = (cardNum) => {
const expMonth = new Date().getMonth() + 1;
const expYear = new Date().getFullYear() + 2;
const cardObj = {
cardNum: cardNum,
expMonth: expMonth,
expYear: expYear
};
let apiCardItem = '';
return testRequestToApi('getStripeToken', 200, 299, cardObj)
.then((cardItem) => {
return testRequestToApi('postNewCard', 200, 299, JSON.parse(cardItem.body));
})
.then((apiCard) => {
apiCardItem = apiCard.body;
try {
apiCardItem = JSON.parse(apiCardItem);
} catch(e) {
console.log('Already a JSON object -----> Moving on');
}
return testRequestToApi('sampleAddToCart', 200, 299);
})
.then(() => testRequestToApi('useFailingStripeCards', 400, 499, apiCardItem.id))
.then(() => testRequestToApi('deleteCard', 200, 299, apiCardItem.id));
};
//Loops through the card numbers and runs the test command against them
Controller.testAllCards = (cardsArray) => {
return Promise.all(cardsArray.map((cardNum) => {
return Controller.testCard(cardNum);
}));
};
module.exports = Controller;
I rewrote your "testCard" function (controller.js)
//Tests response from API for different cards
const testCard = (cardNum) => {
return new Promise((resolve, reject) => {
let apiCardItem = '';
const expDate = new Date();
const cardObj = {
cardNum: cardNum,
expMonth: expDate.getMonth() + 1,
expYear: expDate.getFullYear() + 2
};
testRequestToApi('getStripeToken', 200, 299, cardObj)
.then((cardItem) => testRequestToApi('postNewCard', 200, 299, JSON.parse(cardItem.body)))
.then((apiCard) => {
apiCardItem = apiCard.body;
try {
apiCardItem = JSON.parse(apiCardItem);
} catch(e) {
console.log(e);
}
return testRequestToApi('sampleAddToCart', 200, 299);
})
.then(() => testRequestToApi('useFailingStripeCards', 400, 499, apiCardItem.id))
.then(() => testRequestToApi('deleteCard', 200, 299, apiCardItem.id))
.then(resolve)
.catch(reject);
});
};
And your "testRequestToApi" (test-request-to-api.js)
expObj.testRequestToApi = (requestType, lowerLimit, upperLimit, item) => {
return new Promise((resolve, reject) => {
const reqOps = formHeaders[requestType](item);
request(reqOps, (err, response, body) => {
let badStatus = {};
let badRequest = {};
let resObj = {};
if (err) {
badRequest = {
ErrorMessage: err,
FuncName: requestType,
InternalError: true
};
reject(badRequest);
return false;
}
if (!checkStatus.checkRangeStatusCode(response.statusCode, lowerLimit, upperLimit)) {
console.log(JSON.stringify(body, null, 2));
// Set a bad Status error object
badStatus = {
StatusCode: response.statusCode,
ErrorMessage: body,
FuncName: requestType,
InternalError: false
};
reject(badStatus);
return false;
}
resObj = {
headers: response.headers,
body: body
};
resolve(resObj);
});
});
};
I think the problem is when you return from a promise before a resolve/reject is getting called.
Also check that in nested promises you can pass resolve/reject to .then/catch for brevity.

Resources