Angularfire Increment transaction - javascript

Im having trouble incrementing a count of post "likes". The following is what I have right now:
addLike(pid, uid) {
const data = {
uid: uid,
};
this.afs.doc('posts/' + pid + '/likes/' + uid).set(data)
.then(() => console.log('post ', pid, ' liked by user ', uid));
const totalLikes = {
count : 0
};
const likeRef = this.afs.collection('posts').doc(pid);
.query.ref.transaction((count => {
if (count === null) {
return count = 1;
} else {
return count + 1;
}
}))
}
this obviously throws and error.
My goal is to "like" a post and increment a "counter" in another location. Possibly as a field of each Pid?
What am I missing here? I'm certain my path is correct..
Thanks in advance

You're to use the Firebase Realtime Database API for transactions on Cloud Firestore. While both databases are part of Firebase, they are completely different, and you cannot use the API from one on the other.
To learn more about how to run transactions on Cloud Firestore, see updating data with transactions in the documentation.
It'll look something like this:
return db.runTransaction(function(transaction) {
// This code may get re-run multiple times if there are conflicts.
return transaction.get(likeRef).then(function(likeDoc) {
if (!likeDoc.exists) {
throw "Document does not exist!";
}
var newCount = (likeDoc.data().count || 0) + 1;
transaction.update(likeDoc, { count: newCount });
});
}).then(function() {
console.log("Transaction successfully committed!");
}).catch(function(error) {
console.log("Transaction failed: ", error);
});

Related

Firebase realtime database backend async function not updating users

I call this function when I have a list of users who i want to update. The console.logs and resolve message are in perfect order and what I expect. But nothing gets written/changed on the realtime database. It looks completely untouched. Did I do the promise wrong? Any guidance appreciated, thank you.
function updateUsers(){
return new Promise(resolve => {
objectLength = Object.keys(usersUpdate).length;
for (let i=0; i < objectLength; i++){
admin.database().ref('users/' + usersUpdate[i].key + '/notifications/' + usersUpdate[i].notif).update({
"sendPush" : 0,
})
.then(()=>{
console.log('updated user ' + i);
if (i == (objectLength-1)){
resolve('resolved updateUsers');
}
})
.catch((error) => console.log('Error ' + error));
}
})
}
Apparently firebase doesn't like being passed integers. Lesson learned. Turned the value to "0" instead of 0 and it worked.

NodeJS processing for API calls sequentially, but resulting in stack overflow

