Firebase function: cannot read property 'userId' of undefined - javascript

im trying to send an email through sendgrid via. a firestore trigger. I just cant seem to get the userId out from my context. Any suggestions?
Image link to error message
exports.firestoreEmail = functions.firestore
.document('users/{userId}')
.onCreate((context) => {
const userId = context.params.userId;
const db = admin.firestore();
return db
.collection("users")
.doc(userId)
.get()
.then((doc) => {
const user = doc.data();
const msg = {
to: user.email,
from: "<myEmail>",
subject: "New Follower",
// custom templates
templateId: "d-1584af76f10d475d8cc99d28e5501cf9",
substitutionWrappers: ["{{", "}}"],
substitutions :{
name: user.displayName
}
};
return sgMail.send(msg);
})
.then(() => console.log("email sent!"))
.catch((err) => console.log(err));
});

context should be the second parameter to your function. It doesn't matter that you named it "context" - the position matters entirely. The first argument is a DocumentSnapshot of the new document, so you'll have to give it name as the first parameter, even if you don't use it:
exports.firestoreEmail = functions.firestore
.document('users/{userId}')
.onCreate((snapshot, context) => {
const userId = context.params.userId;

Related

Uncaught Error in snapshot listener:, FirebaseError: [code=permission-denied]: Missing or insufficient permissions

I am trying to let the user only read and write their own data. My rules are as follow(from the docs)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, update, delete: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
}
}
The uid for my user matches my document id but i still get the error:
Uncaught Error in snapshot listener:
FirebaseError: [code=permission-denied]: Missing or
insufficient permissions.
My code for getting uid to document id
const handleSignUp = async () => {
auth
.createUserWithEmailAndPassword(email, password)
.then(async (UserCredentials) => {
const user = UserCredentials.user;
console.log("Registered with: ", user.email);
try {
const uidRef = doc(db, 'users', user.uid);
const docRef = await setDoc(uidRef, {
name: name,
age: age,
currentWeight: currentWeight,
goalWeight: goalWeight,
});
} catch (e) {
console.error("Error adding document: ", e);
}
I am really lost as I have tried many different ways and all docs / answers on here do not work for me. I am guessing the error comes when i call snapshot in this code
const getUser = async() => {
const subscriber = onSnapshot(usersRef, (snapshot) => {
let user = []
snapshot.docs.forEach((doc) => {
user.push({...doc.data(), key: doc.id })
})
setUser(user);
console.log(user);
})
return () => subscriber();
};
I am just unsure as to what is exactly wrong here. Is it my rules? My snapshot?
Given that you get a QuerySnapshot result, I suspect that your code is reading the entire users collection. But as the documentation says rules are not filters, but instead merely ensure that your code only tries to access data that it is permitted to.
So your code should only try to read the document of the currently signed in user.
const getUser = async() => {
if (getAuth().currentUser) {
const uidRef = doc(db, 'users', getAuth().currentUser.uid);
const subscriber = onSnapshot(uidRef, (doc) => {
setUser({...doc.data(), key: doc.id })
})
...
}
};

is there a way to get around using 'await' here?

I am trying to get all the documents in a subcollection by following the firebase documentation, however the error 'await is an reserved identifier' appears.
This is my code currently and I do not see where 'async' could be used with await and the documentation does not indicate that it would be used.
getAuth().onAuthStateChanged((user) => {
if (user) {
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
//reference to the subcollection of subjects in the user's document
const subjectRef = collection(db, "users", auth.currentUser.uid, "subjects");
const querySnapshot = await getDocs(subjectRef);
querySnapshot.forEach((doc) => {
console.log(doc.id, "=>", doc.data());
});
}
});
I have tried getting all the documents with db.collection.('users').document(auth.currentUser.uid).collection('subjects').get() where db = getFirestore(app), however this does not work as the error
'db.collection is not a function' appears and any soloutions I have found to it are not relevant as db is refering firestore not the real time database.
You need to make the callback async:
getAuth().onAuthStateChanged(async (user) => {
if (user) {
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
//reference to the subcollection of subjects in the user's document
const subjectRef = collection(db, "users", auth.currentUser.uid, "subjects");
const querySnapshot = await getDocs(subjectRef);
querySnapshot.forEach((doc) => {
console.log(doc.id, "=>", doc.data());
});
}
});

Cloud Function - onWrite is not triggered when a new document is created

I have a 'Users' collection which contains a list of documents, each document has a user object and a sub-collection 'Notifications'. whenever a user get a new notification, a new document is created under it's sub-collection Notifications.
The trigger in the cloud function is not triggered.
Here is my Firestore structure:
And here is my function:
let functions = require('firebase-functions');
let admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.firestore.collection('Users/{userID}/Notifications/{notificationId}')//
.onWrite(async (change,context) => {
// get receiver ID
const receiverId = context.params.userID;
// get notification object
const notificationObject = change.after.val();
// get sender ID
const senderUid = notificationObject.senderId;
console.log('sending notification to: ' + senderUid);
if (senderUid === receiverId) {
// this should never be called
console.log('sender is the receiver.');
}
// receiver's token
const getTokenPromise = await admin.firestore().collection('Users').doc(receiverId).once('value');
const token = getTokenPromise.val().deviceToken;
// sender user object
const sender = await admin.firestore().collection('Users').doc(senderUid).once('value');
const payload = {
data: {
senderName: sender.val().userName,
senderPhoto: sender.val().userPhoto,
object: JSON.stringify(notificationObject)
}
};
try {
const response = await admin.messaging().sendToDevice(token, payload);
console.log("Successfully sent notification:", response);
}
catch (error) {
console.log("Error sending notification:", error);
}
});
What I'm doing wrong ?
You should declare your function with
exports.sendNotification = functions.firestore.document('Users/{userID}/Notifications/{notificationId}')//
.onWrite(async (change,context) => {...});
and not with
exports.sendNotification = functions.firestore.collection('Users/{userID}/Notifications/{notificationId}')//
.onWrite(async (change,context) => {...});
As a matter of fact, Cloud Functions for Firestore are triggered at the level of the document. More details here and here in the doc.

snap.data is not a function in onUpdate

I have two functions that trigger onCreate and onUpdate however, the {uid} in onUpdate is returning undefined, whilst onCreate returns the {uid}.
How can I get the {uid} to work for onUpdate?
onUpdate.f.js - {uid} is undefined
exports = module.exports = functions.firestore
.document('users/{uid}/alerts/{name}') //UID is the User ID value stored in alerts
.onUpdate(snap => {
const user = snap.data();
console.log(user);
const msg = {
to: user.email,
from: 'notifications#example.com',
templateId: user.template,
dynamic_template_data: {
firstName: user.firstName,
email: user.email,
id: user.uid
}
};
return sgMail.send(msg).catch(err => console.log(`${user.email} - ${err}`));
});
onCreate.f.js - {uid} is correct
exports = module.exports = functions.firestore
.document('users/{uid}/alerts/{name}')
.onCreate(snap => {
const user = snap.data();
console.log(user);
const msg = {
to: user.email,
from: 'notifications#example.com',
templateId: user.template,
dynamic_template_data: {
firstName: user.firstName,
email: user.email,
id: user.uid
}
};
return sgMail.send(msg).catch(err => console.log(`${user.email} - ${err}`));
});
Fields in doc Alerts from frontend
doCreateAlert = (id, email, firstName, lastName, alertType, transactionEmailId) => {
const db = this.firestore;
return db.doc(`users/${id}/alerts/${alertType}`).set({
uid: id,
name: alertType,
email: email,
firstName: firstName,
lastName: lastName,
template: transactionEmailId,
dateCreated: new Date(),
dateModified: new Date()
});
};
The onUpdate is triggered by updating the database with onClick={this.updateAlert} as
updateAlert = () => {
const { firebase, userID } = this.props;
const companyTypeSetup = db.doc(`users/${userID}/alerts/emailVerified`);
companyTypeSetup.update({
dateModified: new Date()
});
};
on the frontend I receive the error of
Uncaught (in promise) Error: No document to update: projects/app/databases/(default)/documents/users/undefined/alerts/emailVerified
and the function is never run. If I manually update the doc in Firestore, I get an error in the firebase functions log as
TypeError: snap.data is not a function
at module.exports.functions.firestore.document.onUpdate.snap (/user_code/lib/auth/onUpdate.f.js:17:23)
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:754:24
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
As the guide shows, onUpdate has two parameters: change and context. You use change since you may want to access the value before the update or after the update. Assuming you want the value after the update, that would look like this:
exports = module.exports = functions.firestore
.document('users/{uid}/alerts/{name}') //UID is the User ID value stored in alerts
.onUpdate((change, context) => {
const user = change.after.data();
console.log(user);
const msg = {
to: user.email,
from: 'notifications#example.com',
templateId: user.template,
dynamic_template_data: {
firstName: user.firstName,
email: user.email,
id: user.uid
}
};
return sgMail.send(msg).catch(err => console.log(`${user.email} - ${err}`));
});
Problem can easily be solved by reading the documents at Handle Event Data. However, if you are like me and skim the documents then the solution is
.onUpdate(change => {
const user = change.after.data();

Get Firebase Database Value into a Cloud Function

I'm currently using Firebase Functions to send automatic push notifications when the database is uploaded. It's working perfectly, I'm just wondering how I can get a specific value from my database, for example PostTitle and display it on, for example title.
In Firebase my database is /post/(postId)/PostTitle
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// database tree
exports.sendPushNotification = functions.database.ref('/posts/{id}').onWrite(event =>{
const payload = {
notification: {
title: 'This is the title.',
body: 'There is a new post available.',
badge: '0',
sound: 'default',
}
};
return admin.database().ref('fcmToken').once('value').then(allToken => {
if (allToken.val()){
const token = Object.keys(allToken.val());
console.log(`token? ${token}`);
return admin.messaging().sendToDevice(token, payload).then(response =>{
return null;
});
}
return null;
});
});
If I understand correctly that you want to get the PostTitle from the node that triggers the Cloud Function, the following should do the trick:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// database tree
exports.sendPushNotification = functions.database.ref('/posts/{id}').onWrite(event =>{
const afterData = event.data.val();
const postTitle = afterData.PostTitle; //You get the value of PostTitle
const payload = {
notification: {
title: postTitle, //You then use this value in your payload
body: 'There is a new post available.',
badge: '0',
sound: 'default',
}
};
return admin.database().ref('fcmToken').once('value').then(allToken => {
if (allToken.val()){
const token = Object.keys(allToken.val());
console.log(`token? ${token}`);
return admin.messaging().sendToDevice(token, payload)
} else {
throw new Error('error message to adapt');
}
})
.catch(err => {
console.error('ERROR:', err);
return false;
});
});
Note the following points:
You are using the old syntax for Cloud Functions, i.e. the one of versions <= v0.9.1. You should migrate to the new version and syntax, as explained here: https://firebase.google.com/docs/functions/beta-v1-diff#realtime-database
I have re-organised your promise chaining and also added a catch() at the end of the chain.
I'd use ...
var postTitle = event.data.child("PostTitle").val;
while possibly checking, it the title even has a value
... before sending out any notifications.

Categories