Firebase Functions: orderBy function not working - javascript

The following code works in the emulator but returns an empty array in firebase when includeVideos or includeAudios is false:
let query = db.collection("content");
if (includeVideos && !includeAudios) {
query = query.where("type", '==', "video").orderBy("type");
}
if (includeAudios && !includeVideos) {
query = query.where("type", '==', "audio").orderBy("type");
}
if (!includeVideos && !includeAudios) {
return {empty json....}
}
if (matchingPersonID != undefined) {
query = query.where("attributionID", '==', matchingPersonID).orderBy("attributionID");
}
//either categories OR matchingSeriesID is always undefined/empty
if (matchingSeriesID != undefined) {
query = query.where("seriesIDs", 'array-contains', matchingSeriesID).orderBy("seriesIDs");
}
if (matchingCategories.length != 0) {
// 'OR' LOGIC
query = query.where("categories", 'array-contains-any', matchingCategories).orderBy("categories");
}
if (lastDocumentID != undefined) {
await db.collection("content").doc(lastDocumentID).get().then((snapshot) => {
query = query.startAfter(snapshot);
log(`starting after document ${snapshot}`)
})
}
//the code works when this line is removed, but the query needs to be sorted by time in order for pagination to work
query = query.orderBy("upload_date", "desc");
return query.limit(getNext).get()...
I get no error messages in the google cloud logs, and so I have no easy way of creating an index, if that's what I need to do.
In firestore, the fields upload_date, attributionID, type, seriesIDs, and categories are all on the same level in the document.
Any help would be much appreciated!!

As you comment, it seems that an index for that field is missing.
This documentation mentions two ways to handle these indexes:
By using the CLI. You can bring your indexes to a local file in a JSON format. For this, run firebase firestore:indexes inside your firebase project folder, you'll get a formatted JSON output for you to copy, add a new one and then deploy them with firebase deploy --only firestore:indexes.
Indeed, you can use the URL generated by Firebase when it catches the error of using an index that does not exist, but you can also do it by hand from the Firebase console of the application:

Related

How do I Collect User IDs + Retrieve Corresponding Tokens + Send a Push Notification Via Firebase Cloud Function (JS)