OK, so I have a situation where I cannot just fire thousands of requests to an API server.
I have a Node process (no UI) that I need to have process each API response/update sequentially, waiting for completion before sending the next request.
I may be making this more complicated than I think - not sure. I can only figure out how to do this with recursive calls, but this results in a stack overflow as there can be thousands of records. The general process is this:
get rows from SQL table with ID's (result)
formulate and send of an API call to retrieve ID's info
if returned data has image data, write it back to SQL table
wait on this process so not to bombard API server with thousands of requests all at once
repeat until last ID is processed (can be thousands, more than stack space)
Here's sample code (not actual so ignore syntax errors if any)...
UPDATED: actual running code with sensitive items removed
var g_con = null; //...yeah I know, globals are bad
//
// [ found updating ]
//
function getSetImage(result, row, found) {
if(row >= result.length) { //...exit on no row or last row processed
con.end();
return;
}
item = result[row]; //...next SQL row
if((item !== undefined) && (item.autoid !== undefined)) {
//...assemble API and send request
//
let url = 'https://...API header...'
+ item.autoid
+ '...API params...';
request(url, (error, response, body) => {
if(response.statusCode !== 200)
throw('Server is not responding\n' + response.statusMessage);
let imageData = JSON.parse(body);
if((imageData.value[0] !== undefined) &&
(imageData.value[0].DETAIL !== undefined) &&
(imageData.value[0].DETAIL.Value.length) ) {
//...post back to SQL
//
found++;
console.log('\n' + item.autoid + '/['+ item.descr + '], ' + 'Found:' + found);
qry = 'update inventory set image = "'+imageData.value[0].DETAIL.Value+'" where autoid = "'+item.autoid+'";';
g_con.query(qry, (err) => {
if (err) {
console.log('ERROR:',err.message, '\nSQL:['+err.sql+']\n');
throw err.message;
}
});
row++;
setTimeout(()=>{getSetImage(result, row, found)}, 0); //...nested call after SQL
} else {
row++;
process.stdout.write('.'); //...show '.' for record, but no image
setTimeout(()=>{getSetImage(result, row, found)}, 0); //...nested call after SQL
}
}); //...request callback
}
// } else {
// throw '\nERROR! result['+row+'] undefined? Images found: '+found;
// }
}
//
// [ main lines ]
//
(() => {
let params = null;
try {
params = JSON.parse(fs.readFileSync('./config.json'));
//...load autoids array from SQL inventory table - saving autoids
// autoids in INVENTRY join on par_aid's in INVENTRYIMAGES
//
g_con = mysql.createConnection(params.SQLConnection);
g_con.connect((err) => { if(err) {
console.log('ERROR:',err.message);
throw err.message;
}
});
//...do requested query and return data or an error
//
let qry = 'select autoid, descr from inventory order by autoid;';
g_con.query(qry, (err, results, flds) => {
if (err || flds === undefined) {
console.log('ERROR:',err.message, '\nSQL:['+err.sql+']\n');
throw err.message;
}
console.log('Results length:',results.length);
let row = 0;
let found = 0;
getSetImage(results, row, found);
});
}
catch (err) {
console.log('Error parsing config parameters!');
console.log(err);
}
})();
So here's the answer using Promises (except for MySQL):
//
// [ found updating ]
//
async function getSetImage(data) {
for(let item of data) {
if(item && item.autoid) {
//...assemble API and send request
//
let url = g_URLHeader + g_URLPartA + item.autoid + g_URLPartB;
let image = await got(url).json().catch(err => {
console.log(err);
err.message = 'API server is not responding';
throw err;
});
if(image && image.value[0] && image.value[0].DETAIL &&
image.value[0].DETAIL.Value.length ) {
console.log('\nFound: ['+item.autoid+' - '+item.descr
+ '] a total of ' + g_found + ' in ' + g_count + ' rows');
g_found++;
//...post back to SQL
//
let qry = 'update inventory set image = "'
+ image.value[0].DETAIL.Value
+ '" where autoid = "'
+ item.autoid+'";';
await g_con.query(qry, (err) => {
if (err) {
console.log('ERROR:',err.message, '\nSQL:['+err.sql+']\n');
throw err.message;
}
});
} else {
process.stdout.write('.'); //...show '.' for record, but no image
} //...if/else image.value
g_count++;
} //...if item
} //...for()
}
As I've said in all my comments, this would be a ton simpler using promises and async/await. To do that, you need to switch all your asynchronous operations over to equivalents that use promises.
Here's a general outline based on the original pseudo-code you posted:
// use got() for promise version of request
const got = require('got');
// use require("mysql2/promise" for promise version of mysql
async function getSetImage(data) {
for (let item of data) {
if (item && item.id) {
let url = uriHeader + uriPartA + item.id + uriPartB;
let image = await got(url).json().catch(err => {
// log and modify error, then rethrow
console.log(err);
err.msg = 'API Server is not responding\n';
throw err;
});
if (image.value && image.value.length) {
console.log('\nFound image for ' + item.id + '\n');
let qry = 'update inventory set image = "' + image.value + '" where id = "' + item.id + '";';
await con.query(qry).catch(err => {
console.log('ERROR:', err.message, '\nSQL:[' + err.sql + ']\n');
throw err;
});
}
} else {
// no image data found
process.stdout.write('.'); //...show '.' for record, but no image
}
}
}
//...sql query is done, returning "result" - data rows
getSetImage(result).then(() => {
console.log("all done");
}).catch(err => {
console.log(err);
});
Some notes about this code:
The request() library is no longer getting new features and is in maintenance mode and you need to change to a different library to get built-in promise support. You could use request-promise (also in maintenance mode), but I recommend one of the newer libraries such as got() that is more actively being developed. It has some nice features (automatically checks status for you to be 2xx, built-in JSON parsing, etc...) which I've used above to save code.
mysql2/promise has built-in promise support which you get with const mysql = require('mysql2/promise');. I'd recommend you switch to it.
Because of the user of async/await here, you can just loop through your data in a regular for loop. And, no recursion required. And, no stack build-up.
The way promises work by default, any rejected promises will automatically terminate the flow here. The only reason I'm using .catch() in a couple places is just for custom logging and tweaking of the error object. I then rethrow which propagates the error back to the caller for you.
You can tweak the error handling to your desire. The usual convention with promises is to throw an Error object (not a string) and that's often what callers are expecting to see if the promise rejects.
This code can be easily customized to log errors and continue on to subsequent items in the array. Your original code did not appear to do that so I wrote it to abort if it got an error.

