Why would a dictionary variable be undefined in async function - node.js

I'm trying to figure out why the variable ROOT_FOLDER_LOCATION is undefined. The function is running as a google "cloud function" and it is triggered from ZOHO CRM in which it is sent URL encoded data containing the variables 'ID', 'firstName', 'lastName', 'Salesman'. I have tried getting this to work by 'awaiting' pretty much every line in the try-catch block, moving the salesmanID variable inside of the function, but it doesn't work. The salesman and workingRoot variables are both defined (salesman='Jim Schwartz' in my testing and workingRoot='abcdefgXv2'), so I don't understand why the ROOT_FOLDER_LOCATION variable is undefined.
const {google} = require('googleapis');
const axios = require('axios').default;
const FormData = require('form-data');
const SCOPES = ['https://www.googleapis.com/auth/drive'];
//IDs for client folders of salesmen
const salesmanID = {'Jim Schwartz': 'abcdefgXv2', 'Marks Shared Folder': '1M6NUf-abcdefg', 'Samanthas Shared Folder': 'abcdefgVZSMzhoVBU5wJ'};
exports.myFunction = async (req, res) => {
try{
const params = new URLSearchParams(req.body);
let zohoLeadID = params.get('ID');
let firstName = params.get('firstName');
let lastName = params.get('lastName');
let CLIENT_NAME = `${lastName}, ${firstName}`
let salesman = params.get('Salesman');
let ROOT_FOLDER_LOCATION = salesmanID[salesman];
let workingRoot = salesmanID['Jim Schwartz'];
console.log(salesman);
console.log(`workingRoot: ${workingRoot}`);
console.log(`${ROOT_FOLDER_LOCATION}`);
res.status(200).send('Success');
} catch (err) {
console.log(err);
res.status(500).send('Failed...');
}
};

Related

Get data from firestore document and use in cloud function

In the user's collection, each user has a document with a customer_id.
I would like to retrieve this customer_id and use it to create a setup intent.
The following code has worked for me in the past. However, all of a sudden it throws the error:
Object is possibly 'undefined'
The error is on the following line under snapshot.data() in this line:
const customerId = snapshot.data().customer_id;
Here is the entire code snippet:
exports.createSetupIntent = functions.https.onCall(async (data, context) => {
const userId = data.userId;
const snapshot = await db
.collection("development")
.doc("development")
.collection("users")
.doc(userId).get();
const customerId = snapshot.data().customer_id;
const setupIntent = await stripe.setupIntents.create({
customer: customerId,
});
const clientSecret = setupIntent.client_secret;
const intentId = setupIntent.id;
return {
clientsecret: clientSecret,
intentId: intentId,
};
});
Any help is appreciated :)
this is because snapshot.data() may return undefined
there are 2 ways to solve this
first is assert as non-null, if you have high confident that the data exist
const customerId = snapshot.data()!.customer_id;
second if check for undefined
const customerId = snapshot.data()?.customer_id;
if(customerId){
// ....
}
I recommend the 2nd method, it is safer
I can see you are using a sub collection order,You need to loop through the snapshot data using the forEach loop.
const customerId = snapshot.data()
customerId.forEach((id)=> {
console.log(id.customer_id)
});
Try this out but.
The document you're trying to load may not exist, in which case calling data() on the snapshot will return null, and thus this line would give an error:
const customerId = snapshot.data().customer_id;
The solution is to check whether the document you loaded exists, and only then force to get the data from it:
if (snapshot.exists()) {
const customerId = snapshot.data()!.customer_id;
...
}
if you want to fetch user data from docId then you can use something like this:
const functions = require("firebase-functions");
var admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
var db = admin.firestore();
db.settings({ timestampsInSnapshots: true });
exports.demoFunction = functions.https.onRequest((request, response) => {
var userId = request.body.userId;
db.collection("user").doc(userId).get().then(snapshot => {
if (snapshot) {
var data = snapshot.data();
// use data to get firestore data
var yourWantedData = data.name;
// use it in your functionality
}
});
});

async/await not working with mongoose instance methods

