I'm trying to do a Cloud Function in Firebase. Initially, I read my document. Then I would take two fields "a" and "b", from this document. Then I want to set another field of my doc ('rank') as the sum of 'a' and 'b'. I can't find a solution!
I want to take only the fields "a" and "b" from my document. Save them in variables. And use those variables to do the sum, and set the result in "rank" field.
I tried:
var data = doc.data()
a = data.a
b = data.b
but it doesn't work.
Code:
export const daicazzo = functions.https.onRequest((request,response)=>{
const store = admin.firestore();
//var b;
store.collection('questions').doc('LD92BBDOihAC3fHDyoV').get().then(doc =>{
if(doc.exists){
response.send(doc.data())
}
else{
response.send("Nothing")
}
}).catch(reason => {
console.log(reason)
response.send(reason)
})
store.collection('questions').doc('LD92BBDOihAC3fHDyoV').set({
rank: //a+b
})
.then(function() {
console.log("done");
})
.catch(function(error){
console.log("Error:",error);
});
});
Should do the work (might be some better solutions)
const store = admin.firestore();
export const daicazzo = functions.https.onRequest(async (request,response)=>{
const questionRef = store.doc(`questions/${LD92BBDOihAC3fHDyoV}`)
const doc = await questionRef.get()
const foundDoc = doc.exists
if (foundDoc) {
// getting both key/value pairs from doc object
const {a, b} = doc.data()
const rank = a + b // whatever is your logic...
// saving the rank in the same document
await questionRef.update({rank}) // or questionRef.set({rank}, {merge: true})
if you want to send back the doc with the updated rank without making another read, assuming you handle errors:
const { id } = doc
const updatedDoc = { ...doc.data(), id, rank }
return response.send(updatedDoc)
} else {
return response.send("Nothing")
}
});
Related
How do I get all the comments from the subcollection?
This is mine reusable function to get comments collection.
import { ref, watchEffect } from 'vue';
import { projectFirestore } from '../firebase/config';
const getCollection = (collection, id, subcollection) => {
const comments = ref(null);
const error = ref(null);
// register the firestore collection reference
let collectionRef = projectFirestore
.collection(collection)
.doc(id)
.collection(subcollection);
const unsub = collectionRef.onSnapshot(
snap => {
let results = [];
snap.docs.forEach(doc => {
doc.data().createdAt && results.push(doc.data());
});
// update values
comments.value = results;
error.value = null;
},
err => {
console.log(err.message);
comments.value = null;
error.value = 'could not fetch the data';
}
);
watchEffect(onInvalidate => {
onInvalidate(() => unsub());
});
return { error, comments };
};
export default getCollection;
And this is mine Comments.vue where i passing arguments in setup() function (composition API)
const { comments } = getAllComments('posts', props.id, 'comments');
When i console.log(comments) its null, in snapshot doc.data() is good but somehow results too is empty array even if i push doc.data() to results array and pass it to comments.value.
Can someone help me how to get that subcollection?
This is my Comment.vue component
export default {
props: ['id'],
setup(props) {
const { user } = getUser();
const content = ref('');
const { comments } = getAllComments('posts', props.id, 'comments');
const ownership = computed(() => {
return (
comments.value && user.value && user.value.uid == comments.value.userId
);
});
console.log(comments.value);
}
return { user, content, handleComment, comments, ownership };
},
};
const getCollection = (collection, id, subcollection) => {
const comments = ref(null);
const error = ref(null);
// Firestore listener
return { error, comments };
}
The initial value of comments here is null and since Firebase operations are asynchronous, it can take a while before the data loads and hence it'll log null. If you are using comments in v-for then that might throw an error.
It'll be best if you set initial value to an empty array so it'll not throw any error while the data loads:
const comments = ref([]);
Additionally, if you are fetching once, use .get() instead of onSnapshot()
can you help me? I have a problem to my code coz instead of updating my map value the path changes also
const userId = firebase.auth().currentUser.uid;
const availableRecord = firebase.firestore().collection('orders').doc(this.state.OrderId);
availableRecord.update({
stores: { userId: 'On the way'}
}).then(( res) => {
console.log('Product is set into AVAILABLE')
})
Instead of
the result is
Using the square brackets notation, as follows, should do the trick:
const userId = firebase.auth().currentUser.uid;
const availableRecord = firebase.firestore().collection('orders').doc(this.state.OrderId);
const stores = {};
stores[userId] = 'On the way';
availableRecord.update({ stores }).then(() => {
console.log('Product is set into AVAILABLE');
});
Doing
availableRecord
.update({ stores: { [userId]: 'On the way' } })
also works, as you noted in your comment.
I have a Firebase Cloud Function that assigns a number to a user on onWrite. The following code works but something is wrong because the console logs state Function returned undefined, expected Promise or value.
I'm also not sure how to refer to the root from inside the onWrite so I've created several "parent" entries that refer to each other. I'm sure there is a better way.
onWrite triggers on this:
/users/{uid}/username
The trigger counts the children in /usernumbers and then writes an entry here with the uid and the child count + 1:
/usernumbers/uoNEKjUDikJlkpLm6n0IPm7x8Zf1 : 5
Cloud Function:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.setCount = functions.database.ref('/users/{uid}/username').onWrite((change, context) => {
const uid = context.params.uid;
const parent1 = change.after.ref.parent; //uid
const parent2 = parent1.ref.parent; //users
const parent3usernumbers = parent2.ref.parent.child('/usernumbers/');
const parent3usernumbersuid = parent2.ref.parent.child('/usernumbers/'+uid);
parent3usernumbers.once("value")
.then(function(snapshot) {
var a = snapshot.numChildren();
return parent3usernumbersuid.transaction((current) => {
return (a + 1);
}).then(() => {
return console.log('User Number Written', uid, a);
});
});
});
Is there a better way to do this? How can I get the Function Returned Undefined error to go away?
I should also mention it takes a few seconds for the 'usernumber' entry to be written. I'm guessing it's waiting for the function to return something.
Your function have to return a Promise :
exports.setCount = functions.database.ref('/users/{uid}/username').onWrite((change, context) => {
const uid = context.params.uid;
const parent1 = change.after.ref.parent; //uid
const parent2 = parent1.ref.parent; //users
const parent3usernumbers = parent2.ref.parent.child('/usernumbers/');
const parent3usernumbersuid = parent2.ref.parent.child('/usernumbers/'+uid);
return new Promise((resolve, reject) => {
parent3usernumbers.once("value").then(function(snapshot) {
var a = snapshot.numChildren();
return parent3usernumbersuid.transaction((current) => {
return (a + 1);
}).then(() => {
console.log('User Number Written', uid, a);
resolve({uid : uid, a : a})
}).catch(function(e) {
reject(e)
})
});
});
});
Currently, I am running two cloud functions. One adds information to posts while another deletes old posts, as such :
exports.reveal = functions.database.ref('/reveals/{postIDthatWasRevealed}/revealed').onUpdate((change, context) => {
const revealedValue = change.after.val()
if (revealedValue === true) {
var updates = {}
const postID = context.params.postIDthatWasRevealed
console.log(postID)
return admin.firestore().collection('posters').doc(postID).get().then(snapshot => {
const value = snapshot.data()
console.log(value)
// console.log(value)
const posterID = value.posterID
const posterName = value.posterName
const profileImage = value.profileImage
const postKey = value.key
return admin.database().ref('/convoID/' + postID).once('value', (snapshot) => {
if (snapshot.exists()) {
const convoIDCollection = snapshot.val()
for (var child in convoIDCollection) {
const convoID = child
updates["/conversations/"+convoID+"/information/reciever/Name"] = posterName
updates["/conversations/"+convoID+"/information/reciever/profileImage"] = profileImage
updates["/conversations/"+convoID+"/key"] = postKey
}
}
const currentTime = Date.now()
const addedTime = currentTime + 172800000
const batch = admin.firestore().batch()
const postFireStoreRef = admin.firestore().collection('posts').doc(postID)
batch.update(postFireStoreRef,{"revealedDate": currentTime})
batch.update(postFireStoreRef,{"timeOfDeletion": addedTime})
batch.update(postFireStoreRef,{"information": {"posterID":posterID,"posterName":posterName,"profileImage":profileImage} })
batch.update(postFireStoreRef,{"key":postKey})
return batch.commit(), admin.database().ref().update(updates)
})
})
}
else {
return null
}
})
^That post adds information to a post when it gets enough likes. The posts are stored in firestore, and when a firebase node associated with the post gets enough likes, some information will be downloaded appended to the firestore post entity. In theory, it should not even take one read as the data from the firestore entity is never downloaded, merely modified. The second function running is as follows :
exports.hourly_job = functions.pubsub.topic('hourly-tick').onPublish((change,context) => {
const currentTime = Date.now()
const getPostsForDate = admin.firestore().collection('posts').where('timeOfDeletion', '<', currentTime)
return getPostsForDate.get().then(snapshot => {
const updates = {}
const batch = admin.firestore().batch()
snapshot.forEach((doc) => {
var key = doc.id
console.log(key)
const convos = database().ref('/convoID/' + key).once('value', (snapshot) => {
if (snapshot.exists){
for (var child in snapshot) {
const convoID = child
console.log(child+"shit")
updates["conversations/" + value] = null
}
}
})
updates["/convoID/"+ key] = null
updates["/reveals/" + key] = null
updates["/postDetails/" + key] = null
const postFireStoreRef = admin.firestore().collection('posts').doc(key)
const posterRef = admin.firestore().collection('posters').doc(key)
batch.delete(postFireStoreRef)
batch.delete(posterRef)
})
return admin.database().ref().update(updates), batch.commit()
})
})
Each minute, this queriers firestore for old posts. At most, it may return two to three posts, leading to a few reads. However, after testing these functions out for an hour, the Google App Engine Quota shows Ten Thousand Reads while I was expecting close to twenty to fifty. Additionally, the entire day the functions had only been deployed such that they ran only 87 times. Are these functions not optimized? Is there a way to monitor where the read operations are coming from?
Edit : It seems that each time I am triggering the deletion function (actually changing the timestamp of a post such that it will be deleted when the. deletion function is called every minute) my reads increase by a couple of hundred...
I have observed this behavior occasionally with both onCreate and onDelete triggers.
Both the executions happened for the same document created in firestore. There's only one document there so I don't understand how it could trigger the handler twice. the handler itself is very simple:
module.exports = functions.firestore.document('notes/{noteId}').onCreate((event) => {
const db = admin.firestore();
const params = event.params;
const data = event.data.data();
// empty
});
this doesn't happen all the time. What am I missing?
See the Cloud Firestore Triggers Limitations and Guarantees:
Delivery of function invocations is not currently guaranteed. As the
Cloud Firestore and Cloud Functions integration improves, we plan to
guarantee "at least once" delivery. However, this may not always be
the case during beta. This may also result in multiple invocations
for a single event, so for the highest quality functions ensure that
the functions are written to be idempotent.
There is a Firecast video with tips for implementing idempotence.
Also two Google Blog posts: the first, the second.
Based on #saranpol's answer we use the below for now. We have yet to check if we actually get any duplicate event ids though.
const alreadyTriggered = eventId => {
// Firestore doesn't support forward slash in ids and the eventId often has it
const validEventId = eventId.replace('/', '')
const firestore = firebase.firestore()
return firestore.runTransaction(async transaction => {
const ref = firestore.doc(`eventIds/${validEventId}`)
const doc = await transaction.get(ref)
if (doc.exists) {
console.error(`Already triggered function for event: ${validEventId}`)
return true
} else {
transaction.set(ref, {})
return false
}
})
}
// Usage
if (await alreadyTriggered(context.eventId)) {
return
}
In my case I try to use eventId and transaction to prevent onCreate sometimes triggered twice
(you may need to save eventId in list and check if it exist if your function actually triggered often)
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const db = admin.firestore()
exports = module.exports = functions.firestore.document('...').onCreate((snap, context) => {
const prize = 1000
const eventId = context.eventId
if (!eventId) {
return false
}
// increment money
const p1 = () => {
const ref = db.doc('...')
return db.runTransaction(t => {
return t.get(ref).then(doc => {
let money_total = 0
if (doc.exists) {
const eventIdLast = doc.data().event_id_last
if (eventIdLast === eventId) {
throw 'duplicated event'
}
const m0 = doc.data().money_total
if(m0 !== undefined) {
money_total = m0 + prize
}
} else {
money_total = prize
}
return t.set(ref, {
money_total: money_total,
event_id_last: eventId
}, {merge: true})
})
})
}
// will execute p2 p3 p4 if p1 success
const p2 = () => {
...
}
const p3 = () => {
...
}
const p4 = () => {
...
}
return p1().then(() => {
return Promise.all([p2(), p3(), p4()])
}).catch((error) => {
console.log(error)
})
})
Late to the party, I had this issue but having a min instance solved the issue for me
Upon looking #xaxsis attached screenshot, my function took almost the amount of time about 15 seconds for the first request and about 1/4 of that for the second request