Firebase functions how to get an array of values from Firebase Database - node.js

I want to obtain an array of all the users of my database in the second function that I use ""return admin.database().ref("/Usuarios").once('value')
" so that we can send a notification to all these users.How could all users get in the second function? Thank you
I have this code:
let functions = require('firebase-functions');
let admin = require('firebase-admin');
admin.initializeApp();
exports.sendNotificationNewAd = functions.database.ref('/Alertas/{notiId}').onWrite((change, context) => {
// Only edit data when it is first created.
if (change.before.exists()) {
return null;
}
// Exit when the
data is deleted.
if (!change.after.exists()) {
return null;
}
//te escribe el json de el mensaje nuevo
const afterData = change.after.val();
console.log("afterData: ", afterData);
//get lat and lng of Ad
const name = afterData.name;
console.log("Name: "+name);
//get lat and lng of Ad
const lat = afterData.lat;
const lng = afterData.lng;
console.log("Lat y Lng", "lat: "+lat+" lng: "+lng);
//get lat and lng of Ad
const adType = afterData.typeAd;
console.log("Tipo: "+adType);
//get the user id of the ad
const notiId = context.params.notiId;
console.log("notiId: ", notiId);
const userId = afterData.userId;
console.log("userId: ", userId);
return admin.database().ref("/Usuarios").once('value')
.then(snap => {
const userName = snap.child("name").val();
return console.log("userName: ", userName);
});
});

It looks like this is what you are asking:
exports.sendNotificationNewAd =
functions.database.ref('/Alertas/{notiId}')
.onCreate((noti_snap, context) => {
//te escribe el json de el mensaje nuevo
const notif = noti_snap.val();
console.log("notif: ", notif);
//get lat and lng of Ad
const name = notif.name;
console.log("Name: " + name);
//get lat and lng of Ad
const lat = notif.lat;
const lng = notif.lng;
console.log("Lat y Lng", "lat: " + lat + " lng: " + lng);
//get lat and lng of Ad
const adType = notif.typeAd;
console.log("Tipo: " + adType);
//get the user id of the ad
const notiId = context.params.notiId;
console.log("notiId: ", notiId);
const userId = notif.userId;
console.log("userId: ", userId);
return admin.database().ref("/Usuarios").once('value')
.then(snap => {
let children = [];
snap.forEach(child_snap => {
children.push(child_snap.val()); // build children
});
return children;
})
.then(children => {
children.map(child => {
let message = {
notification: {
title: "message",
body: "body"
},
token: child.device_token
}
admin.messaging().send(message).catch(console.log);
});
return null;
})
.then( () => {
return notif.ref.remove(); // consume send request
})
.catch(console.log);
});

Related

Getting document not a function

I am attempting to retrieve the boolean child (notificationsOn) of an object stored as a Firestore document to see if the rest of a function should be executed.
The overall function works to completion without this portion, but adding the portion from let threadDoc to the if statement presents a "threadDoc.get is not a function" error. I think my syntax is wrong but I don't know how, as a similar function works in a later part of the function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendDMNotification =functions.firestore.document('/dm_threads/{thread_id}/messages/{message_id}').onCreate((snapshot, context) => {
const newMessage = snapshot.data();
const senderName = newMessage.authorName;
const senderID = newMessage.authorUID;
const messageText = newMessage.message;
const recipientID = newMessage.recipientUID;
var notificationsOn = null;
let deviceTokenQuery = admin.firestore().collection(`/users/${recipientID}/device_tokens/`);
var idsToBeSorted = [senderID, recipientID];
idsToBeSorted.sort();
var threadID = idsToBeSorted[0] + idsToBeSorted[1];
console.log(recipientID);
console.log(threadID);
let threadDoc = admin.firestore().document(`users/${recipientID}/threads/${threadID}/`);
return threadDoc.get().then(doc => {
let notificationsOn = doc.data.notificationsOn;
console.log(notificationsOn);
if (notificationsOn !== false){
return deviceTokenQuery.get().then(querySnapshot => {
let tokenShapshot = querySnapshot.docs;
const notificationPromises = tokenShapshot.map(doc => {
let token_id = doc.data().tokenID;
const payload = {
data: {
title: senderName,
body: messageText,
senderID: senderID,
senderName: senderName
}
};
return admin.messaging().sendToDevice(token_id, payload).then(response => {
console.log("Notification sent: ", response);
})
.catch(error => {
console.log("Error sending message: ", error);
});
});
return Promise.all(notificationPromises);
});
}
return;
});
});
admin.firestore().document() was supposed to be admin.firestore().collection(...).doc(...)
This fixed my problem
I think you meant to say admin.firestore() instead of functions.firestore.

