Firebase functions reading/accessing remaining node information - javascript

I have create a trigger to monitor the path /Messages/{pushId}/originalText, so this should only trigger if a change occurs at that specific node and nowhere else.
What I would like to do is access the remaining Node data so for example how would I need the node date at the /Messages/{pushId}/followers which sits at the same level as originalText
Sample:
exports.makeUppercase = functions.database.ref('/Messages/{pushId}/originalText')
.onWrite(event => {
//how to access data at another node, for example
//important/Messages/{pushId}/followers
})

exports.makeUppercase = functions.database.ref('/Messages/{pushId}/originalText')
.onWrite(event => {
event.data.ref.parent.child('followers').once('value', data => {
console.log('Your data: ' + data)
})
})
I suggest you to take a look at the documentation it is really nice and you can find everything you want!
onWrite's documentations says that .onWrite returns a DeltaSnaphsot
DeltaSnaphot's doc says that DeltaSnaphot.ref() returns a Reference
Reference's documentation has every query method you need, in this case once

Related

Firestore DocumentSnapshot.data() returns undefined, but in the console it definetly has and it works for other documents

In this Firebase Function I'm getting two DocumentSnapshots, the first works fine, I can get the data (emailNonce) from the db, but the second DocumentSnapshot somehow has no data, the object is there, I can see it in the logs, but calling .data() on it returns undefined:
const addRentalFct = async (data, context) => {
// this works:
const secretsRef = db.collection('user-secrets').doc('Yv3gZU8TeJTixl0njm7kUXXpvhc2');
const secretsSnap = await secretsRef.get();
const dbNonce = secretsSnap.data().emailNonce;
functions.logger.log('got the dbNonce: ', dbNonce);
// this doesn't work, but ir's the same logic as above:
const boxesSecretsRef = db.collection('box-secrets').doc('CB8lNQ8ZUnv4FDT6ZXGW');
const boxSecretsSnap = await boxesSecretsRef.get();
functions.logger.log('got the boxSecretsSnap: ', boxSecretsSnap);
functions.logger.log('got the boxSecretsSnap.data(): ', boxSecretsSnap.data());
const boxPassword = boxSecretsSnap.data().password;
functions.logger.log('the box secret is: ', boxPassword);
...
}
The DB:
box-secrets collection
user-secrets:
(the secrets are from my dev environment)
The problem was that I copied the id for the new document from an already existing document in the console like this:
Automatically there was a space added in front. When I created the new doc, the space was not visible, but I could create another doc with the same id, without the space in front. Here you see that it
s not that obvious, it looks like there are two docs with the exact same id:
When having it like this, the firebase function didn't find any of the two docs. I had delete both and readd it without space, then it worked.

cy.wrap().its()... doesn't work when the value in .its() contains a period