The Problem:
I have been unable to use Firebase (Google) Cloud Functions to collect and utilize device tokens for the cloud messaging feature.
Context:
I am a self-taught android-Java developer and have no JavaScript experience. Despite that, I believe I have code that should work and am not sure what the problem is. To my understanding, it could be one of three things:
Somehow my Firebase Realtime Database references are being called incorrectly and I am not retrieving data as expected.
I may need to use Promises to wait for all calls to be made before proceeding, however I don't really understand how I would incorporate that into the code I have.
I may be using multiple return statements incorrectly (which I am also fuzzy on).
My error message on the Firebase Realtime Database console is as follows:
#firebase/database: FIREBASE WARNING: Exception was thrown by user callback. Error: Registration token(s) provided to sendToDevice() must be a non-empty string or a non-empty array.
at FirebaseMessagingError.FirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:42:28)
at FirebaseMessagingError.PrefixedFirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:88:28)
at new FirebaseMessagingError (/srv/node_modules/firebase-admin/lib/utils/error.js:254:16)
at Messaging.validateRegistrationTokensType (/srv/node_modules/firebase-admin/lib/messaging/messaging.js:729:19)
at Messaging.sendToDevice (/srv/node_modules/firebase-admin/lib/messaging/messaging.js:328:14)
at admin.database.ref.once.snapshot (/srv/index.js:84:12)
at onceCallback (/srv/node_modules/#firebase/database/dist/index.node.cjs.js:4933:51)
at /srv/node_modules/#firebase/database/dist/index.node.cjs.js:4549:22
at exceptionGuard (/srv/node_modules/#firebase/database/dist/index.node.cjs.js:698:9)
at EventList.raise (/srv/node_modules/#firebase/database/dist/index.node.cjs.js:9684:17)
The above indicates I am not retrieving data either at all or by the time the return is called. My JavaScript function code is:
'use strict';
const admin = require('firebase-admin');
admin.initializeApp();
exports.pushNotification = functions.database.ref('/Chat Messages/{chatId}/{pushID}').onCreate((snapshot, context) => {
const valueObject = snapshot.after.val();
return admin.database().ref(`/Chat Basics/${valueObject.chatKey}/Chat Users`).once('value', statusSnapshot => {
var index = 0;
var totalkeys = statusSnapshot.numChildren();
var msgIDs = [];
statusSnapshot.forEach(msg=>{
msgIDs.push(msg.key.toString());
if(index === totalkeys - 1){
const payload = {
notification : {
title: valueObject.userName,
body: valueObject.message,
sound: "default"
}
}
sendNotificationPayload(valueObject.uid, payload);
}
index++;
});
});
});
function sendNotificationPayload(uid, payload){
admin.database()
.ref(`/User Token Data/${uid}`)
.once('value', snapshot=> {
var tokens = [];
//if(!snapshot.exists())return;
snapshot.forEach(item =>{
tokens.push(item.val())
});
admin.messaging()
.sendToDevice(tokens, payload)
.then(res => {
return console.log('Notification sent')
})
.catch(err => {
return console.log('Error in sending notification = '+err)
});
});
}
This code is mostly inspired by what was said to be a working example here from another Stack Overflow question here. I have successfully tested sending a notification to a single device by manually copying a device token into my function, so the function does run to completion. My Java code seems to be irrelevant to the problem, so I have not added it (please ask in the comments if you would like it added for further context).
What I Have Tried:
I have tried implementing promises into my code, but I don't think I was doing it properly. My main reference for this was here. I have also looked at the documentation for literally everything related to this topic, however my knowledge of JS is not sufficient to really apply barebones examples to my code.
My Firebase Realtime Database Nodes:
#1: Loop through chat members to collect user IDs:
"Chat Basics" : {
"1607801501690_TQY41wIfArhHDxEisyupZxwyHya2" : {
"Chat Users" : {
"JXrclZuu1aOwEpCe6KW8vSDea9h2" : true,
"TQY41wIfArhHDxEisyupZxwyHya2" : true
},
#2: Collect user tokens from collected IDs (ignore that tokens are matching):
"User Token Data" : {
"JXrclZuu1aOwEpCe6KW8vSDea9h2" : "duDR3KH3i3I:APA91bH_LCeslZlqL8akYw-LrM9Dv__nx4nU1TquCS0j6bGF1tlIARcheREuNdX1FheC92eelatBC8LO4t6gt8liRdFHV-NDuNLa13oHYxKgl3JBPPlrMo5rB5XhH7viTo4vfYOMftRi",
"TQY41wIfArhHDxEisyupZxwyHya2" : "duDR3KH3i3I:APA91bH_LCeslZlqL8akYw-LrM9Dv__nx4nU1TquCS0j6bGF1tlIARcheREuNdX1FheC92eelatBC8LO4t6gt8liRdFHV-NDuNLa13oHYxKgl3JBPPlrMo5rB5XhH7viTo4vfYOMftRi"
}
Conclusion:
Concrete examples would be much appreciated, especially since I am crunching right now. Thanks for your time and help!
Update:
After some more testing, it looks like the problem is definitely due to my lack of understanding of promises in two areas. Firstly, only one user is collected before the final return is called. Secondly, the final return is called before the 2nd forEach() loop can store snapshot data to an array.
For this code then, how may I modify (or rebuild) it so that it collects all keys before proceeding to retrieve token data from all keys - ultimately before returning the notification?
Just as with every question I post, I managed to figure out how to do it (tentatively) a few hours later. Below is a full example of how to send a notification to chat users based on a message sent (although it does not yet exclude the sender) to a given chat. The order of operations are as such:
User message is saved and triggers event. Relevant data the message contains are:
username, chat key, message
These are retrieved, with (username + message) as the (title + body) of the
notification respectively, and the chat key is used for user id reference.
Loop through chat user keys + collect.
Loop through array of chat user keys to collect array of device tokens.
Send notification when complete.
The code:
//Use firebase functions:log to see log
exports.pushNotification = functions.database.ref('/Chat Messages/{chatId}/{pushId}').onWrite((change, context) => {
const valueObject = change.after.val();
return admin.database().ref(`/Chat Basics/${valueObject.chatKey}/Chat Users`).once('value', statusSnapshot => {
var index = 0;
var totalkeys = statusSnapshot.numChildren();
var msgIDs = [];
statusSnapshot.forEach(msg=>{
msgIDs.push(msg.key.toString());
if(index === totalkeys - 1){
const payload = {
notification : {
title: valueObject.userName,
body: valueObject.message,
sound: "default"
}
}
let promises = [];
var tokens = [];
for(let i=0; i < msgIDs.length; i++){
let userId = msgIDs[i];
let promise = admin.database().ref(`/User Token Data/${userId}`).once('value', snapshot=> {
tokens.push(snapshot.val());
})
promises.push(promise);
}
return Promise.all(promises).then(() => {
return admin.messaging().sendToDevice(tokens, payload);
});
}
index++;
return false;
});
});
});

Angular Firestore query with where returns all docs in collection

I have an angular app that is using Firestore. Whenever I query for docs in collection that meet a specific condition, the array that is returned contains every document in the collection. I do not understand why this is happening as I am following the documentation.
On call to the collection in the component
this.FirebaseService.getDocsByParam( 'versions', 'projectId', this.projectData.uid )
.then((snapshot) => {
var tempArray = [];
var docData;
snapshot.forEach((doc) => {
docData=doc.data();
docData.uid=doc.id;
tempArray.push(docData);
});
this.versionList = tempArray;
this.versionData = this.versionList[this.versionList.length-1];
this.initializeAll();
})
.catch((err) => {
console.log('Error getting documents', err);
});
Firebase service making the call
getDocsByParam( collection, getParam:string, paramValue:string ) {
var docRef = this.afs.collection(collection, ref => ref.where(getParam, '==', paramValue));
return docRef.ref.get();
}
Below is a screen shot of the versions collection. It shows one of the returned docs, which does not even have the required field.
When you call docRef.ref on a AngularFirestoreCollection it returns the underlying collection, not the query. So your return docRef.ref.get() is indeed getting the entire collection.
I think you can use docRef.query to get the query, but I don't even thing there's any reason to use an AngularFire call at all here. Since your code is already using the plain JavaScript API to process the documents, you might as well stick to that SDK in your getDocsByParam too:
getDocsByParam( collection, getParam:string, paramValue:string ) {
var docRef = this.afs.collection(collection).ref;
return docRef.where(getParam, '==', paramValue).get();
}

Is it possbie to make an if condition based on Firebase data

I need to make some if statements for advanced filter search and also some if conditions to make a table filterable. Is there any way to make if statements based on Firebase. I need to make the output from Firebase true and false to replace with archived and display it in a dynamic table.
Thank you in advance.
e.g.
var usersRef = firebase.database().ref("users");
var query = usersRef.orderByChild("disabled").equalTo(true);
query.on("value", function(snapshot) {
snapshot.forEach(function(user) {
if (user.disabled != true) {
$('#ArchiveLabel').text("Archived");
} else {
$('#ArchiveLabel').text("");
}
}); });
You're looking for something along these lines:
var usersRef = firebase.database().ref("users");
var query = usersRef.orderByChild("something").equalTo(true);
query.on("value", function(snapshot) {
snapshot.forEach(function(user) {
console.log(user.key, user.val());
});
});
I highly recommend that you spend some time in the Firebase documentation, for example its section on ordering and filtering data. You also might benefit from taking the Firebase codelab for web developers.

How to get database value not related to the event in Cloud Functions for Firebase?

I have a firebase database and I am currently trying to use cloud functions to perform an operation when a value in my database changes. So far, it successfully triggers code to run when the value in my database changes. However, when the database value changes, I now need to check another value to determine it's status, and then perform an action after that. The problem is that I have ~0 experience with JS and I have no way of debugging my code other than deploying, changing the value in my database, and looking at the console log.
Is there any way to look up another value in the database and read it? How about look up a value and then set a value for it? Here is the code:
exports.determineCompletion =
functions.database.ref('/Jobs/{pushId}/client_job_complete')
.onWrite(event => {
const status = event.data.val();
const other = functions.database.ref('/Jobs/' + event.params.pushId + '/other_job_complete');
console.log('Status', status, other);
if(status == true && **other.getValueSomehow** == true) {
return **setAnotherValue**;
}
});
This code partially works, it successfully gets the value associated with client_job_complete and stores it in status. But how do I get the other value?
Additionally, if anyone has any JS or firebase documentation that they think would help me, please share! I have read a bunch on firebase here : https://firebase.google.com/docs/functions/database-events but it only talks about events and is very brief
Thank you for your help!
When writing a database trigger function, the event contains two properties that are references to the location of the data that changed:
event.data.ref
event.data.adminRef
ref is limited to the permissions of the user who triggered the function. adminRef has full access to the database.
Each of those Reference objects has a root property which gives you a reference to the root of your database. You can use that reference to build a path to a reference in another part of your database, and read it with the once() method.
You can also use the Firebase admin SDK.
There are lots of code samples that you should probably look at as well.
I'm maybe a bit late, but I hope my solution can help some people:
exports.processJob = functions.database.ref('/Jobs/{pushId}/client_job_complete').onWrite(event => {
const status = event.data.val();
return admin.database().ref('Jobs/' + event.params.pushId + '/other_job_complete').once('value').then((snap) => {
const other = snap.val();
console.log('Status', status, other);
/** do something with your data here, for example increase its value by 5 */
other = (other + 5);
/** when finished with processing your data, return the value to the {{ admin.database().ref(); }} request */
return snap.ref.set(other).catch((error) => {
return console.error(error);
});
});
});
But take notice of your firebase database rules.
If no user should have access to write Jobs/pushId/other_job_complete, except Your cloud function admin, You need to initialize Your cloud function admin with a recognizable, unique uid.
For example:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const adminCredentials = require('path/to/admin/credentials.json');
admin.initializeApp({
credential: admin.credential.cert(adminCredentials),
databaseURL: "https://your-database-url-com",
databaseAuthVariableOverride: {
uid: 'super-special-unique-firebase-admin-uid'
}
});
Then Your firebase database rule should look something like this:
"client_job_complete": {
".read": "auth !== null",
".write": "auth.uid === 'super-special-unique-firebase-admin-uid'"
}
Hope it helps!
You have to wait on the promise from a once() on the new ref, something like:
exports.processJob = functions.database.ref('/Jobs/{pushId}/client_job_complete')
.onWrite(event => {
const status = event.data.val();
const ref = event.data.adminRef.root.child('Jobs/'+event.params.pushId+'/other_job_complete');
ref.once('value').then(function(snap){
const other = snap.val();
console.log('Status', status, other);
if(status && other) {
return other;
}
});
});
Edit to fix the error that #Doug Stevenson noticed (I did say "something like")

Conditional statement to check if user exists in JSON file (or mongoDB)

Background
The JSON file is being generated from a mongoDB with mongoose ODM. However it difficult for me to add a mongoose query to the if statement as I get alot of errors.
The JSON Approach
Using this approach, I cache the mongoDB collection to a JSON file, the attempt to read off the ids.
I have a mods.json file which looks like this (Array Of Objects):
[
{
"userid": "58258161"
},
{
"userid": "135207785"
},
{
"userid": "268339963"
},
{
"userid": "210152609"
}
]
The JSON file above contains a list of users who are allowed to execute a certain Telegram Bot command.
var config = require('./config.json');
var mods = require('./mods.json');
bot.onText(/\/test/i, function (msg) {
var fromId = msg.from.id;
var chatId = msg.chat.id;
for (index in mods) {
if (fromId == config.owner || mods[index].userid) {
console.log('You Are A Mod!');
} else {
console.log('Not A Mod');
bot.sendMessage(chatId, 'You are not a mod');
}
}
});
A brief explanation of how the above is supposed to work:
When the bot receives a "/test" command it is supposed to check if the message is from the owner whose id is stored in the config.json file or any moderator whose unique id is stored in the mods.json file.
The problem
The following code checks through every single id to confirm whether the message is from a mod. And subsequently spits out multiple messages of 'You are not a mod'. Which would be the best method to confirm the user is a mod in the conditional statement 'if'.
The Mongo Approach
When querying with moongose a callback is required, I dont know exactly how to create a if conditional in such a scenario. However this method would obviously be better in such a situation.
You could use Array.some instead, to check if the ID is in the array, that way the message won't be outputted on every iteration, only when it's done checking
var isMod = fromId == config.owner || mods.some(x => fromId == x.userid);
if ( isMod ) {
console.log('You Are A Mod!');
} else {
console.log('Not A Mod');
bot.sendMessage(chatId, 'You are not a mod');
}
Preferably you'd construct a query to ask the database instead, something like
model.find({'mods.userid': config.owner}, function (err, docs) {...

Categories