How to trigger firestore cloud functions on update of field value? - android

I want to send email to user on update of status field of user to value (true) using firestore and cloud functions.
My case :
I have a users(collections) which has more documents of userId(documents) and each document contains field and value.
One of the field is status: true | false (boolean).
I want to send email to that user if status of that user change to true using cloud functions and sendgrid api.
users
- dOpjjsjssdsk2121j131
- id : dOpjjsjssdsk2121j131
- status : false
- pspjjsjssdsdsk2121j131
- id : pspjjsjssdsdsk2121j131
- status : false
- yspjjsjssdsdsk2121j131
- id : yspjjsjssdsdsk2121j131
- status : false
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const SENDGRID_API_KEY = functions.config().sendgrid.key;
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(SENDGRID_API_KEY);
exports.sendEmail = functions.firestore.document('users/{userId}')
.onUpdate((change, context) => {
const after = change.after.val();
if(after.status === 'VERIFIED'){
console.log('profile verified')
const db = admin.firestore();
return db.collection('users').doc(userId)
.get()
.then(doc => {
const user = doc.data();
const msg = {
to: 'email',
from: 'email',
templateId: 'template id',
dynamic_template_data: {
subject: 'Profile verified',
name: 'name',
},
};
return sgMail.send(msg)
})
.then(() => console.log('email sent!') )
.catch(err => console.log(err) )
}
});

You are getting your error because in Firestore where is no val().
Try to change your change.after.val() to change.after.data(), so your code looks like this:
exports.sendEmail = functions.firestore.document('users/{userId}')
.onUpdate((change, context) => {
const after = change.after.data();
if(after.status === 'VERIFIED'){
console.log('profile verified')
const db = admin.firestore();
return db.collection('users').doc(context.params.userId) // get userId
.get()
.then(doc => {
const user = doc.data();
const msg = {
to: 'email',
from: 'email',
templateId: 'template id',
dynamic_template_data: {
subject: 'Profile verified',
name: 'name',
},
};
return sgMail.send(msg)
})
.then(() => console.log('email sent!') )
.catch(err => console.log(err) )
}
});

you can compare the target field by before and after data, if your target field is not the same. It means the field value has changed.
const beforedata=change.before.data()
const afterdata=change.after.data()
if(afterdata.status == beforedata.status){
// Status not changed
}else{
//status changed
}

All correct for the above answer except "context.params." is missing from (staffId)
Currently:
return db.collection('users').doc(userId) // get userId
Should Be:
> return db.collection('users').doc(context.params.userId) // get userId
This will also answer the above comment:
how to get the userId before return.db.collection('users').doc(userId) . ? – Fortray Sep 6 '19 at 9:13

Related

Can't loop over a map from snapshot retrieved in Firebase Cloud Function

I'm lost with cloud funtions.
I'm trying to read from a table when new entry is written. Then parse some data from this table, and create a new object in a new table.
But I'm lost in some concepts with CF, and node...
At this moment I have the listeners to the table:
exports.createEventInvitation = functions.database
.ref('event/{eventStartDate}/{eventId}/')
.onWrite((change, context) => {
const event = change.after.val();
const eventUsersList = event.userList;
...
})
The object event looks like this:
Event :{ conversationId: '8f6eb2b9-0cbb-4135-b6b6-c9f02c9aa91e',
sharifyEventData:
{ acceptationManual: false,
address: 'ADDRESS FROM USER',
adminProfileImageUrl: 'url_image',
description: 'Description',
eventEmoji: '💬',
finalDate: '2019-11-12',
finalHour: '09:30',
headerImageUrl: '',
initialDate: '2019-11-12',
initialHour: '09:00',
eventType: 'BEER',
title: ''},
eventID: '5f49ff65-bd98-45cb-a554-55da5c3c2f16',
userList: { LeiUlbDlNKWwF6QPgnQiFTE03gt2: true },
userType: 'NORMAL' }
I have to loop over the userlist to check which ID is the owner of the event. But I'm not able to loop it properly.
I tried to get the value like:
const userList = new Map()
const eventUsersList = event.userList
for( var entry in eventUsersList){
if(entry[1]){
userList.set(entry[0],3)
}
}
console.log('UserListInvitation:' + util.inspect(userList, {showHidden: false, depth: null}))
And I achieved something like:
UserListInvitation:Map { 'L' => 3 }
But I'm missing something, because it seems I'm only taken the first letter of the key.
Also I tried:
for( var entryKey in eventUsersList.keys){
if(eventUsersList[entryKey]){
userList.set(entryKey,3)
}
}
And this It's returning nothing
What I'm missing?
If I understood you properly then the following code should work:
const userList = new Map()
const eventUsersList = event.userList
for( var key in eventUsersList){
if(eventUsersList[key]){ // Safety check
userList.set(key, 3) // key-value?
}
}

How read document property inside Firestore Trigger