FIrebase jQuery .on method is updating array values one by one rather than all at once

I am trying to update the values of the orders placed by users on the Corporate's page without a refresh. For this, I used the jQuery .on method. However, this returns the values in the array that I generated for the orders one by one rather than all at once. Is this just an issue with firebase or is it just my code.
Here is my code:
When I get the values:
firebase.database().ref('Orders/' + user_id).on('value', function(snapshot) {
// Check if the user has any pending orders
if (snapshot.val() === null) {
// No Pending Orders are Present
$('.order_content-parent').html(' <div class="order_content">Hooray! You have no pending orders!</div>');
} else {
// One or more pending orders are present
console.log(snapshot.val());
snapshot.forEach(function(child){
$('.order_content-parent').html(' <div class="order_content"></div>');
var order = child.val().Order;
var key = child.key;
console.log('Key is : '+key);
getOrders(key);
});
When I insert the values into the database:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
var myId = user.uid;
const orders = ['Orders: '];
$('.postData').each(function() {
var data = $(this).html();
orders.push(data);
var database = firebase.database();
database.ref('Orders/' + user_id + '/' + myId).set({
Order: orders
}, function(error) {
if (error) {
// The write failed...
alert(error);
} else {
$('.postData').html('Connecting...');
}
});
database.ref('Orders/' + myId).set({
Order: orders,
For: user_id
}, function(error) {
if (error) {
// The write failed...
alert(error);
} else {
$('.postData').html('Order Successfully Placed!');
}
});
});
} else {
// No user is signed in.
}
});
Here is my console when I print the values from the database:
Here is my database structure:
Can anyone help
Thanks in advance,
Tom
I think this is expected behaviour, as the documentation states:
The value event is called every time data is changed at the specified database reference, including changes to children.
Since your inserts are on a each loop, they get inserted one by one, triggering the .on() listener multiple times.
You could try inserting all the orders at once. Please try this approach and let me know if it works:
firebase.auth().onAuthStateChanged(function (user) {
if (!user) {
console.log("No user is signed in");
return;
}
var myId = user.uid;
var orders = [];
// Get the orders to insert first
$('.postData').each(function () {
var data = $(this).html();
orders.push(data);
});
// Then, insert them all at once
var database = firebase.database();
database.ref('Orders/' + user_id + '/' + myId).set({
Order: orders
}, function (error) {
if (error) {
// The write failed...
alert(error);
return;
}
$('.postData').html('Connecting...');
database.ref('Orders/' + myId).set({
Order: orders,
For: user_id
}, function (error) {
if (error) {
// The write failed...
alert(error);
return;
}
$('.postData').html('Order Successfully Placed!');
});
});
});

Firebase StartAfter in query not working as expected

