Fetch multiple documents in a stored procedure (Azure DocumentDB) - node.js

I have two document types, Listing and Products. A Listing object contains a list of Products for certain countries, like this:
Listing:
{
"Name": "Default",
"Countries": {
"_default": [
"4QlxAPFcCAAPAAAAAAAAAA==",
"4QlxAPFcCAAHAAAAAAAAAA=="
],
"US": [
"4QlxAPFcCAAIAAAAAAAAAA==",
"4QlxAPFcCAAHAAAAAAAAAA=="
]
},
"Type": "Listing",
"id": "dfed1839-07c5-482b-81c5-669b1dbcd0b6",
"_rid": "4QlxAPFcCAAEAAAAAAAAAA=="
}
Product:
{
"Name": "Widget",
"Price": 3.45,
"Type": "Product",
"_rid": "4QlxAPFcCAAHAAAAAAAAAA=="
}
My goal was to create a stored procedure in the Azure DocumentDB collection taking two parameters, ridand country, which would essentially fetch the Listing document, and the documents for that country, in the most efficient manner possible. My presumption is that loading a Document by its resource Id using getContext().getCollection().readDocument(...) would be the fastest way, thus attempting to create a stored procedure for this.
My attempts have been to nest the consecutive calls (callback hell?), using generator/iterators with yield and then with a pure Promise approach. All of the attempts have given the same result:
It will fetch the first document, but will end quite abruptly after the document has been received.
For reference, here's my latest attempt:
function test(rid, country) {
var collection = getContext().getCollection();
var collectionSelfLink = collection.getSelfLink();
var docsLink = collectionSelfLink + "docs/";
var body = getContext().getResponse().setBody;
function getDocument(rid) {
return new Promise(function(resolve, reject) {
var accepted = collection.readDocument(docsLink + rid, (err, doc, opts) => {
resolve(doc);
});
if (!accepted)
reject("Not accepted");
});
}
getDocument(rid)
.then(doc => {
body("0. First step"); // set test body
// Countries is a Dictionary<string, string[]> with resource ids
return doc.Countries[country] || doc.Countries["_default"];
})
// This is how far it gets, resulting in response "1. Documents to fetch: 2"
.then(a => body("1. Documents to fetch: " + a.length))
.then(a => a.map(function(productId) { return getDoument(productId); }))
.then(a => body("2. It should come this far, right?"))
.then(a => Promise.all(a))
.then(a => body(a))
.catch(function(e) { throw new Error(JSON.stringify(e)); });
}

It turns out that nesting the calls do in fact work, if you alter the response body frequently(?)
The following procedure worked as expected:
function test(rid, country) {
var collection = getContext().getCollection();
var collectionSelfLink = collection.getSelfLink();
var docsLink = collectionSelfLink + "docs/";
var body = getContext().getResponse().setBody;
var accepted = collection.readDocument(docsLink + rid, (err, doc, opts) => {
if (err) throw new Error(err.message);
// Countries is a Dictionary<string, string[]> with resource ids
var offerIds = doc.Countries[country] || doc.Countries["_default"];
var result = [];
for (var docId of offerIds) {
var subAccepted =
collection.readDocument(docsLink + docId, (err, doc, opts) => {
if (err) throw new Error(err.message);
result.push(doc);
});
if (!subAccepted)
throw new Error("A subsequent request was not accepted");
body(result); // <-- Note, setting body in each iteration.
}
});
if (!accepted)
throw new Error("The request was not accepted");
}

Related

POST request with formidable returning 500 - Node.js

