How to call internal Suitelet from RESTlet? - netsuite

I am attempting to lock down a Suitelet that returns transactions with a search, but I cannot POST to it from a RESTlet unless it is available without login. It will return a page with a prompt to login if I set the Suitelet to be internal and call it from the RESTlet.
Is there a way to accomplish this without making the Suitelet available without login?
Example Suitelet (removed some irrelevant code to simplify):
define(['N/search', 'N/runtime'],
function(search, runtime) {
function onRequest(context) {
try {
var startDate = context.request.parameters.start_date;
var scriptObj = runtime.getCurrentScript();
if (!startDate) {
context.response.write('The start date is required in order to load the relevant records.');
return;
}
else {
log.debug('Start date parameter is: ' + startDate);
}
var searchCriteria = {
type: "invoice",
filters:
[
["datecreated","after", startDate],
"AND",
["mainline","is","T"]
],
columns:
[
search.createColumn({name: "tranid", label: "Document Number"}),
search.createColumn({name: "total", label: "Total"})
]
};
var searchObj = getAllResults(searchCriteria);
var searchResultCount = searchObj.length;
log.debug("searchObj result count", searchResultCount);
var invoices = [];
for (var i = 0; i < searchResultCount; i++) {
var tranId = searchObj[i].getValue({name: 'tranid'});
var total = searchObj[i].getValue({name: 'total'});
invoices.push({
tranId: tranId,
total: total
});
}
var jsonObj = {
success: 'true',
};
} catch (error) {
var jsonObj = {
success: 'false'
};
log.error('error', error);
return jsonObj;
}
log.debug('JSON obj', jsonObj);
context.response.write(JSON.stringify(jsonObj));
return jsonObj;
}
Example RESTlet:
define(['N/search','N/error','N/https','N/url','N/runtime'],
function(search, error, https, url, runtime) {
function doGet(request) {
log.audit({title:'request', details:request});
var startDate = request.startdate;
var params = {
'start_date': startDate,
'current_role': currentRole
};
var header = [];
header['Content-Type']='application/json';
try {
var suiteletURL = url.resolveScript({
scriptId: 'customscript_get_invoices',
deploymentId: 'customdeploy_get_invoices',
returnExternalUrl: true
});
log.debug('Suitelet URL', suiteletURL);
var formRequest = https.post({
url: suiteletURL,
body: params
});
return formRequest;
}
catch(e) {
var errorStr = 'Error posting to suitelet link';
log.error(errorStr, e.message);
throw error.create({
name: errorStr,
message: e.message,
notifyOff: true
});
}
}
return {
get: doGet
};
});

That will consume an extra concurrency vs moving the shared code into a library script file and calling it from both the Suitelet and the Restlet.
Wraparound web service calls in NetSuite should be avoided for that reason.

Related

Expressjs does not execute sequencially and function return does not work

I am new to node/express js, and trying to execute the following. The control executes the lines after function call "var nextVersion =getNextContractVersion(cid)", even before the function returns a response. As a result the value for newVersion is not updated to Contract object. Also, function getNextContractVersion(cid) returns undefined, unlike the updated nextVersion.
How do i fix this behavior, please suggest. Also, is the right way of invoking function?
// Package imports
const express = require('express');
var router = express.Router();
const mongoose = require('mongoose');
//Local imports
var { Customer } = require('../models/customer');
var { Contract } = require('../models/contract');
router.put('/:cid/contracts', (req, res) => {
var cid = req.params.cid;
var nextVersion =getNextContractVersion(cid);
var contract;
if (validateCustomerId(cid)) {
req.body.contract.forEach((item) => {
contract = new Contract({
customerID: cid,
startDate: item.startDate,
endDate: item.endDate,
conditions: item.conditions,
price: item.price,
author: item.author,
version: nextVersion
});
});
contract.save((err, docs) => {
if (!err) {
Customer.findOneAndUpdate({ customerID: cid }, { $push: { contract: contract } },
{ safe: true, upsert: true, new: true }).populate({ path: 'contract' }).exec((err1, docs1) => {
if (!err1) {
res.send(docs1).status(200);
} else {
console.log('Error is adding a new contract:' + JSON.stringify(err1, undefined, 2));
}
});
} else {
console.log('Error is updating a new customer:' + JSON.stringify(err, undefined, 2));
}
});
} else {
res.status(400).send('Bad Request - Invalid input!')
}
});
function getNextContractVersion(cid) {
var nextVersion=1;
Contract.findOne({ customerID: cid }).sort({version: 'descending'}).exec((err, doc) => {
if (!err && doc != null) {
var currentVersion = parseInt(doc.version);
nextVersion = currentVersion + 1;
}
});
return nextVersion;
}
You are mixing synchronous and asynchronous code.
Contract.findOne({ customerID: cid }).sort({version: 'descending'}).exec((err, doc) => {
if (!err && doc != null) {
var currentVersion = parseInt(doc.version);
nextVersion = currentVersion + 1;
}
});
The above code effectively says "Go to the database, find one of these objects and whenever in the future that is done, run this code that's in the exec block."
One of the ways to reason about asynchronous code from a synchronous mindset is that of promises.
Here's a semi pseudo implementation:
router.put('/:cid/contracts', (req, res) => {
var cid = req.params.cid;
return getTheMostRecentContract(cid)
.then(function(oldContract){
var nextVersion = oldContract.version +1;
if(!validateCustomerId(cid)){
return res.status(400).send('Bad Request - Invalid input!');
}
var contract;
var savePromises = [];
req.body.contract.forEach((item) => {
contract = new Contract({
customerID: cid,
startDate: item.startDate,
endDate: item.endDate,
conditions: item.conditions,
price: item.price,
author: item.author,
version: nextVersion
});
savePromises.push(contract.save());
});
return Promise.all(savePromises);
})
.then(function(resultOfAllSavePromises){
//rest of code here
}).catch(function(error){
console.log('Error is updating a new customer:' + JSON.stringify(err, undefined, 2));
return res.status(400);
})
});
function getTheMostRecentContract(cid) {
return Contract.findOne({ customerID: cid }).sort({version: 'descending'});
}
As a matter of practice though, have the database control your auto-increment values. This code won't work in a high traffic environment.

Synchronous api calls in Node.js

I've a cronjob that runs every 10 secs. It requests for machines for a single client and does computation based on the response and then has to update or create documents with those computations in a for loop. But, the api calls after '***' in the code don't happen until the for loop has executed and the data sent to the api calls is that of the last machine which is wrong. I want to solve this by this way or some other way possible. My code looks like this:
// index.js
const cron = require("node-cron");
const express = require("express");
const fs = require("fs");
const request = require("request");
app = express();
var clientId = 'ABCD';
var apiUrl = 'http://example.com:3001/';
var getMachines = apiUrl + 'getMachines/',
updateMachine = apiUrl + 'updateMachine/',
getControlRoomStatus = apiUrl + 'getControlRoomStatus/',
registerControlRoomStatus = apiUrl + 'registerControlRoomStatus/',
updateControlRoomStatus = apiUrl + 'updateControlRoomStatus/';
cron.schedule("*/10 * * * * *", function() {
APICall(getMachines, { 'clientId' : clientId }, 'POST', function(err, machines) {
if (err) {
console.log(err);
} else {
console.log('--------------------------------------------------------------------------------------------------');
var allMachines = machines;
var currentDateTime = IST();
for (var i = 0; i < 2; i++) {
var lastCycleTime = new Date(allMachines[i]['lastCycleTime']);
var lastHeartbeat = new Date(allMachines[i]['lastHeartbeat']);
var machineData;
var controlRoomData;
var machineId = {
'machineId' : allMachines[i]['machineId']
};
console.log(machineId);
if (allMachines[i]['downtimeStatus'] == '0') {
if ((currentDateTime - lastCycleTime)>300000) {
if ((currentDateTime - lastHeartbeat)>300000) {
console.log(allMachines[i]['machineId'] ,' No Internet');
controlRoomData = {
'clientId': clientId,
'lastTimeStamp': allMachines[i]['lastCycleTime'],
'status': 'Inactive',
'type': 'No Internet/Power'
};
} else {
console.log(allMachines[i]['machineId'] ,' No button pressed');
controlRoomData = {
'clientId': clientId,
'lastTimeStamp': allMachines[i]['lastCycleTime'],
'status': 'Inactive',
'type': 'No Button Pressed'
};
}
machineData = {
'status' : 'Inactive'
};
} else {
console.log(allMachines[i]['machineId'] ,' Active');
machineData = {
'status' : 'Active'
};
controlRoomData = {
'clientId': clientId,
'lastTimeStamp': allMachines[i]['lastCycleTime'],
'status': 'Active',
'type': 'N.A'
};
}
} else {
if ((currentDateTime - lastHeartbeat)>300000) {
console.log(allMachines[i]['machineId'] ,' button pressed ',' No Internet');
controlRoomData = {
'clientId': clientId,
'lastTimeStamp': allMachines[i]['lastCycleTime'],
'status': 'Inactive',
'type': 'No Internet/Power'
};
} else {
var downtimeLength = allMachines[i]['downtimeData'].length - 1;
console.log(allMachines[i]['machineId'] ,' button pressed ',allMachines[i]['downtimeData'][downtimeLength]['downtimeType']);
controlRoomData = {
'clientId': clientId,
'lastTimeStamp': allMachines[i]['lastCycleTime'],
'status': 'Inactive',
'type': allMachines[i]['downtimeData'][downtimeLength]['downtimeType']
};
}
machineData = {
'status' : 'Inactive'
};
}
***
APICall(getControlRoomStatus, machineId, 'POST', function(err, controlRoom) {
if (err) {
console.log(err);
} else {
console.log(machineId,controlRoomData);
if (controlRoom == null ) {
APICall(registerControlRoomStatus, controlRoomData, 'POST', function(err, body) {
if (err) {
console.log(err);
} else {
// console.log(body);
}
});
} else {
var updateControlRooomUrl = (updateControlRoomStatus+''+controlRoom['_id']+'');
// console.log(updateControlRooomUrl);
APICall(updateControlRooomUrl, controlRoomData, 'PUT', function(err, body) {
if (err) {
console.log(err);
} else {
// console.log(body);
}
});
}
}
});
var updateMachineUrl = (updateMachine+''+allMachines[i]['_id']+'');
// console.log(updateMachineUrl);
APICall(updateMachineUrl, machineData, 'PUT', function(err, body) {
if (err) {
console.log(err);
} else {
console.log(i,machineId);
// console.log(body);
}
});
}
}
});
});
function APICall(url, requestData, method, callback) {
request({
url: url,
form: requestData,
method: method
}, function (error, response, body) {
if (error || response.statusCode !== 200) {
return callback(error || {statusCode: response.statusCode});
}
callback(null, JSON.parse(body));
});
}
function IST(){
var dateUTC = new Date();
var dateUTC = dateUTC.getTime();
var dateIST = new Date(dateUTC);
dateIST.setHours(dateIST.getHours() + 5);
dateIST.setMinutes(dateIST.getMinutes() + 30);
return dateIST;
}
app.listen(3128);
Thank you in advance.
I used a different method to do things and now it's working just as it's supposed to. I used 'async' and replaced the for loop with the following:
var async = require('async');
...
async.map(allMachines , function(machine, callback) {
...
});
...
You can try the following package:
sync-request
You can find it here on NPM.
Here is an example how to use it (from the docs):
var request = require('sync-request');
var res = request('GET', 'http://example.com');
console.log(res.getBody());
As stated in the documentation, don't use it in production code, since this will terribly block your server and it will slow down considerably (if you are running a HTTP server which you are using express).
If you have asynchronous code and you want to execute some code after the asynchronous you also can use:
Observables (not native need to use a package, RxJS for example)
Promises (native ES6 JS)

access values after authentication in node js

I've a program that does the below.
Look into a DynamoDB table.
Get the data from the table.
Save the variables in session
After the process, print the values in console.
My code is as below.
intentHandlers['GetMYBusinessInfo'] = function (request, session, response, slots) {
console.log('entered ext bloxk');
if (!session.attributes.userName) {
console.log('eneterd the user entered the block');
var userName = 'jim';
isUserRegistered(userName.toLowerCase(), function (res, err) {
if (err) {
response.fail(err);
console.log(err);
}
else if (!res) {
response.shouldEndSession = true;
}
else {
console.log(res);
var countRes = JSON.stringify(res.Count);
var unpUserRegion = JSON.stringify(res.Items[0].Region);
var unpUserCity = JSON.stringify(res.Items[0].State);
var userRegion = JSON.parse(unpUserRegion);
var userCity = JSON.parse(unpUserCity);
session.attributes.city = userCity;
session.attributes.region = userRegion;
console.log("parsed " + countRes + "\t region is " + userRegion);
session.attributes.userName = true;
}
});
}
console.log(`session values after authentication are user city is ${session.attributes.city}`);
}
The method to check if the value is in DynamoDb or not.
function isUserRegistered(userName, callback) {
var params = {
TableName: "usersTable",
FilterExpression: "#nme = :nme",
ExpressionAttributeNames: {
"#nme": "Name",
},
ExpressionAttributeValues: {
":nme": userName
}
};
var count = 0;
docClient.scan(params, function (err, data) {
if (err) {
console.error("Unable to scan the table. Error JSON:", JSON.stringify(err, null, 2));
callback(false, err);
} else {
console.log("Scan succeeded." + data.Items.length);
if (data.Items.length === 0) {
callback(false);
}
else {
data.Items.forEach(function (itemData) {
console.log("Item :", ++count, JSON.stringify(itemData));
});
callback(data);
}
}
});
}
when I run this, the output that I get is:
session values after authentication are user city is undefined
Scan succeeded.1
Item : 1
{
"ID": "3",
"State": "wisconsin",
"Region": "midwest",
"Name": "jim"
}
{ Items: [ { ID: '3', State: 'wisconsin', Region: 'midwest', Name: 'jim' } ],
Count: 1,
ScannedCount: 1 }
parsed 1 region is midwest
Here I know that Node js being Non-blockable process, the above output is correct, but I want to get the value of city printed in session values after authentication are user city is {HereTheCityComes} instead of session values after authentication are user city is undefined.
I'm sure that placing the console.log(session values after authentication are user city is ${session.attributes.city}); in the last else block(place where the data is returned).
But I need this type of functionality(Get data as shown in my current scenario), as there is some other things to be done after checking if the user is available in database.
please let me know where am I going wrong and how can I fix this.
You can't synchronously expect async result.
What you can do here is solve your problem with promises.
Here is a solution:
intentHandlers['GetMYBusinessInfo'] = function(request, session, response, slots) {
console.log('entered ext bloxk');
var userPromise = Promise.resolve();
if (!session.attributes.userName) {
console.log('eneterd the user entered the block');
var userName = 'jim';
userPromise = new Promise(function (resolve, reject) {
isUserRegistered(userName.toLowerCase(), function (res, err) {
if (err) {
response.fail(err);
reject(err);
}
var countRes = JSON.stringify(res.Count);
var unpUserRegion = JSON.stringify(res.Items[0].Region);
var unpUserCity = JSON.stringify(res.Items[0].State);
var userRegion = JSON.parse(unpUserRegion);
var userCity = JSON.parse(unpUserCity);
session.attributes.city = userCity;
session.attributes.region = userRegion;
console.log("parsed " + countRes + "\t region is " + userRegion);
resolve(res);
});
});
}
userPromise.then(function () {
console.log(`session values after authentication are user city is ${session.attributes.city}`);
});
}
If you are not using ES6, then just install bluebird and use var Promise = require('bluebird')

How can I post a GIF image to twitter using Twit library?

I am trying to build a small bot that posts random GIFs to twitter based on a hard-coded category (for now).
I am using Twit library for making posts to twitter using the Twitter API. How can I post a GIF to twitter?
Here's the code :
var twit = require('twit');
var config = require('./config');
var request = require('request');
var fs = require('fs');
var path = require('path');
const GIPHY_API_KEY = 'API-KEY';
const GIPHY_API_URL = 'http://api.giphy.com/v1/gifs/random? api_key='+GIPHY_API_KEY+'&tag=study';
var T = new twit(config);
getAndPostGifImg();
function getAndPostGifImg() {
request(GIPHY_API_URL,function (error,response,body) {
var resp = JSON.parse(body);
var img_url = resp.data.image_url;
console.log(img_url);
// post the image to twitter
postImg(img_url);
});
function postImg(img_url) {
request(img_url).pipe(fs.createWriteStream('images/imgpost.gif'));
var filename = path.join(__dirname,'/images/','imgpost.gif');
var params = { encoding: 'base64' };
var img = fs.readFileSync(filename,params);
T.post('media/upload', { media_data: img }, onUpload);
function onUpload(err,data,response) {
var id = data.media_id_string; console.log(id);
// post a tweet /hashtag along with image
var tweet = { status: 'random Study Tweet #giphyBotTweets', media_ids: [id] };
T.post('statuses/update',tweet, tweeted);
}
function tweeted(err,data,response){
if(err)
{
var errors = data.errors;
var i = 0;
for(i = 0 ; i < errors.length; i++)
console.log("Error Message(s) : "+errors[i].message);
}
else
{ console.log(data.text); }
}
}
}
Thanks in advance.
T.post(
"media/upload",
{
media_data: dataImage,
command: "APPEND",
media_id: data.media_id_string,
segment_index: 0
},
function(err, data2, response2) {
T.post(
"media/upload",
{ command: "FINALIZE", media_id: data.media_id_string },
function(err3, data3, response3) {
let tweet = {
status: post.caption,
media_ids: [id]
};
T.post("statuses/update", tweet, function(
err,
data4,
response
) {
if (err) console.log("Something Went wrong", err.message);
if (data4.errors) {
console.log(
"ERROR On Success : ",
data4.errors[0].message
);
} else {
let update = {status: 2, TwitpublishId: data.media_id, updatedAt: Date.now() },
options = { new: true };
Post.findByIdAndUpdate(post._id, update, options, (error, result) => {
if (error) return;
console.log('result video========twit',result);
console.log("Posted on Twitter : https://twitter.com");
});
}
});
}
);
}
);
}
);
}
}

