Google Apps Script/URLFetchApp and using returned data - api

I am very new to this, so please bear with me-- I have currently have an operational google apps script on the backend of a google sheet that is generated from Google Form answers. I am essentially setting up a ticket form in google forms that will trigger the data in the corresponding sheet to be sent via api call to our ticketing system. It works great, but I am trying to optimize it currently. The goal is to take the json response I get using:
Logger.log(response.getContentText());
which provides me the following info:
Aug 9, 2020, 11:44:40 AM Info {"_url":"https://testticketingsystem.com/REST/2.0/ticket/123456","type":"ticket","id":"123456"}
and send another API call to send data to that new ticket.
Here's a code snippet:
var payload = {
"Subject": String(su),
"Content": String(as),
"Requestor": String(em),
"Queue": String(qu),
"CustomFields": {"CustomField1": String(vn), "CustomField2": String(vb), "CustomField3":
String(vg), "CustomField4": String(av), "CustomField5": String(ov), "CustomField6":
String(sd)}
}
var options = {
'method': 'post',
"contentType" : "application/json",
'payload': JSON.stringify(payload),
'muteHttpExceptions': true
}
var url = "https://testticketingsystem.com/REST/2.0/ticket?token=****************";
var response = UrlFetchApp.fetch(url,options);
Logger.log(response.getContentText());
} catch (error) {
Logger.log(error.toString());
}
}
After the ticket is created, how do I script the use of that ID number as a variable into my next api call?
Thank you!

UrlFetchApp.fetch returns a HTTPResponse, and if you expect JSON then you should be able to just use JSON.parse() to create an object from the text. (The JSON object is a standard JavaScript global object like Math; it is not Google Apps Script specific.)
If all goes well, you should just be able to use
var response = UrlFetchApp.fetch(url,options);
var data = JSON.parse(response.getContentText());
var id = data.id;
and then use that id for your next fetch().
Notes
If your literal response is indeed
Aug 9, 2020, 11:44:40 AM Info {"_url":"https://testticketingsystem.com/REST/2.0/ticket/123456","type":"ticket","id":"123456"}
you will run into trouble as everything until the { is invalid JSON (use a linter if you need to check yourself). But I'm assuming that was added by the console when you logged JSON, and not in the actual response itself.
JSON.parse() throws an error with invalid JSON, so you can use try/catch if needed.
You can also check the headers before you try to JSON.parse().
Here's an example that checks and handles issues, should they arise.
var type = response.getHeaders()["Content-Type"];
var text = response.getContentText();
if (type === "application/json") {
try {
var data = JSON.parse(text);
} catch (error) {
return Logger.log("Invalid JSON: " + response.getContentText(text));
}
} else {
return Logger.log("expected JSON, but got response of type: " + type);
}
// if we get to this line, data is an object we can use

Related

Testing Coinbase API with Postman : pagination gives me error

I am testing the Coinbase API endpoints with Postman and the challenge is when I need to paginate
In order to setup Postman, I have followed the guide available here and in summary:
added variables
coinbase-api-base
coinbase-api-key
coinbase-api-secret
coinbase-api-timestamp
coinbase-api-signature
Added pre-request script in order to generate the request signature
// 1. Import crypto-js library
var CryptoJS = require("crypto-js");
// 2. Create the JSON request object var req = { timestamp: Math.floor(Date.now() / 1000), // seconds since Unix epoch method:
pm.request.method, path: pm.request.url.getPath(), body: '', // empty
for GET requests message: undefined, secret:
pm.collectionVariables.get("coinbase-api-secret"), // read value from
collection variable hmac: undefined, signature: undefined, };
// 3. Create the message to be signed req.message = req.timestamp + req.method + req.path + req.body;
// 4. Create HMAC using message and API secret req.hmac = CryptoJS.HmacSHA256(req.message, req.secret);
// 5. Obtain signature by converting HMAC to hexadecimal String req.signature = req.hmac.toString(CryptoJS.enc.Hex);
// 6. Log the request console.info("request: ", req);
// 7. Set Postman request's authentication headers for Coinbase REST API call pm.collectionVariables.set("coinbase-api-timestamp",
req.timestamp); pm.collectionVariables.set("coinbase-api-signature",
req.signature);
all worked well for a simple request such as:
GET {{coinbase-api-base}}/v2/accounts
then, if I add in the body request parameter (as explained here):
limit=50
to change the default pagination, I get an authentication error....
"errors": [
{ "id": "authentication_error",
"message": "invalid signature"
}
questions:
how can I fix it?
how the body of the request can play with the request signature...
any help suggestion is much appreciated
Thank you
Edit: the below being said, I'm not sure the base accounts API supports paging I could be wrong though, the CB docs are inconsistent to say the least. It does seem that the account history (ledger) and holds do though.
https://docs.cloud.coinbase.com/exchange/reference/exchangerestapi_getaccounts
get accounts function in Node.js API doesn't give an args param where the ledger does (see below):
getAccounts(callback) {
return this.get(['accounts'], callback);
}
Documentation for an api that does support paging, notice it gives you a query param section not available in the accounts documentation:
https://docs.cloud.coinbase.com/exchange/reference/exchangerestapi_getaccountledger
Looking at the node api, you still need to add the query string params to the body in order to sign:
calling function:
return this.get(
['accounts', accountID, 'ledger'],
{ qs: args },
callback
);
signing function:
let body = '';
if (options.body) {
body = JSON.stringify(options.body);
} else if (options.qs && Object.keys(options.qs).length !== 0) {
body = '?' + querystring.stringify(options.qs);
}
const what = timestamp + method.toUpperCase() + path + body;
const key = Buffer.from(auth.secret, 'base64');
const hmac = crypto.createHmac('sha256', key);
const signature = hmac.update(what).digest('base64');
return {
key: auth.key,
signature: signature,
timestamp: timestamp,
passphrase: auth.passphrase,
};
You can't add the limit to the body of the request, GET requests never includes any body.
You should add it as a query string parameter like (this is just an example):
GET {{coinbase-api-base}}/v2/accounts?limit=50

HapiJS reply with readable stream

For one call, I am replying with a huge JSON object which sometimes causes the Node event loop to become blocked. As such, I'm using Big Friendly JSON package to stream JSON instead. My issue is I cannot figure out how to actually reply with the stream
My original code was simply
let searchResults = s3Access.getSavedSearch(guid)).Body;
searchResults = JSON.parse(searchResults.toString());
return reply(searchResults);
Works great but bogs down on huge payloads
I've tried things like, using the Big Friendly JSON package https://gitlab.com/philbooth/bfj
const stream = bfj.streamify(searchResults);
return reply(stream); // according to docs it's a readable stream
But then my browser complained about an empty response. I then tried to add the below to the reply, same result.
.header('content-encoding', 'json')
.header('Content-Length', stream.length);
I also tried return reply(null, stream); but that produced a ton of node errors
Is there some other way I need to organize this? My understanding was I could just reply a readable stream and Hapi would take care of it, but the response keeps showing up as empty.
Did you try to use h.response, here h is reply.
Example:
handler: async (request, h) => {
const { limit, sortBy, order } = request.query;
const queryString = {
where: { status: 1 },
limit,
order: [[sortBy, order]],
};
let userList = {};
try {
userList = await _getList(User, queryString);
} catch (e) {
// throw new Boom(e);
Boom.badRequest(i18n.__('controllers.user.fetchUser'), e);
}
return h.response(userList);
}

