I am a beginner in Javascript, started learning it 2 days ago because I wanted to set up some cloud functions on Firebase. The database structure of my app (already published) is shown in the figure, and what I want is:
When users upload their "achievements" to Firebase, the cloud function calculates "the total times" that a specific achievement has been achieved by all users. For example, achievements[0] is achieved 80000 times in total, and achievements[28] is achieved 54321 times in total. After calculating them, I store the array under the node "achievementsCount_total", which would be at the same level as the node "users".
To do this, I followed the documents on Firebase and tried to accomplish the cloud function. Here are the codes:
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.updateScores = functions.database.ref('/users/{userUID}/achievements')
.onWrite(event => {
var database=firebase.database();
var achievementsCount_total=new Array();
for(var i=0;i<60;i++){achievementsCount_total[i]=0;}
var allUsersSnapshot = event.data.parent.parent.child('/{userUID}');//a list containing all users
allUsersSnapshot.forEach(function(element) {
var achievements_this = element.child('/achievements').val();
for(var i=0;i<60;i++)
{
achievementsCount_total[i]+=achievements_this[i];
}
}, this);
database.ref('achievementsCount_total').set(achievementsCount_total);
return null;
});
However, the new node("achievementsCount_total") was not created at the same level as "users". I checked it on Firebase and found that the function was successfully executed, while the new node did not show up. Please help me with what I did wrong. Thank u~
Related
Hi I am attempting to make a social media app on Firestore.
Now to model a follow system here is my plan.
users (Collection)
{uid} document which contains Followers and Following as a number.
following (Collection)
{uid}
myFollowing (subCollection)
{uid of other user}
followers (Collection)
{uid}
myFollowers (subCollection)
{uid of other user}
So here is my plan, and please feel free to critique it and help me make it better, because I dont know if this is the best way to do it.
When user A follows user B, I will write a document in:
following
A uid
myFollowing
B uid
This write will happen straight from the app.
After which I plan to trigger a cloud function that does two things, 1. It will increment a counter in the users collection, that holds total following. 2. It will write another document which would be
Followers
B uid
myFollowers
A uid
And after this I can have another cloud function that triggers whenever a document is made in the Followers/uid/myFollowers collection which increments followers count in the users collection.
So here are the questions
Is this the best way to go about this?
How do i write the cloud functions?
Thanks for any help you can give me!
I solved this by doing everything I did above, and using the following code for cloud functions
const functions = require('firebase-functions');
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
exports.onFollowCreate = functions.firestore
.document("following/{userID}/myFollowing/{id}")
.onCreate((snap, context) => {
const newValue = snap.data()
const db = admin.firestore();
db.collection("users").doc(context.params.userID).update({following: admin.firestore.FieldValue.increment(1)}).catch((er)=>{console.log(er)})
db.collection('followers').doc(newValue.uid).collection("myFollowers").doc(context.params.userID).set({uid: context.params.userID, timeStamp: new Date()}).catch(er=>console.log(er))
});
exports.onFollowDelete = functions.firestore
.document("following/{userID}/myFollowing/{id}")
.onDelete((snap, context)=>{
const deletedValue = snap.data()
const db = admin.firestore();
db.collection("users").doc(context.params.userID).update({following: admin.firestore.FieldValue.increment(-1)}).catch(er=>console.log(er))
db.collection('followers').doc(deletedValue.uid).collection("myFollowers").doc(context.params.userID).delete().catch(er=>console.log(er))
})
exports.onFollowersCreate = functions.firestore
.document("followers/{userID}/myFollowers/{id}")
.onCreate((snap, context)=>{
const db = admin.firestore();
db.collection("users").doc(context.params.userID).update({followers: admin.firestore.FieldValue.increment(1)}).catch(er=>console.log(er))
})
exports.onFollowersDelete = functions.firestore
.document("followers/{userID}/myFollowers/{id}")
.onDelete((snap, context)=>{
const db = admin.firestore();
db.collection("users").doc(context.params.userID).update({followers: admin.firestore.FieldValue.increment(-1)}).catch(er=>console.log(er))
})
I've thought of this before and it was very similar. I think this might be the best way to go about structuring your database. Here's an article on Medium about some database designs.
Now for the functions, you want one which will trigger once you write that document about A following B. See the docs for a onCreate function. Your cloud functions will live in a node.js 10 serverless environment and will have no connection to your front-end application. Here's a real world example of some of my functions on a deployed site. I would recommend not adding data to firestore on your front-end. Instead make a onCall HTTP function, see more about those here.
Sorry for not giving you actual code to go off of, but I find doing it yourself will help you learn. Good luck :)
For a scheduling app I'm building in Flutter I'm trying to write data to my cloud Firestore database with cloud functions and cron jobs to make my app more self-sustaining. I'm stuck at the first step, which is trying to get my cloud function to write data to my cloud Firestore database.
Below I've included links to pictures on github of how my current data is structured in Firestore. What I want is to add named documents to the collection 'days' with a subcollection of 'hours', in which there are more named documents with the fields 'hour' and 'reserved'.
Picture 1: https://github.com/winckles/rooster/blob/master/Schermafbeelding%202019-11-07%20om%2014.27.55.png?raw=true
Picture 2: https://github.com/winckles/rooster/blob/master/Schermafbeelding%202019-11-07%20om%2014.28.26.png?raw=true
Below I have also included my try on getting data in Firestore.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// admin.firestore().collection('days').add({original: original}). then(writeResult => {
//
// });
exports.updateData = functions.https.onCall((data, context) => {
const days = admin.firestore().collection('days');
return days.doc(data['2019-11-08/hours/001']).set({
hour: '08:00-09:00',
reserved: '',
});
});
Ideally I would want the cloud function to add 14 documents (eg. 2019-11-06 to 2019-11-19) to the collection 'days' at the same time. Those documents would then each have a subcollection 'hours' with the data from the second screenshot (so the documents 001, 002, 003 etc. with the fields hour and reserved).
I read the documentation on cloud functions and mostly found triggers when data is written to Firestore, but this is not what I want. I also tried the quickstart function samples but with no success. It shouldn't be too hard but I can't seem to figure it out. I'm using Javascript to write the cloud function in. Any help/tips would be greatly appreciated!
You didn't specify exactly what wasn't working for you. However, the first thing I notice is that you're not calling initilizeApp properly from the cloud functions context. For cloud functions, you don't need any parameters, as the default credentials should work for you (unless you have done something very unusual already).
Here is a cloud function that will model the behavior you want. It does not do the full behavior, as I found that writing the date handling code would likely distract from the main part of the problem you are asking about, which is the firestore and functions code itself.
Likewise, this uses an https function (as it is a bit easier for me to test :), to use a callable function (or any other function type, e.g. a scheduled function if you're using a cron job) you would need to adjust it slightly (e.g., for a callable function you would need to change the declaration back to onCall and changing the final .then() call to return the value you want to return).
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.doIt = functions.https.onRequest((request, response) => {
const days = db.collection('days');
const writePromises = [];
['2019-11-08', '2019-11-09'].forEach((day) => {
const documentReference = days.doc(day);
writePromises.push(documentReference.set({reserved: ''}));
const hoursReference = documentReference.collection('hours');
const dataMap = { '001': '08:00-09:00',
'002': '09:00-10:00',
'003': '10:00-11:00',
'004': '11:00-12:00' };
Object.keys(dataMap).forEach((hour) => {
writePromises.push(hoursReference.doc(hour).set({
hour: dataMap[hour],
reserved: ''
}))
});
});
return Promise.all(writePromises)
.then(() => { response.send('ok'); })
.catch((err) => { console.log(err); });
});
Note that when you write this entire structure, you will be billed for a write to each document. There isn't really a way to avoid that though, as writes are billed per document, not per request.
Likewise, you may want to consider doing this as a batched write -- the above example is just showing a basic approach to writing. A batched write would put all of the documents into the database atomically. However, a batched write is limited to 500 updates, which you would hit at around 21 days of data (21 days * 24 hours). This would look very similar to the above (below is just the content of the function itself:
const days = db.collection('days');
const batch = db.batch();
['2019-11-08', '2019-11-09'].forEach((day) => {
const documentReference = days.doc(day);
batch.set(documentReference, {reserved: ''});
const hoursReference = documentReference.collection('hours');
const dataMap = { '001': '08:00-09:00',
'002': '09:00-10:00',
'003': '10:00-11:00',
'004': '11:00-12:00' };
Object.keys(dataMap).forEach((hour) => {
batch.set(hoursReference.doc(hour), {
hour: dataMap[hour],
reserved: ''
});
});
});
return batch.commit()
.then(() => { response.send('ok'); })
.catch((err) => { console.log(err); });
I do wonder a small bit about why you need to do this in a cloud function, rather than via a Firestore call directly from your app. Regardless, the above should allow you to get started.
if you wanna do a cron in firebase maybe its better if you use Google Cloud Scheduler, but be careful this approach have a different kind of facturation
exports.scheduledFunction = functions.pubsub.schedule('every 5 minutes').onRun((context) => {
console.log('This will be run every 5 minutes!');
return null;
});
You can learn more about this in:
https://firebase.google.com/docs/functions/schedule-functions
Scenario I have created two projects in firebase using command line tools. The first site(say A) is a web-app that uses hosting, firestore, cloud functions and auth. The second site(say B) is sort of admin-portal which should be able to show all the data in site-A. Site-B uses different Auth than site-A.
Note More sites like site-A will be created in future and site-B will remain the admin-portal of all of them.
Question How to bring the data from one project in firebase to some other project?
PS I know that SO doesn't allow this but if there is a better way of doing this, I would be very happy to know.
You'll have to initialize Firebase multiple times, once for each project. According to the documentation:
// Initialize the default app
firebase.initializeApp(defaultAppConfig);
// Initialize another app with a different config
var otherApp = firebase.initializeApp(otherAppConfig, "other");
console.log(firebase.app().name); // "[DEFAULT]"
console.log(otherApp.name); // "other"
// Use the shorthand notation to retrieve the default app's services
var defaultStorage = firebase.storage();
var defaultDatabase = firebase.database();
// Use the otherApp variable to retrieve the other app's services
var otherStorage = otherApp.storage();
var otherDatabase = otherApp.database();
I'm trying to implement a feature in an app which will list out all the file-names in a ListView and clicking the specific list item will show the contents of the file. I don't really know JavaScript and am also very new to Cloud functions. I wrote a small function which adds metadata to firebase whenever a file is added to firebase storage but it ends up adding the data even when the file is deleted.
Here is the code
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.urls = functions.storage.object().onChange( event => {
const file = event.data;
const metadata = file.metadata;
console.log(metadata);
return admin.database().ref("/").set(metadata); });
Can someone please tell me what changes I could make in the above to add urls instead of metadata and also remove the same if the file is being removed?
From the Firebase documentation section Detect resourceState and other event attributes:
The resourceState attribute for an event has the value "exists" (for object creation and updates) or "not_exists" (for object deletion and moves).
// Exit if this is a move or deletion event.
if (resourceState === 'not_exists') {
console.log('This is a deletion event.');
return;
}
I apologize if the title is not worded properly. I am seeking advice on the recommended way to perform the following operation on Firebase.
I am using Firebase for a group collaboration kind of app (think of Whatsapp). A user registers using his phone number and is added as a user to the Firebase database. A user is stored as follows on Firebase
users
-KGvMIPwul2dUYABCDEF
countryCode: 1
id: -KGvMIPwul2dUYABCDEF
mobileNumber: 1231231234
name: Varun Gupta
Whenever a user opens the app, I want to check who all in user's phone contact list is also using my app and present those contacts in the app. The phone number is used to check if a person in the phone contacts is using the app. To achieve this, I store the user contacts list to Firebase which triggers a Firebase function to compute the contacts also using my app and store them separately on Firebase.
To figure out who is using my app, I create a map of all the users in Firebase keyed on the phone number and a combination of country code and phone number. The value is the user ID which would be -KGvMIPwul2dUYABCDEF for the above example. So, the map would have the following two entries for the user
{
1231231234: -KGvMIPwul2dUYABCDEF
11231231234: -KGvMIPwul2dUYABCDEF
}
I create the above for all the users and then I just query for each contact, if there is an entry for the user's phone number in the map and figure out the list of users who are using the app.
Below are the excerpts from the code. Right now it is done in a firebase-queue worker but I intend to move it to a Firebase function
// This piece of code is used to read the users in Firebase and create a map as described above
ref.child('users').on('child_added', (snapshot) => {
var uid = snapshot.key;
var userData = snapshot.val();
// Match against both mobileNumber and the combination of countryCode and mobileNumber
// Sanity check
if(userData.mobileNumber && userData.countryCode) {
contactsMap.set(sanitizePhoneNumber(userData.mobileNumber), uid);
contactsMap.set(sanitizePhoneNumber(userData.countryCode + userData.mobileNumber), uid);
}
});
// This piece of code is used to figure out which contacts are using the app
contactsData.forEach((contact) => {
contact.phoneNumbers.forEach((phoneNumber) => {
var contactsMapEntry = contactsMap.get(sanitizePhoneNumber(phoneNumber))
// Don't add user himself to the contacts if he is present in the contacts
if(contactsMapEntry && contactsMapEntry !== uid && !contactsObj[contactsMapEntry]) {
const contactObj = {
name: createContactName(contact),
mobileNumber: phoneNumber.number,
id: contactsMapEntry
}
contactsObj[contactsMapEntry] = contactObj
currentContacts.push(contactObj)
}
});
});
// After figuring out the currentContacts, I do some processing and they are pushed to Firebase which are then synched with the app
My concern is that as the number of users increases, this will start to become slow because I am reading all the users from the Firebase creating this map in memory for every request to figure out the contacts who are using the app or would I be okay with this brute force kind of method and shouldn't worry too much.
Should I consider duplicating data like below also
contacts
1231231234: -KGvMIPwul2dUYABCDEF
11231231234: -KGvMIPwul2dUYABCDEF
and then just query for /contacts/{contact phone number}
If there is an even better method to achieve this workflow, please suggest.