I am using firebase firestore as my database and i have written firebase functions to retrieve data from the firestore database.
What i am trying to achieve is pagination and as per the docs i have implemented the code for my firebase function. Below is the code:
exports.getBillList = functions.https.onRequest((req, res) => {
let docs=[];
let limit = 15;
return cors(req, res, () => {
let lastBillInList=req.query.lastBillInList;
console.log("lastBillInList value: " + lastBillInList);
if (lastBillInList === null || lastBillInList === undefined) lastBillInList = 0;
//var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length - 1];
if(lastBillInList==0){
console.log('first time call: as no lastbillseqq');
db.collection("bills").orderBy('billNo','desc').limit(limit).get().then(function (querySnapshot) {
querySnapshot.forEach(function (doc) {
docs.push(doc.data());
});
res.status(200).send(docs);
}).catch(function (error) {
console.error("Error getting list: ", error);
res.status(500).send();
});
}else{
console.log('second time call: as no lastbillseqq'+ lastBillInList);
db.collection("bills").orderBy('billNo', 'desc').startAfter(lastBillInList).limit(limit).get().then(function (querySnapshot) {
querySnapshot.forEach(function (doc) {
docs.push(doc.data());
});
res.status(200).send(docs);
}).catch(function (error) {
console.error("Error getting list: ", error);
res.status(500).send();
});
}
});
});
I have added condition to my firebase function where it checks whether a last bill number is provided.
If yes then retrieve all bill records after the last bill till the set limit or else if no last bill number is provided then consider it as first request and retrieve the initial records upto the limit
However the problem I am facing is that irrespective of code executing the else part, when the last bill number is provided , the query always return the records from the start of the result upto the limit specified. For some reason StartAfter is not working
e.g i have records with bill number form 1 to 25 , i have arranged them in descending order in above code ,so the result is from bill number 25 to 1.
When no bill number is provided i get result of bill numbers from 25 to 11.
When I provide the bill number i get result from bill numbers 25 to 11 instead of expected bill numbers from 10 to 1.
Can anyone help me out in this?
I could manage to paginate by below code. In my model object I have used the Document ID , based on which i find the documentReference -> documentSnapshot. I finally use the documentSnapshot to compare(this was my mistake as earlier i was not using documentSnapshot) to get the desired output.
Below is my code:
exports.getBillList = functions.https.onRequest((req, res) => {
console.log("----------------------------------function start");
let docs = [];
let lastBillInList=req.query.lastBillInList;
if(lastBillInList===undefined){
lastBillInList="noDocument";
}
let limit = 15;
return cors(req, res, () => {
var lastVisible = db.collection("bills").doc(lastBillInList);
lastVisible.get().then(function (doc) {
if (doc.exists) {
db.collection("bills")
.orderBy("billNo", "desc")
.startAfter(doc)
.limit(limit).get().then(function(querySnapshot){
querySnapshot.forEach(function (doc) {
//console.log(doc.data().billNo + " pushed.." + ": last bill was: " + tempBill);
docs.push(doc.data());
});
return res.status(200).send(docs);
});
} else {
db.collection("bills")
.orderBy("billNo", "desc")
.limit(limit).get().then(function (querySnapshot) {
querySnapshot.forEach(function (doc) {
docs.push(doc.data());
});
return res.status(200).send(docs);
});
}
}).catch(function (error) {
console.log("Error getting document:", error);
return res.status(500).send();
});
console.log("----------------------------------function end");
});
});
Ok, I've found an interesting point. You have to call start(afterDocument:) before limit(to:) otherwise it won't work as expected.
So it should be something like following:
db.collection("collection")
.order(by: "createdAt", descending: true)
.start(afterDocument: snapshot) // This is where you need to put start
.limit(to: Constants.Database.Feed.limit)
.getDocuments { (snapshot, error) in
// some useful stuff
}

Firebase function execution and subscription to list that is being updated by a firebase function