How to upload file in jhipster form?

I am just starting to use jhipster 5 and angular 5. I have a form and in that form in addition to few regular fields, I have a file input.
I could not find any documentation on how to file in jhipster.
EDIT 1:
I could somehow managed to upload file and send to server. Below is my server method to handle the form submission.
#PostMapping("/email-jobs")
#Timed
public ResponseEntity<EmailJobDTO> createEmailJob(MultipartFile file, #Valid #RequestBody EmailJobDTO emailJobDTO) throws URISyntaxException {
log.debug("REST request to save EmailJob : {}", emailJobDTO);
if (emailJobDTO.getId() != null) {
throw new BadRequestAlertException("A new emailJob cannot already have an ID", ENTITY_NAME, "idexists");
}
System.out.println(file.getName() + " File Name ");
EmailJobDTO result = emailJobService.save(emailJobDTO);
return ResponseEntity.created(new URI("/api/email-jobs/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
.body(result);
}
Here i get following exception,
Unsupported Media Type: Content type 'multipart/form-data;boundary=----WebKitFormBoundary73sdwuJtdeRk6xsO;charset=UTF-8' not supported
If I remove #RequestBody from method signature then I dont get above exception but then I start getting 400 bad request exception saying my form fields can not be null.
You must define MultipartFile is #RequestParam and declare produces = MediaType.APPLICATION_JSON_VALUE in post mapping, like:
#PostMapping("/email-jobs", produces = MediaType.APPLICATION_JSON_VALUE)
Client side, you can try send request as this:
Upload.upload({
url: 'api/path',
data: {
file: yourdatafile
},
headers: {'Content-Type': 'multipart/form-data'}
}).progress(function (evt) {
// handle progress
}).success(function (data, status, headers, config) {
// handle success
});
Instead of uploading a file, create a field type as a BLOB and then in your business logic, make a file if u need or else do your.

Meteor.http.get issue with Twitter API

I am using Meteor and the Twitter API for a project. I want to get information on a user from Twitter. I wrote a function that for example returns only the location of a user from Twitter. I believe this is the proper way to do a request on Meteor. Here it is :
Meteor.methods({getTwitterLocation: function (username) {
Meteor.http.get("https://api.twitter.com/1/users/show.json?screen_name="+ username +"&include_entities=true", function(error, result) {
if (result.statusCode === 200) {
var respJson = JSON.parse(result.content);
console.log(respJson.location);
console.log("location works");
return (respJson.location)
}else {
return ( "Unknown user ")
}
});
}});
Now this function will log what's in the console on my Git Bash. I get someones Location by doing a Meteor.call. But I want to post what that function returns on a page. In my case, I want to post in on a user's profile. This doesn't work. But the console.log(respJson.location) returns the location in my Git Bash but it won't display anything on the profile page. This is what I did on my profile page:
profile.js :
Template.profile.getLocation= function(){
return Meteor.call("getTwitterLocation","BillGates");
}
profile.html :
<template name="profile">
from {{getLocation}}
</template>
With that I get "Seattle, WA" and " "location works" on my Git Bash but nothing on the profile page. If anyone knows what I can do, that'd be really appreciated. Thanks.
Firstly when data is returned from the server you need to use a synchronous call, as the callback will return the data when the server already thinks the meteor method has completed. (the callback will be fired at a later time, when the data is returned from the server, by which time the meteor client would have already got a response)
var result = Meteor.http.get("https://api.twitter.com/1/users/show.json?screen_name="+ username +"&include_entities=true");
if (result.statusCode === 200) {
var respJson = JSON.parse(result.content);
console.log(respJson.location);
console.log("location works");
return (respJson.location)
}else {
return ( "Unknown user ")
}
The second is you need to use a Session hash to return the data from the template. This is because it will take time to get the response and the getLocation would expect an instant result (without a callback). At the moment client side javascript can't use synchronous api calls like on the server.
Template.profile.getLocation= function(){
return Session.get("twitterlocation");
}
Use the template created event to fire the meteor call:
Template.profile.created = function() {
Meteor.call("getTwitterLocation","BillGates", function(err,result) {
if(result && !err) {
Session.set("twitterlocation", result);
}
else
{
Session.set("twitterlocation", "Error");
}
});
});
Update:
Twitter has since updated its API to 1.1 a few modifications are required:
You now need to swap over to the 1.1 api by using 1.1 instead of 1. In addition you need to OAuth your requests. See https://dev.twitter.com/docs/auth/authorizing-request. Below contains sample data but you need to get proper keys
var authkey = "OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog",
oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",
oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp=""+(new Date().getTime()/1000).toFixed(0)+"",
oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
oauth_version="1.0"";
Be sure to remove the newlines, I've wrapped it to make it easy to read.
var result = Meteor.http.get("https://api.twitter.com/1.1/users/show.json?screen_name="+ username +"&include_entities=true",{headers:{Authorization : authkey});
If you find this a bit troublesome it might be easier to just use a package like https://github.com/Sewdn/meteor-twitter-api via meteorite to OAuth your requests for you.

Display result from server in IBM Worklight

I have implemented HTTP adapter in IBM Worklight. I want to display the result returned from server. I want to display HTML file. My code is
function getFeeds() {
var input = {
method : 'get',
returnedContentType : 'text',
path : "marketing/partners.html"
};
WL.Logger.debug("sdfsds");
return WL.Server.invokeHttp(input);
}
I want to receive(display) WL.Server.invokeHttp(input). After receiving it I want to parse the data.
Take a look at the Server-side Development Getting Started Modules. Inside the HTTP adapter – Communicating with HTTP back-end systems Module on Slide 15 - 'XSL Transformation Filtering' will show you how to filter data you get back from the backend. Further parsing and showing data has to be done on the client using onSuccess callback for WL.Client.invokeProcedure. There's a module for that too.
Here's an example of getting data and showing to a user:
var invocationData = {
adapter : 'adapter-name',
procedure : 'procedure-name',
parameters : []
};
var options = {};
options.onSuccess = function (response) {
//response is a JavaScript object
$("#id").html(response.invocationResponse.text);
}
options.onFailure = function (response) {
alert('Failed!'); //You probably want something more meaningful here.
}
WL.Client invokeProcedure(invocationData, options);
There are JavaScript libraries you can add to make searching for values inside the JSON response easier, such as: jspath and jquery-jspath. There's also XPath if you're working with XML.
If you retrieve it as plain text, once you got it back to your application, do something like
$("#container-id").html(response.invocationResponse.text);
This will inject the HTML you've retrieved to an element with id container-id.