Stubbing push notifications with sinon

I would like to test a function that sends a push notification. I would like to stub the notification send. I use mocha and sinon. I tried to stub with several syntax, but it's not working. One stub I tried:
var sender = sinon.createStubInstance(gcm.Sender);
var gcmReturn = {
multicast_id: 1,
success: 1,
failure: 1,
canonical_ids: 1,
results: [{
registration_id: 'xxxx',
message_id: 'yyy'
}, {
error: 'InvalidRegistration'
}]
}
sender.send = function() {
return gcmReturn;
}
The code to test:
var gcm = require('node-gcm');
// Function to test
NotificationService.prototype.sendSpecificNotification = function (data) {
var self = this;
// Process data to create the users, title and text parameters
return self.send(users, title, text)
.then(function(pushResults) {
// expect ...
})
};
NotificationService.prototype.send = function (users, title, text) {
var registrationIds = [];
users.forEach(function (user) {
var push = user.preferences != null ? user.preferences.push : false;
if (push) {
var regId = user.preferences.pushRegId;
registrationIds.push(regId);
}
});
var deferred = Q.defer();
if (registrationIds.length > 0) {
var message = new gcm.Message();
message.addData('message', text);
message.addData('title', title);
message.delayWhileIdle = false;
message.timeToLive = 24 * 60 * 60;
var currentDate = new Date();
var notId = currentDate.getHours() * 60 * 60 + currentDate.getMinutes() * 60 + currentDate.getSeconds();
message.addData('notId', notId);
var sender = new gcm.Sender(config.gcm.senderId);
// I want to stub that call
sender.send(message, registrationIds, function (err, result) {
if (err) {
deferred.reject(new Error(error));
} else {
deferred.resolve(result);
}
});
} else {
deferred.resolve('');
}
return deferred.promise;
}
EDIT: The test
it('Subscription Push', function () {
var sender = sinon.createStubInstance(gcm.Sender);
var gcmReturn = {
multicast_id: 1,
success: 1,
failure: 1,
canonical_ids: 1,
results: [{
registration_id: 'xxxx',
message_id: 'yyy'
}, {
error: 'InvalidRegistration'
}]
}
sender.send.returns(Q.resolve(gcmReturn));
return fixtureService.clearDatabase()
.then(function () {
return fixtureService.load('./test/subscription-push.json');
})
.then(function () {
return notificationService.sendSpecificNotification();
});
});
Have you tried using the sinon stub method? Example:
it('Subscription Push', function () {
var sender = sinon.createStubInstance(gcm.Sender);
var gcmReturn = {
multicast_id: 1,
success: 1,
failure: 1,
canonical_ids: 1,
results: [{
registration_id: 'xxxx',
message_id: 'yyy'
}, {
error: 'InvalidRegistration'
}]
}
sender.send.returns(Q.resolve(gcmReturn));
return fixtureService.clearDatabase()
.then(function () {
return fixtureService.load('./test/subscription-push.json');
})
.then(function () {
return notificationService.sendSpecificNotification();
});
});
UPDATE
You aren't using the stubbed methods at all, there is no magic going on behind the scenes here that stub the method automatically on the notificationService. You need to use the sender you created or just stub the method out directly on the notificationService.
it('Subscription Push', function () {
var sender = sinon.createStubInstance(gcm.Sender);
var gcmReturn = {
multicast_id: 1,
success: 1,
failure: 1,
canonical_ids: 1,
results: [{
registration_id: 'xxxx',
message_id: 'yyy'
}, {
error: 'InvalidRegistration'
}]
}
sender.send.returns(Q.resolve(gcmReturn));
return fixtureService.clearDatabase()
.then(function () {
return fixtureService.load('./test/subscription-push.json');
})
.then(function () {
// Here you should be using sender.sendSpecificNotification();
// Alternatively, you could simply stub out the method
// on the notificationService directoy and avoid using the sender at all.
// To stub out the method on the notificationService do this:
// sinon.stub(notificationService, 'send', function() { return Q.resolve(gcmReturn); });
return notificationService.sendSpecificNotification();
});
});

Resources