I am trying to send notifications to an Android app's users with firebase cloud messaging. I am using cloud firestore triggers but when trying to access a user node's properties, they are undefined.
Here is my index.js :
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.notifyNewMessage = functions.firestore
.document('conversations/{conversation}/messages/{message}')
.onCreate((docSnapshot, context) => {
const message = docSnapshot.data();
const recipientId = message['recipientId'];
const senderName = message['senderName'];
return admin.firestore().collection("users").doc(recipientId).get().then(userDoc => {
const registrationTokens = userDoc.registrationTokens;
console.log("registrationTokens = "+ registrationTokens);
const notificationBody = message['message'];
const payload = {
notification : {
title : senderName + " sent you a message,",
body: notificationBody,
clickAction: "ConversationActivity"
},
data : {
contactName : senderName,
userId : message['senderId']
}
}
return admin.messaging().sendToDevice(registrationTokens, payload).then(response => {
const stillRegisteredTokens = registrationTokens;
response.results.forEach((result, index) => {
const error = result.error;
if (error){
const failedRegistrationToken = stillRegisteredTokens['index'];
if (error.code === 'messaging/invalid-registration-token'
|| error.code === 'messaging/registration-token-not-registered') {
const failedIndex = stillRegisteredTokens.indexOf(failedRegistrationToken)
if (failedIndex > -1) {
stillRegisteredTokens.splice(failedIndex, 1);
}
}
}
})
return admin.firestore().doc("users/" + recipientId).update({
registrationTokens: stillRegisteredTokens
})
})
})
})
Because of that I get an error "sendDevice() argument must be non-empty array or non null string"
UPDATE
registrationTokens were undefined because I called userDoc instead of userDoc.data()
Now registrationTokens is not null nor empty but I still get the error :
Registration token(s) provided to sendToDevice() must be a non-empty string or a non-empty array.

Firebase Functions - ReferenceError: event is not defined