Why might this Cloud Function not be triggering a notification?

I am writing a Cloud Function in Firebase that sends a notification to devices with tokens that are stored as documents in a collection:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendDMNotification = functions.firestore.document('/dm_threads/{thread_id}/messages/{message_id}')
.onCreate((snapshot, context) => {
const newMessage = snapshot.data();
const senderName = newMessage.authorName;
const senderID = newMessage.authorUID;
const messageText = newMessage.message;
const recipientName = newMessage.recipientName;
const recipientID = newMessage.recipientUID;
const timestamp = newMessage.timestamp;
console.log(senderName + " " + senderID + " " + messageText + " " + recipientName + " " + recipientID + " " + timestamp.toString());
let deviceTokenQuery = admin.firestore().collection(`/Users/${recipientID}/device_tokens/`);
return deviceTokenQuery.get().then(querySnapshot => {
console.log('deviceTokenQuery returned');
let tokens = querySnapshot.docs;
console.log('1');
const notificationPromises = tokens.map(token => {
console.log('2');
let token_id = token.tokenID.toString();
console.log('3');
const payload = {
notification: {
title: senderName,
body: messageText,
icon: "default"
}
};
return admin.messaging().sendToDevice(token_id, payload)
});
console.log('4');
return Promise.all(notificationPromises);
});
});
Only "deviceTokenQuery returned", "1", and "4" log statements appear. Do I have a syntax problem or am I not using these these methods properly?

How do I test an agenda job with jest?