I need to save data and file as a new project to my Mongo. For this I am using formidable.
My POST method looks like this:
exports.create = async (req, res) => {
let form = new formidable.IncomingForm();
form.keepExtensions = true;
form.parse(req, (err, fields, files) => {
if (err) {
return res
.status(400)
.json({ errors: [{ msg: 'Image could not be uploaded' }] });
}
const {
title,
description,
photo,
tags,
git,
demo,
projectType,
} = fields;
//get links object
const projectFields = {};
projectFields.creator = req.user._id;
if (title) projectFields.title = title;
if (title) projectFields.description = description;
if (photo) projectFields.photo = photo;
if (projectType) projectFields.projectType = projectType;
if (tags) {
projectFields.tags = tags.split(',').map((tag) => tag.trim());
}
//get links object
projectFields.links = {};
if (git) projectFields.links.git = git;
if (demo) projectFields.links.demo = demo;
//1kb = 1000
//1mb = 1000000kb
//name 'photo' mus match client side. use photo
if (files.photo) {
if (files.photo.size > 1000000) {
return res.status(400).json({
errors: [{ msg: 'Image could not be uploaded. File to big.' }],
});
}
//this relates to data in schema product
project.photo.data = fs.readFileSync(files.photo.path);
project.photo.contentType = files.photo.type;
}
});
I want to use async/await so I am using try{}catch(err){} for my project.save(). I am initializing all my fields where I have also nested links. Unfortunately this is not working as I thought it will work. Right now my POST is returning 500. I am sitting on this and right now I am at the point that this can get a bit messy and not even close to any solution.

How to push notifications in firebase node.js cloud functions?

I want to create a function with node.js but I've got stuck at a point.
Explanation of what I want to do:
First, the function will trigger when a new document added to the path profile/{profileID}/posts/{newDocument}
the function will send a notification to all the following users. the problem comes here.
I've another collection in the profile collection which is followers and contains documents of the field followerID.
I want to take this followerID and use it as a document id to access the tokenID field with I've added to the profile document.
like this:
..(profile/followerID).get(); and then access the field value of tokenID field.
My current Code:- Index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.fcmTester = functions.firestore.document('profile/{profileID}/posts/{postID}').onCreate((snapshot, context) => {
const notificationMessageData = snapshot.data();
var x = firestore.doc('profile/{profileID}/followers/');
var follower;
x.get().then(snapshot => {
follower = snapshot.followerID;
});
return admin.firestore().collection('profile').get()
.then(snapshot => {
var tokens = [];
if (snapshot.empty) {
console.log('No Devices');
throw new Error('No Devices');
} else {
for (var token of snapshot.docs) {
tokens.push(token.data().tokenID);
}
var payload = {
"notification": {
"title": notificationMessageData.title,
"body": notificationMessageData.title,
"sound": "default"
},
"data": {
"sendername": notificationMessageData.title,
"message": notificationMessageData.title
}
}
return admin.messaging().sendToDevice(tokens, payload)
}
})
.catch((err) => {
console.log(err);
return null;
})
});
my firestore database explanation.
profile | profileDocuments | posts & followers | followers collection documents & posts collection documents
I have a parent collection called profile and it contains documents as any collection these documents contain a field called tokenID and that I want to access, but I will not do this for all users only for followers (the users who follwed that profile) so I've created a new collection called followers and it contains all the followers IDs, I want to take every followerID and for each id push tokenID to tokens list.
If I understand correctly your question, you should do as follows. See the explanations below.
exports.fcmTester = functions.firestore.document('profile/{profileID}/posts/{postID}').onCreate((snapshot, context) => {
const notificationMessageData = snapshot.data();
const profileID = context.params.profileID;
// var x = firestore.doc('profile/{profileID}/followers/'); //This does not point to a document since your path is composed of 3 elements
var followersCollecRef = admin.firestore().collection('profile/' + profileID + '/followers/');
//You could also use Template literals `profile/${profileID}/followers/`
return followersCollecRef.get()
.then(querySnapshot => {
var tokens = [];
querySnapshot.forEach(doc => {
// doc.data() is never undefined for query doc snapshots
tokens.push(doc.data().tokenID);
});
var payload = {
"notification": {
"title": notificationMessageData.title,
"body": notificationMessageData.title,
"sound": "default"
},
"data": {
"sendername": notificationMessageData.title,
"message": notificationMessageData.title
}
}
return admin.messaging().sendToDevice(tokens, payload)
});
First by doing var x = firestore.doc('profile/{profileID}/followers/'); you don't declare a DocumentReference because your path is composed of 3 elements (i.e. Collection/Doc/Collection). Note also that,in a Cloud Function, you need to use the Admin SDK in order to read other Firestore documents/collections: So you need to do admin.firestore() (var x = firestore.doc(...) will not work).
Secondly, you cannot get the value of profileID just by doing {profileID}: you need to use the context object, as follows const profileID = context.params.profileID;.
So, applying the above, we declare a CollectionReference followersCollecRef and we call the get() method. Then we loop over all the docs of this Collection with querySnapshot.forEach() to populate the tokens array.
The remaining part is easy and in line with your code.
Finally, note that since v1.0 you should initialize your Cloud Functions simple with admin.initializeApp();, see https://firebase.google.com/docs/functions/beta-v1-diff#new_initialization_syntax_for_firebase-admin
Update following your comments
The following Cloud Function code will lookup the Profile document of each follower and use the value of the tokenID field from this document.
(Note that you could also store the tokenID directly in the Follower document. You would duplicate data but this is quite common in the NoSQL world.)
exports.fcmTester = functions.firestore.document('profile/{profileID}/posts/{postID}').onCreate((snapshot, context) => {
const notificationMessageData = snapshot.data();
const profileID = context.params.profileID;
// var x = firestore.doc('profile/{profileID}/followers/'); //This does not point to a document but to a collectrion since your path is composed of 3 elements
const followersCollecRef = admin.firestore().collection('profile/' + profileID + '/followers/');
//You could also use Template literals `profile/${profileID}/followers/`
return followersCollecRef.get()
.then(querySnapshot => {
//For each Follower document we need to query it's corresponding Profile document. We will use Promise.all()
const promises = [];
querySnapshot.forEach(doc => {
const followerDocID = doc.id;
promises.push(admin.firestore().doc(`profile/${followerDocID}`).get()); //We use the id property of the DocumentSnapshot to build a DocumentReference and we call get() on it.
});
return Promise.all(promises);
})
.then(results => {
//results is an array of DocumentSnapshots
//We will iterate over this array to get the values of tokenID
const tokens = [];
results.forEach(doc => {
if (doc.exists) {
tokens.push(doc.data().tokenID);
} else {
//It's up to you to decide what you want to to do in case a Follower doc doesn't have a corresponding Profile doc
//Ignore it or throw an error
}
});
const payload = {
"notification": {
"title": notificationMessageData.title,
"body": notificationMessageData.title,
"sound": "default"
},
"data": {
"sendername": notificationMessageData.title,
"message": notificationMessageData.title
}
}
return admin.messaging().sendToDevice(tokens, payload)
})
.catch((err) => {
console.log(err);
return null;
});
});

Add data to Cloud Firestore in Loop

I have a nested json which have a key and value is another json with key and value. Below is the json.
{
"Testicular Torsion": {
"What is testicular torsion?": "ABC",
"Symptoms": "AB",
"Risks": "AL",
"Diagnosis": "LK",
"Treatment": "UY"
},
"XYZ": {
"X": "ABC",
"Symptoms": "AB",
"Risks": "AL",
"Diagnosis": "LK",
"Treatment": "UY"
}
};
What I am trying to do is insert the data in cloud firestore of firebase. Following is the code for the same. But the issue is, only the first key value pair(In this case the Testicular Torsion key and it's value which is another JSON) is getting inserted and not other key value pairs. Why is the case and what needs to be done in the code?
var string_medical_data = JSON.stringify(medical_json);
var json_medical = JSON.parse(string_medical_data);
function abc(poi){
firestore.collection('medical').doc(poi).set(json_medical[poi])
.then(() => {
return console.log("Added");
})
.catch((e => {
console.log('error: ', e);
return console.log(e);
}))
}
exports.medical = functions.https.onRequest((request, response) => {
var problems = [];
for(var myKey in json_medical) {
problems.push(myKey);
break;
}
for(var i=0;i<problems.length;i++){
// firestore.collection('medical').doc(problems[i]).set(json_medical[problems[i]])
abc(problems[i]);
}
response.send({
'fulfillmentText': `Success!!!`
});
});
You'd probably be better off doing this in a batch request. You can commit multiple writes in a single request. If your data has more than 500 entries you'll have to do break it up and do 500 at a time.
exports.medical = functions.https.onRequest((request, response) => {
var batch = firestore.batch();
for(var myKey in json_medical) {
var myKeyRef = firestore.collection('medical').doc(myKey);
batch.set(myKeyRef, json_medical[myKey]);
}
batch.commit().then(function () {
response.send({
'fulfillmentText': `Success!!!`
});
});
});

Trouble with asynchronous requests pulling data from SQL database in Microsoft Bot Framework

I have a bot in the Microsoft bot Framework that I want to be able to pull data from an azure SQL database in order to answer questions asked to the bot. I have set up the database and it has some excel files in it.
Here is my code right now:
var Connection = require('tedious').Connection;
var Request = require('tedious').Request;
var connection = new Connection(dataconfig);
connection.on('connect', function(err) {
console.log("Connected");
executeStatement();
});
var Request = require('tedious').Request;
var TYPES = require('tedious').TYPES;
function executeStatement() {
request = new Request("select \"Product Name\" from SPA_Data_Feeds where \"Strategic Priority\" = 'Accelerate to Value (LD)'",
function(err, rowCount, rows)
{
console.log(rowCount + ' row(s) returned');
}
);
var result = "";
var count = 0
request.on('row', function(columns) {
columns.forEach(function(column) {
console.log("%s\t", column.value);
result+= column.value + "\t\n";
count++;
if ( count == rowCount ) {
ATVData(result);
} ;
});
});
connection.execSql(request);
}
function ATVData(result) { //Puts "result" inside of an adaptive card }
I cant seem to figure out how to get the if statement right. rowCount does not work because it does not wait for it to be defined by the function before first, and I have tried using things like column(s).length, result(s).length but none work.
Is there something else I could use that would complete the if statement? Or do I need to reformat somehow with callbacks/promises to get it to wait for rowCount to be defined? If so could I get some advice on that?
Is there something else I could use that would complete the if statement? Or do I need to reformat somehow with callbacks/promises to get it to wait for rowCount to be defined? If so could I get some advice on that?
We can use Q.js which is one of the JavaScript Promise implementation to solve this issue. For example:
var Connection = require('tedious').Connection;
var Request = require('tedious').Request;
var q = require('q');
// Create connection to database
var config =
{
userName: '', // update me
password: '', // update me
server: '', // update me
options:
{
database: '' //update me
, encrypt: true
}
}
var connection = new Connection(config);
// Attempt to connect and execute queries if connection goes through
connection.on('connect', function(err)
{
if (err)
{
console.log(err)
}
else
{
queryDatabase().then(function(result){
ATVData(result);
}, function(err){
console.log(err);
});
}
}
);
function queryDatabase()
{
console.log('Reading rows from the Table...');
//create a promise
var deferred = q.defer();
// Read all rows from table
var result = [];
var request = new Request(
"SELECT * From ForumMessages",
function(err, rowCount)
{
deferred.resolve(result);
});
request.on('row', function(columns) {
columns.forEach(function(column) {
console.log("%s\t%s", column.metadata.colName, column.value);
result.push(columns);
});
});
connection.execSql(request);
//return the promise
return deferred.promise;
}
function ATVData(result){
//your bot code goes here
}
I think to expand on Grace's answer, for each row, you can also do this for some utility:
request.on('row', function(columns) {
var singleResult = {};
columns.forEach(function(column) {
console.log("%s\t%s", column.metadata.colName, column.value);
// Add a property to the singleResult object.
singleResult[column.metadata.colName] = column.value;
// Push the singleResult object to the array.
result.push(singleResult);
});
});
Then you can, in your bot's code, call each object by the property name in dot notation, for example: result[x].colName where colName is the name of the column (or object property in this case).
Example (assuming at least one result item from the database, with a "link" column that has data):
var adaptiveCardExample = {
'contentType': 'application/vnd.microsoft.card.adaptive',
'content': {
'$schema': 'http://adaptivecards.io/schemas/adaptive-card.json',
'type': 'AdaptiveCard',
'version': '1.0',
'body': [
{
"type": "TextBlock",
"text": "Code Example"
},
{
"type": "TextBlock",
"text": "We're going to " + result[0].link,
"wrap": true
}],
'actions': [
{
'type': 'Action.OpenUrl',
'title': 'Go to the example link',
'url': result[0].link
}
]
}
};
var adaptiveCardMsg = new builder.Message(session).addAttachment(adaptiveCardExample);
session.send(adaptiveCardMsg);
You may want to add a check for null or undefined for the property in the case it is a nullable field in the database, as a precaution.

Combined search in sails js

I have game collection:
{
"name": "Play RoadRash",
"version": "1.0.0",
"icon": "image-md-two-thirds.png",
"id": "6dc41c3fa0e7"
}
and platform collection:
{
"name": "PlayStation",
"version": "7",
"icon": "playstation.jpg",
"id": "55eaf322f1a16"
}
I'm trying to create a search query who searches in both collection based on name parameters. Does anyone have any idea how to search on multiple collection in sails waterline?
We've wrote a controller with full-text search within all models. All what it does is search within all models and their attributes by q parameter from request. Here is the full code of this controller:
var _ = require('lodash');
var Promise = require('bluebird');
module.exports = {
index: function (req, res) {
var models = [];
if (!req.param('q')) {
return res.badRequest(null, null, 'You should specify a "q" parameter!');
}
var q = req.param('q');
if (req.param('model')) {
var modelStr = req.param('model').toString().toLowerCase();
if (!(modelStr in sails.models)) {
return res.badRequest(null, null, 'Cannot find model: ' + modelStr);
}
models.push({name: modelStr, model: sails.models[modelStr]});
} else {
_.forEach(sails.models, function (model, modelStr) {
models.push({name: modelStr, model: model});
});
}
Promise.map(models, function (modelObj) {
var model = modelObj.model;
var modelStr = modelObj.name;
var where = _.transform(model.definition, function (result, val, key) {
result.or.push(_.set({}, key, {contains: q}));
}, {or: []});
return model
.find(where)
.then(function (queryRes) {
var resObj = {};
resObj[modelStr] = queryRes;
return Promise.resolve(resObj)
});
})
.then(function (searchRes) {
return _.transform(searchRes, function (result, val) {
result = _.merge(result, val);
}, {});
})
.then(res.ok)
.catch(res.serverError)
}
};
You can just copy-paste it in your api/controllers/SearchController.js and that's it. It still need to refactor this code, but it works.

Resources