I'm try to create user mongoose document which require a storage path. I want to await for directory to be created and path resolved. But it is not working and after user is saved, user.storagePath is still undefined. please figure out problem.
Following is code of createStorage()
const getMountRoot = require('../configuration/configuration').getMountRoot
const path = require('path')
const fs = require('fs')
const Logger = require('../configuration/configuration').getLogger
module.exports = (email, firstName, secondName) => {
email = String(email).toLowerCase().replace(/[#_\.\-]/g, '')
firstName = String(firstName).toLowerCase().replace(/[#_\.\-]/g, '')
secondName = String(secondName).toLowerCase().replace(/[#_\.\-]/g, '')
let storagePath = path.join(getMountRoot(), `${secondName}${email}${firstName}`)
return fs.promises.mkdir(storagePath, { recursive: true })
.then(() => { return storagePath })
.catch(() => {Logger.log(err); return storagePath})
}
And following is instance method
const createStorage = require('../extra/create-storage')
userSchema.methods.createStorage = async function() {
this.storagePath = await createStorage(this.email, this.firstName, this.secondName)
}
Kindly note that I call createStorage() on User instance before calling the save()
As #qqilihq figured, I need to await at instance method call. Doing that worked correctly.

All my scraped text ends up in one big object instead of separate objects with Cheerio

I'm following a web scraping course that uses Cheerio. I practice on a different website then they use in the course and now I run into the problem that all my scraped text end up in one big object. But every title should end up in it's own object. Can someone see what I did wrong? I already bumbed my head 2 hours on this problem.
const request = require('request-promise');
const cheerio = require('cheerio');
const url = "https://huurgoed.nl/gehele-aanbod";
const scrapeResults = [];
async function scrapeHuurgoed() {
try {
const htmlResult = await request.get(url);
const $ = await cheerio.load(htmlResult);
$("div.aanbod").each((index, element) => {
const result = $(element).children(".item");
const title = result.find("h2").text().trim();
const characteristics = result.find("h4").text();
const scrapeResult = {title, characteristics};
scrapeResults.push(scrapeResult);
});
console.log(scrapeResults);
} catch(err) {
console.error(err);
}
}
scrapeHuurgoed();
This is the link to the repo: https://github.com/danielkroon/huurgoed-scraper/blob/master/index.js
Thanks!
That is because of the way you used selectors. I've modified your script to fetch the content as you expected. Currently the script is collecting titles and characteristics. Feel free to add the rest within your script.
This is how you can get the required output:
const request = require('request-promise');
const cheerio = require('cheerio');
const url = "https://huurgoed.nl/gehele-aanbod";
const scrapeResults = [];
async function scrapeHuurgoed() {
try {
const htmlResult = await request.get(url);
const $ = await cheerio.load(htmlResult);
$("div.item").each((index, element) => {
const title = $(element).find(".kenmerken > h2").text().trim();
const characteristics = $(element).find("h4").text().trim();
scrapeResults.push({title,characteristics});
});
console.log(scrapeResults);
} catch(err) {
console.error(err);
}
}
scrapeHuurgoed();

Retrieve AWS ssm parameter in bulk

How can I retrieve parameters from AWS Systems Manager (parameter store) in bulk (or more than one parameter) at a time? Using aws-sdk, following is the Node.js code I have written to retrieve SSM parameter from parameter store:
const ssm = new (require('aws-sdk/clients/ssm'))()
const getSSMKey = async params => {
const {Parameter: {Value: APIKey}} = await ssm.getParameter(params).promise()
return APIKey
}
const [param1, param2, param3] = await Promise.all([
getSSMKey({ Name: '/data/param/PARAM1', WithDecryption: true }),
getSSMKey({ Name: '/data/param/PARAM2', WithDecryption: true }),
getSSMKey({ Name: '/data/param/PARAM3', WithDecryption: true })
])
console.log(param1, param2, param3)
But with this code, I am sending 3 request for getting 3 parameters which is inefficient in case of large number of parameters. Is there any way to retrieve more than one parameters in one request. if ssm.getParameters() is the method to do that then please give an example (particularly parameter to that method). I tried but I receive nothing.
According to the AWS document, GetParameter gets the value for one parameter, whereas GetParameters gets the value for multiple.
Their usages are very similar too. When using GetParameters to get multiple values, pass in multiple names as a list for Names, instead of passing a single name as string for Name.
Code sample, to get parameters named "foo" and "bar", in "us-west-1" region:
const AWS = require('aws-sdk');
AWS.config.update({ region: "us-west-1" });
const SSM = require('aws-sdk/clients/ssm');
const ssm = new SSM()
const query = {
"Names": ["foo", "bar"],
"WithDecryption": true
}
let param = ssm.getParameters(query, (err, data) => {
console.log('error = %o', err);
console.log('raw data = %o', data);
})
At last it worked for me. Following is the code:
const ssmConfig = async () => {
const data = await ssm.getParameters({ Names: ['/data/param/PARAM1', '/data/param/PARAM2', '/bronto/rest//data/param/PARAM3'],
WithDecryption: true }).promise()
const config = {}
for (const i of data.Parameters) {
if (i.Name === '/data/param/PARAM1') {
config.param1 = i.Value
}
if (i.Name === '/data/param/PARAM2') {
config.rest.clientId param2 = i.Value
}
if (i.Name === '/data/param/PARAM3') {
config.param3 = i.Value
}
}
return config
}
This is what I did to retrieve all the parameters from a specific path.
**your SSM function client :**
'use strict';
const SSM = require('aws-sdk/clients/ssm');
let ssmclient;
module.exports.init = () => {
const region = process.env.REGION === undefined ? 'us-east-1' : process.env.REGION ;
ssmclient = new SSM({region: region});
}
module.exports.getParameters = async (path) => {
try {
let params = {
Path: path,
WithDecryption: true
};
let allParameters = [];
let data = await ssmclient.getParametersByPath(params).promise();
allParameters.push.apply(allParameters, data.Parameters);
while(data.NextToken) {
params.NextToken = data.NextToken;
data = await ssmclient.getParametersByPath(params).promise();
allParameters.push.apply(allParameters, data.Parameters);
}
return allParameters;
} catch (err) {
return Promise.reject(err);
}
}
calling this client:
const ssm = require("yourssmclinet");
ssm.init();
// you call only once to retrieve everything which falls under /data/param
const parameters = await getParameters("/data/param");
//from here you can fetch parameters['what ever needed'].
You essentially have two options to get parameters in bulk.
One is the method provided by #user1032613, but the other is to use the built-in function getParametersByPath().
A Lambda code example in node with all three methods can be seen below. Each method can take different params, for instance with the path you can make filters, etc. to get the exact values you need, see the documentation.
'use strict';
const AWS = require('aws-sdk');
const SSM = new AWS.SSM();
exports.handler = async (event) => {
//Example get single item
const singleParam = { Name: 'myParam' };
const getSingleParam = await SSM.getParameter(singleParam).promise();
//Example: Get Multiple values
const multiParams = {
Names: [ 'myParam1', 'myParam2', 'myParam3' ],
WithDecryption: true
};
const getMultiParams = await SSM(multiParams).promise();
//Example: Get all values in a path
const pathParams = { Path: '/myPath/', WithDecryption: true };
const getPathParams = await SSM.getParametersByPath(pathParams).promise();
return 'Success';
};
Remember that you can also use environment variables. For example, you could write singleParam like this:
const singleParam = { Name: process.env.PARAM }
That way you can have code that extracts code from DEV, PROD, etc. depending on the stage.

how to run multiple firebase promises and then once completed, execute function

I need to execute 2 firebase calls to retrieve specific data from the database. Once these promises resolve, I want to call another function with the data retrieved. How can I do this? ..something with Promise.All?
Code below:
app.post('/testtwilio', function(req, res) {
//save request variables
var to_UID = req.body.to;
var from_UID = req.body.from;
var experience_id = req.body.exp_id;
//Query firebase and save 'zone_id' which we need later
firebase.database().ref('experiences').child(experience_id).once('value').then((snap) => {
zone_id = snap.val().ZoneID;
});
//Query firebase and save 'from_name' which we need later
firebase.database().ref('users').child(from_UID).once('value').then((snap) => {
from_name = snap.val().Name;
});
//Once we have the two variables returned and saved
//Call a final firebase query and a twilio function with all the recieved data
firebase.database().ref('users').child(to_UID).once('value').then((snap) => {
//Do something with this aggregated data now
client.messages.create({
//blah blah do something with the saved data that we retrieved
var phone = snap.val().Phone;
var msg = from_name + zone_id + from_UID + experience_id
});
});
});
Yes, you can use Promise.all since once('value') returns one.
Quick n dirty example:
var promises = [];
promises.push(firebase.database().ref('experiences').child(experience_id).once('value'));
promises.push(firebase.database().ref('users').child(from_UID).once('value'));
// Wait for all promises to resolve
Promise.all(promises).then(function(res) {
// res[0] is your experience_id snapshot
// res[1] is your from_UID snapshot
// Do something...
});
If you are using NodeJS of version 7.6 and higher you can also write this code with async function, which much simpler to read and maintain
// ...
const wrap = require('express-async-wrap')
// ...
// need to wrap async function
// to make it compatible with express
app.post('/testtwilio', wrap(async (req, res) => {
const to_UID = req.body.to
const from_UID = req.body.from
const experience_id = req.body.exp_id
const [
snap1,
snap2,
snap3
// waiting for all 3 promises
] = await Promise.all([
firebase.database().ref('experiences').child(experience_id).once('value'),
firebase.database().ref('users').child(from_UID).once('value'),
firebase.database().ref('users').child(to_UID).once('value')
])
const zone_id = snap1.val().ZoneID
const from_name = snap2.val().Name
const phone = snap3.val().Phone
const msg = from_name + zone_id + from_UID + experience_id
// ...
client.messages.create(...)
}))

Resources