Fetch in IF statement produces output error - node.js

I'm working with Asana and Zapier, and am working to GET a Section within a project, and optionally create one if it doesn't exist. This action doesn't exist natively in Zapier.
I used Zapier Code to do the GET, and that code successfully returns the sectionId as either 0, or the actual id from Asana.
In the next action, I'm doing Zapier Code again, and I'm trying to do the POST to create the Section, but only if the sectionId returned by the GET action is 0.
I think the issue I'm having has to do with a lack of understanding of asynchronous programming. But, I was more or less copied the code from the successful GET action into the new action, and changed the HTTP request a bit to be a POST.
If I remove the if statement, then the POST works as expected.
Where I'm getting stuck is wrapping the fetch in an if statement. I've tried to simply wrap it in an if statement (if 0, do the fetch, else, return the ID that's not 0), and create it as a function that I put within the if statement. I've tried a few different configurations of each method, but either way, the error I'm receiving is:
Bargle. We hit an error creating a run javascript. :-( Error:
ReferenceError: output is not defined
Here's my code:
var sId = input.sId;
function doFetch(aPId) {
var settings = {
'method': 'POST',
'headers': {
'Authorization': 'Bearer <ACCESS TOKEN>',
'Content-Type': 'application/x-www-form-urlencoded'
},
'body': "name=Emails%3A"
};
var url = 'https://app.asana.com/api/1.0/projects/' + aPId + '/sections';
fetch(url,settings)
.then(function(res) {
return res.json();
})
.then(function(json) {
callback(null, json);
})
.catch(callback);
}
if(sId === 0){
return doFetch(input.asanaProjectId);
}
output = [{test: 123}];
Any thoughts?

Ok, I fixed it. I can't explain it fully, but this worked. I believe the if statement itself was the issue.
var sId = inputData.sId;
function createSection(aPId) {
var settings = {
'method': 'POST',
'headers': {
'Authorization': 'Bearer <ACCESS TOKEN>',
'Content-Type': 'application/x-www-form-urlencoded'
},
'body': "name=Emails%3A"
};
var url = 'https://app.asana.com/api/1.0/projects/' + aPId + '/sections';
console.log('Settings:',settings);
console.log('URL:',url);
fetch(url,settings)
.then(function(res) {
var response = res;
//console.log(response);
return response.json();
})
.then(function(json) {
//console.log(json);
var sId = json['data']['id'];
callback(null,{sectionId: sId});
})
.catch(callback);
}
if(sId == 0){
var sId = createSection(input.asanaProjectId);
console.log('If test ran!');
} else {
output = [{sectionId: sId}];
}

Related

Is there anything to do with order of writing scope for client credential flow in Node.js?

I am trying to call a get API that gives patient details from Cerner EHR. I am using the system based SMART app. But when I am trying to call it, I get an error
invalid scope.
'scope':' system/Observation.write system/Patient.read system/Patient.write system/Observation.read'
I tried this in postman and CURL both works perfectly fine.
But when I am giving the scope as - 'scope':'system/Patient.write system/Patient.read system/Observation.write system/Observation.read' i.e. giving system/Patient.write or any other scope as the first scope I can`t get the details of patients, I have to give system/Patient.read as the first scope itself then only I can read the patient details. I don't understand why the order of scope matters here.
The code for getting access token is given below:
const getAuth = async () => {
try{
const token_url = 'token-url endpoint';
const data = qs.stringify({'grant_type':'client_credentials','scope':'system/Patient.write system/Patient.read system/Observation.write system/Observation.read '});
console.log(data)
const opts = {
headers: {
'Authorization': `Basic ${auth_token}`,
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json',
'Content-Length': '61',
'Connection':'close',
'cache-control': 'no-cache'
}
};
const response = await axios.post(token_url, data, opts);
return response.data.access_token;
}catch(error){
console.log( error);
}
}
After this, I am calling a function to get the patient details , the function code is given below:
async function getFunction(){
const bearer_token=await getAuth();
const get_url = 'get endpoint';
const get_header = {
headers: {
'Authorization': `Bearer ${bearer_token}`,
'Accept': 'application/json+fhir'
}
}
console.log(bearer_token);
const get_respone = await axios.get(get_url, get_header);
let data = get_respone.data;
console.log(data);
return data;
}
but shows the error as :
data: {
resourceType: 'OperationOutcome',
issue: [
{
severity: 'error',
code: 'forbidden',
diagnostics: 'Bearer realm="fhir-ehr-code.cerner.com", error="insufficient_scope"',
expression: [ 'http.Authorization' ]
}
]
}
But if I am taking the access token by changing the scope as :
'scope':'system/Patient.read system/Patient.write system/Observation.write system/Observation.read'
Then I will get the patient details since I have added the system/Patient.read scope at the beginning of the scope.
I am using Node.js as the backend for this SMART App and I am a beginner in Node.js, so I can't figure out why the order of scope matters in calling an API. Does anyone know this?

Autodesk Forge - Reality Capture Issue - Specified Photoscene ID doesn't exist in the database

i'm trying to upload my files as form-data, after i've created a scene. But I receive always the error "Specified Photoscene ID doesn't exist in the database" (which were created directly before).
My upload function:
// Upload Files
async function uploadFiles(access_Token, photoSceneId, files) {
try {
const params = new URLSearchParams({
'photosceneid': photoSceneId,
'type': 'image',
'file': files
})
const headers = Object.assign({
Authorization: 'Bearer ' + access_Token,
'Content-Type': 'multipart/form-data' },
files.getHeaders()
)
let resp = await axios({
method: 'POST',
url: 'https://developer.api.autodesk.com/photo-to-3d/v1/file',
headers: headers,
data: params
})
let data = resp.data;
return data;
} catch (e) {
console.log(e);
}
};
I've also tried a few varaints, e.g. adding the photosceneId to the form data (form.append(..), but doesn't works either.
Any helpful suggestion are appreciated. Thx in advance.
There might be two problems here.
First, I am not sure of it, as I don't have experience of URLSearchParams as a "packer" for POST requests. This might be the reason why you get "Specified Photoscene ID doesn't exist in the database" error - perhaps the way the data are serialized using URLSearchParams is not compatible.
The second problem, I am sure of it, is regarding the way you submit the files.
According to documentation, you have to pass the files one by one, like
"file[0]=http://www.autodesk.com/_MG_9026.jpg" \
"file[1]=http://www.autodesk.com/_MG_9027.jpg"
and not just passing an array to the "file" field.
Having this said, try this approach:
var axios = require('axios');
var FormData = require('form-data');
var fs = require('fs');
var data = new FormData();
var TOKEN = 'some TOKEN';
const photoSceneID = 'some_photoscene_id';
data.append('photosceneid', photoSceneID);
data.append('type', 'image');
data.append('file[0]', fs.createReadStream('/C:/TEMP/Example/DSC_5427.JPG'));
data.append('file[1]', fs.createReadStream('/C:/TEMP/Example/DSC_5428.JPG'));
data.append('file[2]', fs.createReadStream('... and so on ...'));
var config = {
method: 'post',
url: 'https://developer.api.autodesk.com/photo-to-3d/v1/file',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer ' + TOKEN,
},
data : data
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
Also, I always recommend instead of jumping right into the code, to check first the workflow using apps like Postman or Insomnia and then, after you validated the workflow (created the photoscene, all images were properly uploaded and so on), you can translate this into the code.
At the end of this blogpost you will find the link to alrady created Postman collection, but I highly recommend building your own collection, as part of the learning step.
This is the solution that worked for me. Please note that the upload should be limited by a maximum of 20 files per call.
// Upload Files
async function uploadFiles(access_Token, photoSceneId) {
try {
let dataPath = path.join(__dirname, '../../data')
let files = fs.readdirSync(dataPath)
var data = new FormData();
data.append('photosceneid', photoSceneId)
data.append('type', 'image')
for(let i=0; i < files.length; i++) {
let filePath = path.join(dataPath, files[i])
let fileName = 'file[' + i + ']'
data.append(fileName, fs.createReadStream(filePath))
}
const headers = Object.assign({
Authorization: 'Bearer ' + access_Token,
'Content-Type': 'multipart/form-data;boundary=' + data.getBoundary()},
data.getHeaders()
)
let resp = await axios({
method: 'POST',
url: 'https://developer.api.autodesk.com/photo-to-3d/v1/file',
headers: headers,
maxContentLength: Infinity,
maxBodyLength: Infinity,
data: data
})
let dataResp = resp.data;
return dataResp;
} catch (e) {
console.log(e);
}
};

Multiple api calls in loop fails

Having some issues when calling an external api to fetch information inside a loop in my node/express backend. I need information I get from the loop to get the correct data back from the endpoint. When I loop through the data and make the call I get this error.
{ message: 'Too Many Requests Limit 30. Reset time 1594218437315',
status: 429 }
I get the correct data back sometimes and sometimes only this message. There will be about 10 000 or so calls I need to make. I've tried a multiple of throttling libs and a lot of the code here on SO but it's pretty much always the same result which is the error message or it doesnt work at all.
I think I need a way send about 20-30 requests at a time and then wait a second or so and then continue. Or is there another better way? How would I achieve this?
const product = await NewProduct.find({});
product.map((item, index) => {
item.stores.map(async inner => {
inner.trackingUrl = await fetchRetry(inner.programId, inner.productUrl)
})
})
async function fetchRetry(id, urlToTrack) {
url1 = 'https://api.adtraction.com/v2/affiliate/tracking/link/?token=token';
const data = {
channelId,
programId: id,
shortLink: true,
url: urlToTrack
};
const options = {
method: 'POST',
headers: {
'Content-type': 'application/json',
Accept: 'application/json',
'Accept-Charset': 'utf-8',
},
body: JSON.stringify(data),
};
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-type': 'application/json',
Accept: 'application/json',
'Accept-Charset': 'utf-8',
},
body: JSON.stringify(data),
});
const json = await res.json();
console.log(json) // Error
return json.trackingUrl;
}
Well, I'll share with you something that I've done in one of my projects. For all of your data that you call with your api. Keep making requests and when you encounter any error or response with throttling then wait for 5 minutes (or required in your api) and move to previous index (previous data to call the api with)
async function callAPI(data) {
for (let index = 0; index < data.length; index++) {
try {
// api call with your data
yourApiCall(data[index]);
} catch (e) {
if (e.type == "RequestThrottled") {
let oneMinute = 60000;
// wait for the time your request will be availabl
await sleep(5 * oneMinute);
// because of error in this request get back to
//previous request (or data)
index--;
}
}
}
}
Function using setTimeout to wait synchronously.
async function sleep(ms) {
await new Promise((resolve) => setTimeout(resolve, ms));
}
Also, you may want to run it on some background process as it might block your main event loop.

How to get multiple bodyResponses to ejs

I'm using a forEach loop to make a request to two different paths, but, although it does console.log() both bodyResponses, it gives error when trying to save it to render "index.ejs" with its value. (index.ejs is the template I wanna render):
manyPaths.forEach(function (element){
var signature = crypto.createHmac('sha256', apiSecret).update(verb + element.path + expires).digest('hex');
var headers = {
'content-type' : 'application/json',
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'api-expires': expires,
'api-key': apiKey,
'api-signature': signature
};
const requestOptions = {
headers: headers,
url:'https://testnet.bitmex.com'+element.path,
method: verb
};
request(requestOptions, function(error, response, bodyResponse) {
if (error) {
console.log(error);
} else {
console.log(bodyResponse);
bodyResponse=JSON.parse(bodyResponse);
res.render("index", {bodyResponse:bodyResponse});
}
});
});
it does console.log both responses, but I get this error for render():
Error: Can't set headers after they are sent.
You can respond to a HTTP request only ONCE. So in your FOR loop, the first response is sent,and the connection is closed, and when it comes to the second iteration it fails with the error you see.
You have to wait for both calls to complete in parallel, and then kick of a function to send the response .

Podio API addItem call

I'm trying to implement https://developers.podio.com/doc/items/add-new-item-22362 Podio API addItem call in a nodejs module. Here is the code:
var _makeRequest = function(type, url, params, cb) {
var headers = {};
if(_isAuthenticated) {
headers.Authorization = 'OAuth2 ' + _access_token ;
}
console.log(url,params);
_request({method: type, url: url, json: true, form: params, headers: headers},function (error, response, body) {
if(!error && response.statusCode == 200) {
cb.call(this,body);
} else {
console.log('Error occured while launching a request to Podio: ' + error + '; body: ' + JSON.stringify (body));
}
});
}
exports.addItem = function(app_id, field_values, cb) {
_makeRequest('POST', _baseUrl + "/item/app/" + app_id + '/',{fields: {'title': 'fgdsfgdsf'}},function(response) {
cb.call(this,response);
});
It returns the following error:
{"error_propagate":false,"error_parameters":{},"error_detail":null,"error_description":"No matching operation could be found. No body was given.","error":"not_found"}
Only "title" attribute is required in the app - I checked that in Podio GUI. I also tried to remove trailing slash from the url where I post to, then a similar error occurs, but with the URL not found message in the error description.
I'm going to setup a proxy to catch a raw request, but maybe someone just sees the error in the code?
Any help is appreciated.
Nevermind on this, I found a solution. The thing is that addItem call was my first "real"-API method implementation with JSON parameters in the body. The former calls were authentication and getApp which is GET and doesn't have any parameters.
The problem is that Podio supports POST key-value pairs for authentication, but doesn't support this for all the calls, and I was trying to utilize single _makeRequest() method for all the calls, both auth and real-API ones.
Looks like I need to implement one for auth and one for all API calls.
Anyway, if someone needs a working proof of concept for addItem call on node, here it is (assuming you've got an auth token beforehand)
_request({method: 'POST', url: "https://api.podio.com/item/app/" + app_id + '/', headers: headers, body: JSON.stringify({fields: {'title': 'gdfgdsfgds'}})},function(error, response, body) {
console.log(body);
});
You should set content-type to application/json
send the body as stringfied json.
const getHeaders = async () => {
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json; charset=utf-8',
};
const token = "YOUR APP TOKEN HERE";
headers.Authorization = `Bearer ${token}`;
return headers;
}
const createItem = async (data) => {
const uri = `https://api.podio.com/item/app/${APP_ID}/`;
const payload = {
fields: {
[data.FIELD_ID]: [data.FIELD_VALUE],
},
};
const response = await fetch(uri, {
method: 'POST',
headers: await getHeaders(),
body: JSON.stringify(payload),
});
const newItem = await response.json();
return newItem;
}

Resources