I'm trying to do a simple notification function
but every time i fire the function i get ERROR
TypeError: Cannot read property 'params' of undefined
at exports.sendNotification.functions.database.ref.onWrite (/user_code/index.js:23:32)
at cloudFunctionNewSignature (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:105:23)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:135:20)
at /var/tmp/worker/worker.js:758:24
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
Or i get the error
TypeError: Cannot read property 'user_id' of undefined
I got an answer from here
but i't didn't help at all , and i can't find the problem . this is my code
'use strict'
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendNotification = functions.database.ref('/notifications/{user_id}/{notification_id}').onWrite((change,context) => {
const user_id = event.params.user_id;
const notification_id = event.params.notification_id;
console.log('We have a notification from : ', user_id);
if(!context.data.val()){
return console.log('A Notification has been deleted from the database : ',
notification_id);
}
const fromUser =admin.database().ref(`/notifications/${user_id}/${notification_id}`).once('value');
return fromUser.then(fromUserResult => {
const from_user_id = fromUserResult.val().from;
console.log('You have new notification from : ', from_user_id);
const userQuery = admin.database().ref(`Users/${from_user_id}/name`).once('value');
const deviceToken = admin.database().ref(`/Users/${user_id}/device_token`).once('value');
return Promise.all([userQuery, deviceToken]).then(result => {
const userName = result[0].val();
const token_id = result[1].val();
const payload = {
notification: {
title : "New Friend Request",
body: `${userName} has sent you request`,
icon: "default",
click_action : "none"
},
data : {
from_user_id : from_user_id
}
};
return admin.messaging().sendToDevice(token_id, payload).then(response => {
return console.log('This was the notification Feature');
});
});
});
});
You never defined event, so it's undefined. You're trying to access a property on an undefined variable. Did you mean context.params.user_id?
(You're probably using an out of date tutorial. I see bad "sendNotification" functions like this all the time on Stack Overflow. Let the author know.)

How to call firebase cloud function to verify a purchase

I've been thinking of using Google API directly to reward users (update user data on firestore) after verifying purchase through:
GET https://www.googleapis.com/androidpublisher/v3/applications/packageName/purchases/products/productId/tokens/token
But the authorization step is somewhat tricky. How do I achieve it with this Cloud Function I have got from one of you:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {google} = require("googleapis");
const publisher = google.androidpublisher('v2');
const authClient = new google.auth.JWT({
email: 'Service Account Email',
key: 'BEGIN PRIVATE KEY*************END PRIVATE KEY',
scopes: ['https://www.googleapis.com/auth/androidpublisher']
});
admin.initializeApp();
exports.validatePurchases = functions.database
.ref('/purchases/{uId}/{orderId}')
.onCreate((event, context) => {
const purchase = event.val();
if (purchase.is_processed === true) {
console.log('Purchase already processed!, exiting');
return null;
}
const orderId = context.params.orderId;
const dbRoot = event.ref.root;
const package_name = purchase.package_name;
const sku = purchase.sku;
const my_token = purchase.token;
authClient.authorize((err, result) => {
if (err) {
console.log(err);
}
publisher.purchases.products.get({
auth: authClient,
packageName: package_name,
productId: sku,
token: my_token
}, (err, response) => {
if (err) {
console.log(err);
}
// Result Status must be equals to 200 so that the purchase is valid
if (response.status === 200) {
return event.ref.child('is_validated').set(true);
} else {
return event.ref.child('is_validated').set(false);
}
});
});
return null;
});
Please spot errors and identify if it'll work. How do I authorize and deploy the function?
Thanks

How to create firestore transaction where target document needs to be found

I am looking for a way to create a firestore transaction where i find a document from a query and then modify this document in a transaction.
Something along those lines (kotlin):
firestore.runTransaction { transaction ->
val snapshot = transaction.get(db.collection("document")
.whereEqualTo("someField", null)
.orderBy("creationDate", ASCENDING)
.limit(1L))
val myObject = snapshot.toObject(MyObject::class.java)
myObject.someFiled = "123"
transaction.set(snapshot.reference, myObject)
}
The problem here is that the query returned by the .limit(1) method is not a DocumentReference, which is the only type the transaction accepts. Therefore my question is, how can such a transaction be achieved in java/kotlin?
I have seen something similar in this blog post using the admin sdk:
return trs.get(db.collection('rooms')
.where('full', '==', false)
.where('size', '==', size)
.limit(1));
After investigation looks like you can't do that in Kotlin/Java it is not supported. You will have to create cloud function and do something similar to:
exports.executeTransaction = functions.https.onRequest(async (req, res) => {
const db = admin.firestore();
const query = db
.collection('collection')
.where('name', '==', 'name')
.limit(1);
db.runTransaction(transaction => {
return transaction
.get(query)
.then((querySnapshot) => {
const gameDocSnapshot = querySnapshot.docs[0];
const gameData = gameDocSnapshot.data();
transaction.update(gameDocSnapshot.ref, { name: 'change' });
return gameData;
})
})
.then((gameData) => {
res.send(gameData);
console.log('Transaction successfully committed!', gameData);
})
.catch((error) => {
res.send('Transaction failed:' + error);
console.log('Transaction failed:', error);
});
});
I don't know about java/kotlin but here is how I did it in TypeScript/JavaScript in a Cloud Function.
const beerTapIndex: number = parseInt(req.params.beerTapIndex);
const firestore: FirebaseFirestore.Firestore = admin.firestore();
firestore
.runTransaction((transaction: FirebaseFirestore.Transaction) => {
const query: FirebaseFirestore.Query = firestore
.collection('beerOnTap')
.where('tapIndexOrder', '==', beerTapIndex)
.limit(1);
return transaction
.get(query)
.then((snapshot: FirebaseFirestore.QuerySnapshot) => {
const beerTapDoc: FirebaseFirestore.QueryDocumentSnapshot = snapshot.docs[0];
const beerTapData: FirebaseFirestore.DocumentData = beerTapDoc.data();
const beerTapRef: FirebaseFirestore.DocumentReference = firestore
.collection('beerOnTap')
.doc(beerTapDoc.id);
transaction.update(beerTapRef, {enabled: !beerTapData.enabled});
return beerTapData;
})
})
.then((beerTapData: FirebaseFirestore.DocumentData) => {
console.log('Transaction successfully committed!', beerTapData);
})
.catch((error: Error) => {
console.log('Transaction failed:', error);
});
Plan JavaScript Version
const beerTapIndex = parseInt(req.params.beerTapIndex);
const firestore = admin.firestore();
firestore
.runTransaction((transaction) => {
const query = firestore
.collection('beerOnTap')
.where('tapIndexOrder', '==', beerTapIndex)
.limit(1);
return transaction
.get(query)
.then((snapshot) => {
const beerTapDoc = snapshot.docs[0];
const beerTapData = beerTapDoc.data();
const beerTapRef = firestore
.collection('beerOnTap')
.doc(beerTapDoc.id);
transaction.update(beerTapRef, {enabled: !beerTapData.enabled});
return beerTapData;
})
})
.then((beerTapData) => {
console.log('Transaction successfully committed!', beerTapData);
})
.catch((error) => {
console.log('Transaction failed:', error);
});
Found my answer here: https://medium.com/#feloy/building-a-multi-player-board-game-with-firebase-firestore-functions-part-1-17527c5716c5
Async/Await Version
private async _tapPouringStart(req: express.Request, res: express.Response): Promise<void> {
const beerTapIndex: number = parseInt(req.params.beerTapIndex);
const firestore: FirebaseFirestore.Firestore = admin.firestore();
try {
await firestore.runTransaction(async (transaction: FirebaseFirestore.Transaction) => {
const query: FirebaseFirestore.Query = firestore
.collection('beerOnTap')
.where('tapIndexOrder', '==', beerTapIndex)
.limit(1);
const snapshot: FirebaseFirestore.QuerySnapshot = await transaction.get(query);
const beerTapDoc: FirebaseFirestore.QueryDocumentSnapshot = snapshot.docs[0];
const beerTapData: FirebaseFirestore.DocumentData = beerTapDoc.data();
const beerTapRef: FirebaseFirestore.DocumentReference = firestore
.collection('beerOnTap')
.doc(beerTapDoc.id);
transaction.update(beerTapRef, {enabled: !beerTapData.enabled});
const beerTapModel = new BeerTapModel({
...beerTapData,
tapId: beerTapDoc.id,
});
res.send(beerTapModel);
});
} catch (error) {
res.send(error);
}
}

Categories

Resources