I want to test the jobs I'm running with agenda. I have written the following test but then I realized that all the expect()... functions are not getting called before it determines that the test had passed or failed. I put some console.log() by all the expect() functions and they do get called just not before it determines if the tests result. I tried adding expect.assertions(6); at the top of the test but it didn't make the test wait longer, it just failed saying it didn't find all 6.
How do I make jest wait for the whole test to finish?
This is the code I want to test:
rules.js
const Account = require("./../models/user.account");
const Listing = require("./../models/user.account.listing");
const Message = require("./../models/user.account.listing.message");
const MessageRule = require("./../models/user.account.listing.messageRule");
const Reservation = require("./../models/user.account.listing.reservation");
const Core = require("./../core");
const moment = require("moment-timezone");
require("colors");
module.exports = function(agenda) {
agenda.define("send message", {priority: "highest"}, async (job, done) => {
try {
console.time("send message " + job.attrs._id);
const userID = job.attrs.data.userID;
const listingID = job.attrs.data.listingID;
const reservationID = job.attrs.data.reservationID;
const messageRuleID = job.attrs.data.messageRuleID;
const sendDate = job.attrs.data.sendDate;
//console.log("Rule Job:".cyan + " send message - listingID: " + listingID);
let messageQuery = {
userID,
listingID,
reservationID,
messageRuleID
};
let message = await Message.findOne(messageQuery);
if (message) {
throw new Error(
"Message has already been sent. userID: " +
userID +
" listingID: " +
listingID +
" reservationID: " +
reservationID +
" messageRuleID: " +
messageRuleID +
" message._id: " +
message._id +
" message.sendDate: " +
message.sendDate +
" message.sentDate: " +
message.sentDate
);
} else {
const isLastMinuteMessage = false;
let listing = await Listing.findById(listingID);
let account = await Account.findById(listing.accountID);
let messageRule = await MessageRule.findById(messageRuleID);
let reservation = await Reservation.findById(reservationID);
// Check that it found all of the required documents
if (!listing || !listing._id) {
throw new Error("Missing the listing document. userID: " + userID + " listingID: " + listingID);
}
if (!account || !account._id) {
throw new Error(
"Missing the account document. userID: " + userID + " accountID: " + listing.accountID
);
}
if (!messageRule || !messageRule._id) {
throw new Error(
"Missing the messageRule document. userID: " + userID + " messageRuleID: " + messageRuleID
);
}
if (!reservation || !reservation._id) {
throw new Error(
"Missing the reservation document. userID: " + userID + " reservationID: " + reservationID
);
}
// Double check the send date by recalculating it then checking comparing them
if (messageRule.event == "checkin" || messageRule.event == "checkout") {
let sendDateCheck = moment.tz(
reservation.startDate + " " + messageRule.time,
"YYYY-MM-DD H",
listing.timeZone
);
if (messageRule.event == "checkout") {
sendDateCheck = sendDateCheck.add(reservation.nights, "day");
}
sendDateCheck = sendDateCheck.add(messageRule.days, "day");
if (!sendDateCheck.isSame(sendDate)) {
throw new Error(
"Message send date and calculated send date don't match. userID: " +
userID +
" listingID: " +
listingID +
" reservationID: " +
reservationID +
" messageRuleID: " +
messageRuleID +
" sendDate: " +
moment(sendDate).format() +
" sendDateCheck: " +
moment(sendDateCheck).format()
);
}
}
await Core.buildMessage(account, listing, messageRule, reservation, isLastMinuteMessage);
}
console.timeEnd("send message " + job.attrs._id);
done();
} catch (error) {
console.error("Rule Job: send message", error);
return done(error);
}
});
};
And here is my test:
rules.test.js
const Account = require("./../models/user.account");
const Listing = require("./../models/user.account.listing");
const Message = require("./../models/user.account.listing.message");
const MessageRule = require("./../models/user.account.listing.messageRule");
const Reservation = require("./../models/user.account.listing.reservation");
const Core = require("./../core");
const Rules = require("./rules");
const Docs = require("./../tests/docs");
describe("Tests for rules.js", () => {
// Mock moment()
Date.now = jest.fn(() => 1538794832371); //October 5th 2018 8pm PST
beforeEach(() => {
jest.clearAllMocks();
});
describe("rules()", () => {
it("should send a message, message exists but is enabled", async () => {
// Mock Message
// Message.findOne()
const MessageFindOneMock = jest.spyOn(Message, "findOne");
const MessageFindOneResult = Docs.messageReviewReminderDisabledThenEnabled;
const MessageFindOne = jest.fn(() => MessageFindOneResult);
MessageFindOneMock.mockImplementation(MessageFindOne);
const userID = Docs.userID;
const reservation = Docs.reservationInTheFuture;
const messageRule = Docs.messageRuleCheckUp;
const accountID = Docs.listing.accountID;
const listingID = Docs.listing._id;
const reservationID = reservation._id;
const messageRuleID = messageRule._id;
// Mock Listing
// Listing.findById()
const ListingFindByIdMock = jest.spyOn(Listing, "findById");
const ListingFindByIdResult = Docs.listing;
const ListingFindById = jest.fn(() => ListingFindByIdResult);
ListingFindByIdMock.mockImplementation(ListingFindById);
// Mock Account
// Account.findById()
const AccountFindByIdMock = jest.spyOn(Account, "findById");
const AccountFindByIdResult = {_id: accountID};
const AccountFindById = jest.fn(() => AccountFindByIdResult);
AccountFindByIdMock.mockImplementation(AccountFindById);
// Mock MessageRule
// MessageRule.findById()
const MessageRuleFindByIdMock = jest.spyOn(MessageRule, "findById");
const MessageRuleFindByIdResult = messageRule;
const MessageRuleFindById = jest.fn(() => MessageRuleFindByIdResult);
MessageRuleFindByIdMock.mockImplementation(MessageRuleFindById);
// Mock Reservation
// Reservation.findById()
const ReservationFindByIdMock = jest.spyOn(Reservation, "findById");
const ReservationFindByIdResult = reservation;
const ReservationFindById = jest.fn(() => ReservationFindByIdResult);
ReservationFindByIdMock.mockImplementation(ReservationFindById);
// Mock Core
// Core.buildMessage()
const CoreBuildMessageMock = jest.spyOn(Core, "buildMessage");
const CoreBuildMessage = jest.fn((account, listing, messageRule, reservation, isLastMinuteMessage) => {
expect(account._id).toBe(accountID);
expect(listing._id).toBe(listingID);
expect(messageRule._id).toBe(messageRuleID);
expect(reservation._id).toBe(reservationID);
});
CoreBuildMessageMock.mockImplementation(CoreBuildMessage);
// Run test
const sendDate = "2018-11-08T10:00:00-06:00";
const done = jest.fn(error => {
expect(error).toBeFalsy();
expect(CoreBuildMessage).toHaveBeenCalledTimes(10);
});
const job = {
attrs: {
data: {
userID,
listingID,
reservationID,
messageRuleID,
sendDate
}
}
};
let agenda = {
define: jest.fn((name, options, callback) => {
if (name == "send message") {
callback(job, done);
}
})
};
Rules(agenda);
});
});
});
In your rules.js you have defined your callback as an async function:
agenda.define("send message", {priority: "highest"}, async (job, done) // <--- this
However you are calling it with:
define: jest.fn((name, options, callback) => {
if (name == "send message") {
callback(job, done);
}
})
So there is no await there ...
Which explains why you get stuff running but not awaiting for it to finish.
Should be something like:
define: await jest.fn((name, options, callback) => {
if (name == "send message") {
await callback(job, done);
}
})

firebase functions datasnapshot is not working

I am trying to run the next code in firebase functions, and it gives an error of not recognizing datasnapshot. what can cause it?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.updateVotes = functions.database.ref('travels/{travelId}/locations/{locationId}/voting/users')
.onUpdate((userVotes, context) => {
var travelId = context.params.travelId;
var locationId = context.params.locationId
var sumVotes = 0;
userVotes.forEach((userVote) => {
sumVotes += userVote.val().userVote;
})
// var votes = userVotes.val();
// for (var vote in votes) {
// if (votes.hasOwnProperty(vote)) {
// sumVotes += votes[vote].userVote;
// }
// }
return admin.database()
.ref('travels/' + travelId + '/locations/' + locationId + '/voting')
.update({ votes: sumVotes })
})
The error is:
TypeError: userVotes.forEach is not a function at exports.updateVotes.functions.database.ref.onUpdate
onUpdate doesn't return a DataSnapshot it returns a Change object.
You need to select the after or before property - do you want the information before the write, or after the write (Usually it's after - this is the new data in Firebase).
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.updateVotes = functions.database.ref('travels/{travelId}/locations/{locationId}/voting/users')
.onUpdate((change, context) => {
var travelId = context.params.travelId;
var locationId = context.params.locationId
var sumVotes = 0;
var userVotes = change.after;
userVotes.forEach((userVote) => {
sumVotes += userVote.val().userVote;
})
return admin.database()
.ref('travels/' + travelId + '/locations/' + locationId + '/voting')
.update({ votes: sumVotes })
})

Getting undefined error when printing a variable

I am retrieving data from the firestore to get the data. I store the data into a variable. I need to use that variable in another part of the code. when I use that it shows me an undefined code. I retrieved the user details and sending the notification . In datapayload I need to pass the retrieved field . But it shows an error undefined
cloud functions
const functions = require('firebase-functions');
const Firestore = require('#google-cloud/firestore');
const firestore = new Firestore();
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
exports.splitting = functions.firestore
.document('deyaPayUsers/{authid}/Split/{authid1}/SentInvitations/{autoid}')// Here the path is is triggereing
.onWrite(event =>{
var id = event.params.authid;//here we get the authid of the user who triggeres.
var dbref = db.collection('deyaPayUsers').doc(id);
var sendername ;
var getDoc = dbref.get()
.then(doc =>{
if(!doc.exists){
console.log("No Such Document");
}else{
console.log('Document data of the firstname', doc.data().FirstName);
sendername = doc.data().FirstName;
}
console.log("sent"+sendername);
})
.catch(err => {
console.log("error getting document",err);
});
console.log(id);
var id1 = event.params.authid1;
var splitid = event.params.autoid;
console.log("laha"+sendername);
var document = event.data.data();
var phoneNumber = [];
//In loop
for(var k in document){ // this line says about looping the document to get the phonenumber field in all the invites
phoneNumber.push(document[k].PhoneNumber);// All the phonenumbers are stored in the phoneNumber array.
}
console.log("The json data is " +phoneNumber);
var ph1 = document.Invite1.PhoneNumber;//It gets the single user phoneumber in Invite1
var amount = document.Invite1.Amount;//It gets the amount of the single user in invite1 in integer
var a = amount.toString();// It is used to convert the Integer amount into String
console.log(a);
console.log(document);
console.log(ph1);
var deyaPay = db.collection("deyaPayUsers");// This path is user profile path to query with phonenumber
for(var k in document){ // here It is the loop in a document to get the phonenumber
var p = document[k].PhoneNumber;// here we get the phonenumber for every loop in invite and stored in p variable
var am = document[k].Amount;// we get the amount of that invite
var ams = am.toString();// converting integer amount into string
console.log("AMount of the user"+ams);
let phQuery = deyaPay.where('PhoneNumber','==',p)// This line says that checking the user proile PhoneNumber field and the document phonenumber which is stored in p variable.
.get()
.then(snapshot => {
snapshot.forEach(doc=>{ // It loops all the documnets whether the PhoneNumbers are matching with user profiles phonenumber
console.log(doc.id, " => ", doc.data());// If matches it prints the doc id and the user data
var userData = doc.data();//Here we get the doc data of that matched phonenumber
var userId = doc.id;// here it we get the id of the Matched profile
var FirstName = userData.FirstName;
console.log(FirstName);
var LastName = userData.LastName;
console.log(FirstName);
var FullName = FirstName + LastName;
console.log(FullName);
var Token = userData.FCMToken; // we need to take that fcm token to send the notification
console.log(userId);
console.log("FCM Token for that phoneNumber" + Token);
console.log(userData.PhoneNumber);
console.log(ph1 + "Exist In DB");
var msg = FirstName + " "+ "requested you to pay $" +ams;
console.log("total notification is" +msg);
let payload = { //This is for sending notification message
notification: {
title: "Message",
body: msg,
sound: "default",
},
'data':{// these is the data it calls in the messagereceived method
'Name':sendername,
'Amount':ams
}
};
console.log(payload);
return admin.messaging().sendToDevice(Token, payload).then((response)=> { // This method is used for returning the notification to a specific device
console.log(Token);
console.info("Successfully sent notification")
}).catch(function(error) {
console.warn("Error sending notification " , error)
});
});
}) .catch(err => {
console.log('Error getting documents', err);
});
}
});
try this, Problem is whenever you perform any async operation in node.js, it does not wait for it to complete execute next line.
const functions = require('firebase-functions');
const Firestore = require('#google-cloud/firestore');
const firestore = new Firestore();
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
exports.splitting = functions.firestore
.document('deyaPayUsers/{authid}/Split/{authid1}/SentInvitations/{autoid}') // Here the path is is triggereing
.onWrite(async (event) => {
try {
const responses = [];
var id = event.params.authid; //here we get the authid of the user who triggeres.
var dbref = db.collection('deyaPayUsers').doc(id);
var sendername;
var doc = await dbref.get();
if (!doc.exists) {
console.log("No Such Document");
} else {
console.log('Document data of the firstname', doc.data().FirstName);
sendername = doc.data().FirstName;
}
console.log(id);
var id1 = event.params.authid1;
var splitid = event.params.autoid;
console.log("laha" + sendername);
var document = event.data.data();
var phoneNumber = [];
//In loop
for (var k in document) { // this line says about looping the document to get the phonenumber field in all the invites
phoneNumber.push(document[k].PhoneNumber); // All the phonenumbers are stored in the phoneNumber array.
}
console.log("The json data is " + phoneNumber);
var ph1 = document.Invite1.PhoneNumber; //It gets the single user phoneumber in Invite1
var amount = document.Invite1.Amount; //It gets the amount of the single user in invite1 in integer
var a = amount.toString(); // It is used to convert the Integer amount into String
console.log(a);
console.log(document);
console.log(ph1);
var deyaPay = db.collection("deyaPayUsers"); // This path is user profile path to query with phonenumber
for (var k in document) { // here It is the loop in a document to get the phonenumber
var p = document[k].PhoneNumber; // here we get the phonenumber for every loop in invite and stored in p variable
var am = document[k].Amount; // we get the amount of that invite
var ams = am.toString(); // converting integer amount into string
console.log("AMount of the user" + ams);
let snapshot = await deyaPay.where('PhoneNumber', '==', p) // This line says that checking the user proile PhoneNumber field and the document phonenumber which is stored in p variable.
.get();
snapshot.forEach(doc => { // It loops all the documnets whether the PhoneNumbers are matching with user profiles phonenumber
console.log(doc.id, " => ", doc.data()); // If matches it prints the doc id and the user data
var userData = doc.data(); //Here we get the doc data of that matched phonenumber
var userId = doc.id; // here it we get the id of the Matched profile
var FirstName = userData.FirstName;
console.log(FirstName);
var LastName = userData.LastName;
console.log(FirstName);
var FullName = FirstName + LastName;
console.log(FullName);
var Token = userData.FCMToken; // we need to take that fcm token to send the notification
console.log(userId);
console.log("FCM Token for that phoneNumber" + Token);
console.log(userData.PhoneNumber);
console.log(ph1 + "Exist In DB");
var msg = FirstName + " " + "requested you to pay $" + ams;
console.log("total notification is" + msg);
let payload = { //This is for sending notification message
notification: {
title: "Message",
body: msg,
sound: "default",
},
'data': { // these is the data it calls in the messagereceived method
'Name': sendername,
'Amount': ams
}
};
console.log(payload);
const response = await admin.messaging().sendToDevice(Token, payload)
console.info("Successfully sent notification")
responses.push(response);
});
};
return responses; //here
} catch (error) {
console.info("error", error)
}
});

Resources