I think a firebase function updating a list that I have in the firebase database is being captured by a subscription that is subscribed to that list. From what the list output looks like on my phone (in the app)...and from what my console output looks like (the way it repeats) it seems like it is capturing the whole list and displaying it each time one is added. So (I looked this up)...I believe this equation represents what is happening:
(N(N + 1))/2
It is how you get the sum of all of the numbers from 1 to N. Doing the math in my case (N = 30 or so), I get around 465 entries...so you can see it is loading a ton, when I only want it to load the first 10.
To show what is happening with the output here is a pastebin https://pastebin.com/B7yitqvD.
In the output pay attention to the array that is above/before length - 1 load. You can see that it is rapidly returning an array with one more entry every time and adding it to the list. I did an extremely rough count of how many items are in my list too, and I got 440...so that roughly matches the 465 number.
The chain of events starts in a page that isn't the page with the list with this function - which initiates the sorting on the firebase functions side:
let a = this.http.get('https://us-central1-mane-4152c.cloudfunctions.net/sortDistance?text='+resp.coords.latitude+':'+resp.coords.longitude+':'+this.username);
this.subscription6 = a.subscribe(res => {
console.log(res + "response from firesbase functions");
loading.dismiss();
}, err => {
console.log(JSON.stringify(err))
loading.dismiss();
})
Here is the function on the page with the list that I think is capturing the entire sort for some reason. The subscription is being repeated as the firebase function sorts, I believe.
loadDistances() {
//return new Promise((resolve, reject) => {
let cacheKey = "distances"
let arr = [];
let mapped;
console.log("IN LOADDISTANCES #$$$$$$$$$$$$$$$$$$$$$");
console.log("IN geo get position #$$$$$$$5354554354$$$$$$$");
this.distancelist = this.af.list('distances/' + this.username, { query: {
orderByChild: 'distance',
limitToFirst: 10
}});
this.subscription6 = this.distancelist.subscribe(items => {
let x = 0;
console.log(JSON.stringify(items) + " length - 1 load");
items.forEach(item => {
let storageRef = firebase.storage().ref().child('/settings/' + item.username + '/profilepicture.png');
storageRef.getDownloadURL().then(url => {
console.log(url + "in download url !!!!!!!!!!!!!!!!!!!!!!!!");
item.picURL = url;
}).catch((e) => {
console.log("in caught url !!!!!!!$$$$$$$!!");
item.picURL = 'assets/blankprof.png';
});
this.distances.push(item);
if(x == items.length - 1) {
this.startAtKey4 = items[x].distance;
}
x++;
})
//this.subscription6.unsubscribe();
})
}
The subscription in loadDistances function works fine as long as I don't update the list from the other page - another indicator that it might be capturing the whole sort and listing it repeatedly as it sorts.
I have tried as as I could think of to unsubscribe from the list after I update...so then I could just load the list of 10 the next time the page with the list enters, instead of right after the update (over and over again). I know that firebase functions is in beta. Could this be a bug on their side? Here is my firebase functions code:
exports.sortDistance = functions.https.onRequest((req, res) => {
// Grab the text parameter.
var array = req.query.text.split(':');
// Push the new message into the Realtime Database using the Firebase Admin SDK.
// Get a database reference to our posts
var db = admin.database();
var ref = db.ref("profiles/stylists");
var promises = [];
// Attach an asynchronous callback to read the data at our posts reference
ref.on("value", function(snapshot) {
//console.log(snapshot.val());
var snap = snapshot.val();
for(const user in snap) {
promises.push(new Promise(function(resolve, reject) {
var snapadd = snap[user].address;
console.log(snapadd + " snap user address (((((((())))))))");
if(snapadd != null || typeof snapadd != undefined) {
googleMapsClient.geocode({
address: snapadd
}).asPromise()
.then(response => {
console.log(response.json.results[0].geometry.location.lat);
console.log(" +++ " + response.json.results[0].geometry.location.lat + ' ' + response.json.results[0].geometry.location.lng + ' ' + array[0] + ' ' + array[1]);
var distanceBetween = distance(response.json.results[0].geometry.location.lat, response.json.results[0].geometry.location.lng, array[0], array[1]);
console.log(distanceBetween + " distance between spots");
var refList = db.ref("distances/"+array[2]);
console.log(snap[user].username + " snap username");
refList.push({
username: snap[user].username,
distance: Math.round(distanceBetween * 100) / 100
})
resolve();
})
.catch(err => { console.log(err); resolve();})
}
else {
resolve();
}
}).catch(err => console.log('error from catch ' + err)));
//console.log(typeof user + 'type of');
}
var p = Promise.all(promises);
console.log(JSON.stringify(p) + " promises logged");
res.status(200).end();
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
});
What is weird is, when I check the firebase functions logs, all of this appears to only run once...but I still think the subscription could be capturing the whole sorting process in some weird way while rapidly returning it. To be as clear as possible with what I think is going on - I think each stage of the sort is being captured in an (N(N + 1))/2...starting at 1 and going to roughly 30...and the sum of the sorting ends up being the length of my list (with 1-10 items repeated over and over again).
I updated to angularfire2 5.0 and angular 5.0...which took a little while, but ended up solving the problem:
this.distanceList = this.af.list('/distances/' + this.username,
ref => ref.orderByChild("distance").limitToFirst(50)).valueChanges();
In my HTML I used an async pipe, which solved the sorting problem:
...
<ion-item *ngFor="let z of (distanceList|async)" no-padding>
...

Categories