firebase realtime database calling set with new properties - firebase

i am using firebase realtime DB with my ionic app and using angularfire2. I have data stored like:
"users" : {
"230A3lKQqWh0TczOGP8sbtMVpuF2" : {
"email" : "xxx#gmail.com",
"isAdmin" : false,
"name" : "Santosh Yadav",
"pic" : "https://graph.facebook.com/xxx/picture"
},
"oSEYj0zrkhPCk9r7uwyOOkHcqe53" : {
"email" : "yyy#gmail.com",
"isAdmin" : true,
"name" : "Vik Kumar",
"pic" : "https://graph.facebook.com/xxx/picture"
},
"tl3uvseaBeWVYFCTwSngUqcSokX2" : {
"email" : "zzz#gmail.com",
"isAdmin" : false,
"name" : "Neeti Singhal",
"pic" : "https://graph.facebook.com/xxx/picture"
}
}
We ended up to store more user data like date of birth and date of aniversary and when we try to update it as below:
createUser(user:User){
console.log('start of create user')
var payload = {
"name": user.name,
"email": user.email,
"pic" : user.pic,
"dob" : user.dob,
"anivDate" : user.anivDate,
}
return this.db.object('/users/' + user.uid).set(payload).then(
(resp) => console.log("user created")
).catch(
(err) => console.log("issues creating user:" + err)
)
}
It fails with error:
ERROR Error: Reference.set failed: First argument contains undefined in property 'users.oSEYj0zrkhPCk9r7uwyOOkHcqe53.anivDate'
at validateFirebaseData (validation.js:113)
at validation.js:140
at forEach (obj.js:46)
I understand the error that it is unable to set a property anivDate that does not exist in my firebase realtime DB. This will fix if i manually add the property into the db manually. But that is not a practical fix. So, what is the solution here?
To define the fix: i am expecting to add a new property if that does not exist else just update it.