I am looking to extract a URL parameter from the current URL I'm testing with Cypress. I was able to basically get the answer from this SO post, however, my extracted values are not available to me when I use Cypress's .its() command.
The parameters in the url all have periods in them, and I believe this is the cause for my error.
Here is my custom Cypress Command I'm building:
Cypress.Commands.add('getParmsCommand', function(value) {
cy.url().as('url')
cy.then( () => {
cy.log(this.url)
const kvPairArray = this.url.toString().split('?')[1].toString().split('&')
const paramObj = {}
kvPairArray.forEach(param => {
cy.log(param)
//default 'value' to 0 if it doesn't exist
const [ key, value="0" ] = param.split('=')
paramObj[key] = value
})
//forcefully adding a debug element to the key value store for testing
paramObj['beverage'] = 'soda'
cy.wrap(paramObj)
.its('timeline.ws') //doesn't work
// .its(`${Cypress.$.escapeSelector('timeline.ws')}`) doesn't work
// .its('timeline\.ws') doesn't work
// .its('"timeline.ws"') doesn't work
// .its('beverage') this DOES work!
.then(parmVal => {
cy.log(parmVal)
})
Here is the relevant part of the URL that I'm trying to extract from:
timeline.ws=3600000&timeline.to&timeline.fm&timeline.ar=false
You can see from the error that Cypress is only looking for the id timeline, NOT timeline.ws; it completely ignores everything after the period, and thus, never finds my parameter.
I saw there was a similar error with Cypress's .get() function back in 2018.
I am new to both javascript and Cypress, so I hope it's just a weird easy thing I'm overlooking. Any advice or educated guesses are greatly welcome at this point!
Thank you.
.its() is just a shorthand for property extraction. Since it fails with the period, you could instead use bracket notation in a .then().
cy.wrap(paramObj)
.then(paramObj => paramObj['timeline.ws'])
or just
cy.wrap(paramObj['timeline.ws'])
Playing around with the URL constructor
const urlString = 'http://example.com?timeline.ws=3600000&timeline.to&timeline.fm&timeline.ar=false'
const url = new URL(urlString)
cy.wrap(url.searchParams.get('timeline.ws'))
.should('eq', '3600000')
cy.wrap(url.searchParams.get('timeline.to'))
.should('be.empty')
cy.wrap(url.searchParams.get('timeline.ar'))
.should('eq', 'false')

Firestore startAfter() returning the same data in infinite scrolling when ordered by descending timestamp

I'm writing a profile page with chronological user posts (latest post on top) using Firestore on Ionic by setting orderBy() to "timestamp" descending. I'm using Ionic's infinite loading to load more posts when the user reaches the bottom, but the result is that Firestore loads the exact same posts over and over again. Please help!
Hi!
Sorry if this is a beginner question, but I've been wrapping my head around this for sever hours to no avail. The pagination works properly when in ascending order, but loads the same posts when in descending order. I've looked at trying the following alternatives, but they would not be cost-efficient:
Change the limit when user reaches the bottom: this would lead to reading all the posts again and again
Do it in ascending order but reverse the array: would defeat the purpose of pagination
use a query (where) to grab the documents before x timestamp: works in theory, but is a bit hacky and I would really want to know how startAfter() works since it's already there.
.
'''
READ_posts__profile(uid,limit, start) : Promise<[]>
{
return new Promise((resolve, reject) =>
{
if (start == null)
{
resolve();
}
else if(start == 'head')
{
start = new Date();
}
console.log(start);
this._DB.collection(this.ref.posts + uid + '/userposts' )
.orderBy("timestamp", 'desc').startAfter(start).limit(limit)
.get()
.then((querySnapshot) =>
{
let obj : any = [];
console.log(obj);
querySnapshot
.forEach((doc : any) =>
{
obj.push({
id : doc.id,
content : doc.data().content,
likes : doc.data().likes,
timestamp : doc.data().timestamp,
comments : doc.data().comments
});
});
resolve(obj);
})
.catch((error : any) =>
{
reject(error);
});
});
}
'''
Expected result: Infinite scrolling of posts from latest to oldest
Result: Infinite scrolling of first x posts again and again (limit is x)
Thank you for taking the time to read. Hope to hear from you guys soon!
UPDATE FOR FUTURE REFERENCE:
Rather than using the doc itself in .startAfter(), it worked with using doc.timestamp like so:
this._DB.collection(this.ref.posts + uid + '/userposts' )
.orderBy("timestamp", "desc").startAfter(start.timestamp).limit(this.limit)
.get()
.then(querySnapshot =>
{
In my case I added the query like this
query.startAfter(lastId)
where it should have been
query = query.startAfter(lastId)
The problem is that startAfter() is expecting a query cursor for the parameter, not the timestamp that you are passing.
A query cursor identifies the document:
Paginate data with query cursors | Firebase
What you need to do is save the doc to a variable at the class level and then pass that in with the next one:
// in the class variables at the top
latestEntry: any;
// then save a reference to it
this.latestEntry = data[data.length - 1].doc;
// Now you can use the latestEntry to query with startAfter
.startAfter(this.latestEntry)
This is the general theory. When I got this working myself in an app I used the code in this answer.
It's going to need quite a lot of rewriting of your code to do this, which is beyond the scope of the time I have right now, but hopefully this will help you resolve it yourself.
The problem is, that you have to match your last query order:
The order of the field values must ""match the order of the order by clauses of the query""
so if you first make a query like this
collectionReference
.whereEqualTo("id", someId)
.orderBy("timestamp", Query.Direction.DESCENDING)
.limit(5)
.get()
.addOnSuccessListener { documentSnapshots ->
//do some stuff
}
Then your next query should be like:
collectionReference
.whereEqualTo("id", someId)
.orderBy("timestamp", Query.Direction.DESCENDING) //FIELD
.limit(5)
.startAfter(lastVisible.get("timestamp"))
.get()
.addOnSuccessListener { documentSnapshots ->
//do some stuff
}
So you see that startAfter({somecriteria}) has to match the orderBy({somecriteria}) of your first query where you got your last doc visible from, otherwise it will return your first query again.
The query should work if you make sure that the data type are the same between timestamp in firestore and variable start
I understand the OP uses Javascript SDK, I think I find a workaround for this issue. unfortunately currently I am using Flutter, but I think you can tweak it easily.
so the idea is to convert the Date or DateTime object from the language (Dart, Javascript, Kotlin etc) to Timestamp object from the Firestore library. and then pass that Timestamp object to startAfter
in Flutter, you can do it like this
import 'package:cloud_firestore/cloud_firestore.dart'; // to import Timestamp class from Firestore
var lastCreatedAtDateTime = yourObject.createdAt; // this is Date Time Object from your language
var firestoreTimestamp = Timestamp.fromDate(lastCreatedAtDateTime); // convert Date to be Firestore's timestamp
query = query.startAfter([firestoreTimestamp)]);

More efficient way to handle an extremely specific response from a Rest API in JavaScript

I'm working with a Dungeons & Dragons 5e API and want that an especific result be treated in a special way. The user can choose what to search from a range of options, and in only one of them would I need to take care of the answer in a different way. In this option, I get the answer in JSON that contains a 'name' field, which stores a String, but in this specific case this String comes with an acronym, and I would like to transform it into the full name.I'm afraid to just put am 'if' statement in the middle of the code and deal with the situation inefficiently, even more so that I did not find similar situations to have any reference.
This is part of the result of the API I want to handle in a special way:
{"count":6,
"results":[
{"name":"STR",
"url":"http://www.dnd5eapi.co/api/ability-score/1"},
{"name":"DEX",
"url":"http://www.dnd5eapi.co/api/ability-scores2"},
....
]
}
This is how I handle the answer:
fetch(fullAPIURL)
.then(result => result.json())
.then(data => {
let resultContainer = document.getElementById('resultContainer');
//Cleaning the result container from previous results
document.querySelectorAll('#resultContainer article').forEach(container =>
resultContainer.removeChild(container));
spanSearchResult.classList.remove('invisible', 'searchFail');
spanSearchResult.classList.add('searchSucess');
spanSearchResult.innerHTML = `Search returned ${data.count} results`;
for (element of data.results) {
let containerTitle = element.name != undefined ? element.name : element.class;
resultContainer.appendChild(createResultContainer(containerTitle));
}
})
.catch(err => {
spanSearchResult.classList.remove('invisible');
spanSearchResult.classList.add('searchFail');
spanSearchResult.innerHTML = 'Something went wrong! Details in the console';
console.log(err);
});
Is putting a condition in this snippet of code really the most efficient way to solve this situation?
Thanks in advance.
You could just make a lookup call, actually. In fact, that'd be preferable if you ever want to port your application to another language, for example.
Define the following:
var retrieve = (function() {
var items = {
"STR": "Strength",
"DEX": "Dexterity"
};
return function(item) {
return items[item] || item;
}
})();
console.log(retrieve("DEX"));
With this, you can simply call retrieve(element.name) to retrieve its "actual" name. You can add elements to the object to create new translations, and if you ever need to support multiple languages, you can even replace the function entirely.

Firebase Cloud function event - sometime not getting data on update event

I have written firebase cloud function to trigger on update record. sometimes I am not getting the same record which is updating. I am adding my code below.Please check attached image also.
exports.onNotificationUpdate = functions.database.ref('/Notification/{userId}/{notificationId}/userResponse').onUpdate(event => {
return admin.database().ref(`/Notification/${event.params.userId}/${event.params.notificationId}`).once('value').then(function (snapshot) {
var notification = snapshot.val();
if (!notification) {
console.error("Notification not found on notification update");
return;
};
I can also get Notification object from the parent but I want to know issue best approach and the problem with this code.
this is error log
this is database structure
This is my 1st post here please let me know if need more information.
Thanks
You don't have to call once within the Function since it is already returning the data at the location you are listening to, just listen to the parent node.
So you should do like:
exports.onNotificationUpdate = functions.database.ref('/Notification/{userId}/{notificationId}').onUpdate(event => {
const notification = event.data.val();
if (notification === null) {
console.error("Notification not found on notification update");
return null;
//actually this would only be called in case of deletion of the Notification
} else {
//do something with the notification data: send Android notification, send mail, write in another node of the database, etc.
//BUT return a Promise
//notification const declared above is a JavaScript object containing what is under this node (i.e. a similar structure than your database structure as shown in the image within your post.)
}
});
I would suggest that you have a look at these three videos from the Firebase team:
https://www.youtube.com/watch?v=7IkUgCLr5oA&t=517s
https://www.youtube.com/watch?v=652XeeKNHSk&t=27s
https://www.youtube.com/watch?v=d9GrysWH1Lc
Also, note that Cloud Functions have been updated and the first line of your code shall be written differently if you are using a CF version above 1.0.0. See https://firebase.google.com/docs/functions/beta-v1-diff

Categories