For a quick fix, use the || operator to set a default value on undefined elements. Usage:
var payload = {
"anivDate": user.anivDate || "1970-01-01",
...
};
This sets anivDate to user.anivDate if it exists, 1970-01-01 if it does not. (This works, but in my opinion it is better practice to not have a anivDate property on a user if no such value exists, and check for that value's existence when accessing the data.)
For a more scalable/reliable fix, use Firebase's realtime database triggers to add async validating or default values to your new columns.

Related

how to update a value in firebase realtime database using Cloud Functions for Firebase

I went though the firebase docs for updating a value in realtime database using Cloud Functions for Firebase, but am not able to understand.
My database structure is
{
"user" : {
"-KdD1f0ecmVXHZ3H3abZ" : {
"email" : "ksdsd#sdsd.com",
"first_name" : "John",
"last_name" : "Smith",
"isVerified" : false
},
"-KdG4iHEYjInv7ljBhgG" : {
"email" : "superttest#sds213123d.com",
"first_name" : "Max1",
"last_name" : "Rosse13131313l",
"isVerified" : false
},
"-KdGAZ8Ws6weXWo0essF" : {
"email" : "superttest#sds213123d.com",
"first_name" : "Max1",
"last_name" : "Rosse13131313l",
"isVerified" : false
}
}
I want to update the isVerified using database trigger cloud functions. I don't know how to update a database value using cloud functions (language : Node.JS)
I wrote a code to automatically update the value of the key 'isVerified' of a user, when the user is created by using database trigger onWrite. My code is
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.userVerification = functions.database.ref('/users/{pushId}')
.onWrite(event => {
// Grab the current value of what was written to the Realtime Database.
var eventSnapshot = event.data;
if (event.data.previous.exists()) {
return;
}
eventSnapshot.update({
"isVerified": true
});
});
but when i deploy the code, and add a user to the database, the cloud function log shows the below error
TypeError: eventSnapshot.child(...).update is not a function
at exports.userVerification.functions.database.ref.onWrite.event (/user_code/index.js:10:36)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:35:20
at process._tickDomainCallback (internal/process/next_tick.js:129:7)
You are trying to call update() on a DeltaSnapshot object. There is no such method on that type of object.
var eventSnapshot = event.data;
eventSnapshot.update({
"isVerified": true
});
event.data is a DeltaSnapshot. If you want to change the data at the location of the change represented by this object. Use its ref property to get a hold of a Reference object:
var ref = event.data.ref;
ref.update({
"isVerified": true
});
Also, if you are reading or writing the database in a function, you should always return a Promise that indicates when the change is complete:
return ref.update({
"isVerified": true
});
I would recommend taking Frank's advice from the comments and study the existing sample code and documentation to better understand how Cloud Functions works.

How to implement user based security in the firebase database?

This is the database for example:
"Messages" : {
"Message1" : {
"Uid" : "sampleid1"
"Text" : "hi"
},
"Message2" : {
"Uid" : "sampleid2"
"Text" : " hello"
}
}
I want only those users to read the messages whose uid is equal to the Uid field of Message#.
The structure of database given in firebase documentation(i.e. using user id based messages in the database where the node of each message represents the uid of the user who sent the message) doesn't achieve the goal of my project as I need to know the uid of the user who sent the message each time any user sends a message.
Therefore, please suggest the rules that would help me achieve my task as mentioned in this question
Also, when I applied certain rules on the above structure of database, I couldn't read any data because 'firebase rules are not filters'.
Please ignore the syntax and format of json written in above example as it is just for reference
Please help!
Structure your data so:
"messages" : {
"<receiver_uid>" : {
"msg_1" : {
"text" : "Hello world...",
"uid" : "<sender_uid>"
}
// more msgs for this receiver ...
}
}
the rules should be something like
{
"rules" : {
"messages" : {
"$receiver" : {
".read" : "auth.uid == $receiver", // only receiver shall read
".write" : "auth != false" // any authenticated user can write
}
}
}
}

Look up / join data with AngularFire

I've got a question on how to combine data from different Firebase database nodes before displaying it on the frontend. I've got a Firebase database with the following structure. (I'm new to a nosql setup, so this looks more relational):
{
"agents" : {
"-KPCmnwzjd8CeSdrU3As" : {
"contactNumber" : "12345",
"name" : "aaa"
},
"-KPCmw6dKuopDlsMVOlU" : {
"contactNumber" : "123",
"name" : "bbb"
},
"-KPCoWcLecpchcFV-vh_" : {
"contactNumber" : "123",
"name" : "ccc"
},
"-KPROMhPatLjVxMdvfLf" : {
"contactNumber" : "256342",
"name" : "blah"
},
"-KPWIFl5qp5FvAeC3YhG" : {
"contactNumber" : "123",
"name" : "eee"
}
},
"listings" : {
"-KPWKTvW3GzFEIT2hUNU" : {
"agent" : "-KPCoWcLecpchcFV-vh_",
"description" : "third",
"reference" : "REF1"
}
}
}
I'm using Firebase SDK 3.2.0 and AngularFire 2.0.1. In my Angular app I am able to get the list of listings, and for each one look up the agent information. The reason I'm not storing the agent info with the listing is I want the ability to update the agent and the change should reflect on all listings. I don't want to have to go and update all listings if the agent telephone number changes (as an example).
In my controller I have the following:
// get the listings
var listingsRef = firebase.database().ref().child('listings');
vm.listings = $firebaseArray(listingsRef);
// this will move to my ui-router as a resolve but for simplicity's sake
// I added it here...
vm.listings.$loaded().then(function(data){
// loop through the listings...
data.forEach(function(listing) {
if (listing.agent) {
// get the agent for the listing
listing.agent = AgentFactory.getAgent(listing.agent);
}
});
});
Right now the data is displaying correctly on the frontend. There is a slight delay with the agent data showing because of the need of the getAgent promise to resolve.
My questions are:
Is this the correct way of getting the agent data? Should I be looping through the listings and for each query the agent data? How do I wait / keep track of all of the getAgents to resolve?
Any help would be appreciated.
Thanks.
I've structured my data similarly. If you want to wait for all the getAgents to resolve you can use $q.all. I'm not entirely sure what your AgentFactory.getAgent is returning, but let's assume it's a $firebaseObject. If that's the case inject $q and then do the following:
vm.listings.$loaded().then(function (data) {
// loop through the listings...
var promises = [];
data.forEach(function (listing) {
if (listing.agent) {
// get the agent for the listing
listing.agent = AgentFactory.getAgent(listing.agent);
promises.push(listing.agent.$loaded());
}
});
return $q.all(promises);
}).then(function (agents) {
//all agents are loaded
});

Synchronizing data across collections in meteor

I am using meteor to keep track of a person's progress as they progress through quiz questions. I want to return a random question from quizzes that matches the current question they are on. My trouble is that I have two methods, one creates a document to keep track of their progress through the whole quiz, and another creates a document to keep track of a particular question as well as documents that match back to the answers. I need to have the "questionId" set in one method call equal the "currentQuestionId" in the other.
My problem is that in trying to synchronize the currentquestionId in my responseToLectures, with the question that it actually picks, does not quite work in my current code because the methods are asynchronous, but I am calling them in a synchronous manner. Could I get help for how to refactor this code to be more meteor-like? The funny thing I notice is that if I put console.logs in the inner most "if" block, I will see that they get called >5 times before the page loads, I guess that I expect that as soon as it gets returned, that it would stop running the block and the next time that it gets loaded, it would just return the right question immediately. Thanks!
Question: function(){
var responsesToLectures = ResponsesToLectures.findOne({});
if (responsesToLectures != null){
if(responsesToLectures.currentProblemId == null){
var questionCount = Questions.find({level: responsesToLectures.currentLevel}).count();
if(questionCount != null){
var randomNumber = Math.floor((Math.random() * questionCount));
var randomQuestion = Questions.findOne({level: responsesToLectures.currentLevel}, {skip: randomNumber});
if (randomQuestion != null) {
Meteor.call('setRandomQuestion', randomQuestion._id, randomQuestion.lectureId);
return randomQuestion;
}
}
} else {
return Questions.findOne({_id : responsesToLectures.currentProblemId});
}
}
},
The issue is that the Question.findOne which gets returned from this a different id from the one which I set using 'setRandom' question. What is the proper way to retrieve values from documents in collections so that I can shuttle them into documents from other collections?
This is the mongo db to show you what I mean:
meteor:PRIMARY> db.responses.find();
{ "_id" : "zTTF7GrnuRqT4JNck", "userId" : "hneHgetL9oYaL2iWA", "questionId" : "ZSYJXn59ykA9QrTv6", "answerId" : "yHQzekpge486CwqLJ" }
{ "_id" : "47d2hhw7okLq2SXhm", "userId" : "hneHgetL9oYaL2iWA", "questionId" : "ZSYJXn59ykA9QrTv6", "answerId" : "WyyKfxzbinjxgrujd"}
{ "_id" : "ZqQu3TDcdMeo2SFBR", "userId" : "hneHgetL9oYaL2iWA", "questionId" : "ZSYJXn59ykA9QrTv6", "answerId" : "MES2Aj8PTNzMmthyh"}
{ "_id" : "dHtyKSxDccXvBTjnD", "userId" : "hneHgetL9oYaL2iWA", "questionId" : "ZSYJXn59ykA9QrTv6", "answerId" : "tPcCSjS8qhL7oebWZ" }
meteor:PRIMARY> db.responsesToLectures.find();
{ "_id" : "TohrY5tHK2DJ2ngWb", "userId" : "hneHgetL9oYaL2iWA", "lectureId" : "pZKuD2LnBh73wPTtN", "currentLevel" : 1, "currentQuestionId" : "FZBQu5wQq2bA2e6SB" }
meteor:PRIMARY> db.scores.find();
{ "_id" : "9HhC8CBG4JJTxcR8m", "userId" : "hneHgetL9oYaL2iWA", "questionId" : "ZSYJXn59ykA9QrTv6" }
For those who encounter this type of issue in the future, I was able to use collection hooks to synchronize the Ids across multiple collections. This worked for me!
ResponsesToLectures.before.insert(function(userId, doc){
var questionCount = Questions.find({level: doc.currentLevel}).count();
if(questionCount != null){
var randomNumber = Math.floor((Math.random() * questionCount));
var randomQuestion = Questions.findOne({level: doc.currentLevel}, {skip: randomNumber});
if (randomQuestion != null) {
doc.currentQuestionId = randomQuestion._id;
}
}
});

Meteor Accounts via external services dont set user.username

I'm a complete newbie, and I've been fiddling with the Meteor 1.0 sample todo list app to connect google oauth to it.
When I do so the page no longer renders properly because {{username}} is not set at all.
https://docs.meteor.com/#/full/meteor_users says "username: a unique String identifying the user." but the oauth stuff doesn't create one for you.
Connect service to existing meteor account talks about linking an already existing account to another service, but in this case I just want to use the external service.
https://stackoverflow.com/questions/25182903/meteor-facebook-registration uses onCreateUser() to manually set
user.username = user.services.facebook.name
but this isn't portable across services nor to guarantee uniqueness.
https://github.com/aldeed/meteor-collection2 defines the User schema so that username is mandatory.
When I dump the users table (some fields removed) the google account doesn't have a username, and there is no field that can really take on that value automatically as there could be a clash. Email could be used but I'd rather the username wasn't the email address. Do I just force the user to specify a desired username?
meteor:PRIMARY> db.users.find()
{
"_id" : "YNWt2cATMsKFG7od6",
"createdAt" : ISODate("2014-11-05T11:08:00.406Z"),
"services" : {
"password" : {
},
},
"username" : “a_user”
}
{
"_id" : "CyQsJqcez3kWTRHyQ",
"createdAt" : ISODate("2014-11-05T12:09:40.139Z"),
"profile" : {
"name" : “Alice User”
},
"services" : {
"google" : {
"email" : “a_user#example.com",
"family_name" : “User”,
"gender" : “female",
"given_name" : "Alice”,
"id" : "1115",
"name" : “Alice User,
}
}
}
What is the correct way of handling this?
This is how I did it myself with facebook and google
Accounts.onCreateUser(function (options, user) {
if (options && options.profile) {
user.profile = options.profile;
}
if (user.services) {
var service = _.pairs(user.services)[0];
var serviceName = service[0];
var serviceData = service[1];
console.log("serviceName", serviceName)
if (serviceName == "facebook") {
user.emails = [
{"address": serviceData.email, "verified": true}
];
user.profile = {"first_name": serviceData.first_name, "last_name": serviceData.last_name, "avatar": getFbPicture(serviceData.id)};
}
else if (serviceName == "google") {
user.emails = [
{"address": serviceData.email, "verified": true}
];
user.profile = {"first_name": serviceData.given_name, "last_name": serviceData.family_name, "avatar": getGooglePicture(serviceData.id)};
}
}
console.log("user created :", user)
return user;
});
I do not use username but I use email so that I'm sure that it will be unique. After that
I could allow the user to set his username or display name like Stackoverflow or other services do.
However you could use the email as username and again let the user change it later.
In my application, I am using this to handle the same problem.
username = user.services.facebook.name
user.username=generateUsername(username)
generateUsername = function(username) {
var count;
username = username.toLowerCase().trim().replace(" ", "");
count = Meteor.users.find({"profile.un": username}).count();
if (count === 0) {
return username;
}
else {
return username + (count + 1)
}
This is will create a unique username. After successful signup you can allow the users to change the username and check your db for its existence.
In my application I use
if(user.services.facebook)
this.user = user.services.facebook.name
if(user.services.google)
this.user = user.services.google.name

Resources