This is a small question and if need be I'll just rewrite everything, but I want to save myself the work.
I have a structure that looks like users/usernames/<someUN>/. I checked out the Firebase documentation but I couldn't find examples of how to update in a 'tight' tree.
Now if I want to change <someUN>, you can see the problem
this.database.ref('users/usernames/' + this.UN).update({
username: newUsernameVariable
})
I would have to restructure an unchangable value and do something like users/usernames/<userID>/username/<username> but
it goes against denormalizing the database and
I already have a tree holding uids/<uid>/usernames/<UN>, so that would be extra redundant
Now if you tried to move the ref up a notch:
this.database.ref('users/usernames').update({
this.UN: newUsernameVariable
})
This is very close to what I want but unfortunately is not valid JSON. The left hand side is not converted to string. I've tried doing this
var UNJSON = JSON.stringify(this.UN);
this.database.ref('users/usernames/').update({
UNJSON: newUsername
});
But it won't work, just treats UNJSON as a word
EDIT:
I've changed it to
var updates = {};
updates[this.UN] = newUsername;
this.database.ref('users/usernames').update(updates);
Which almost works, but now the children node are replaced!
I've changed the question of this title because essentially I've gotten to the point where I have something like users/usernames/myawesomeusername/{manychildren} and I want to keep the children while editing myawesomeusername.
EDIT 2: this.UN is populated in onAuthStateChanged by the time it hits the update code. Here is the setting of this.UN
// UN is a global read/write username string that persists throughout the session.
// SETTING THE VALUE OF this.UN WITH UID
if(this.UN == undefined) {
console.log('this.UN was null. Fetch the real one with UID from the database');
this.database.ref('uids/' + user.uid).once('value', function(snapshot){
this.UN = snapshot.val().username;
console.log('Grabbed from snapshot.username');
if(this.UN == undefined) {
console.log('Impossible error');
}
}.bind(this)).catch(function(err){
console.log('Error obtaining userID from database', err);
this.UN = user.displayName || "User" + user.uid;
}.bind(this));
} else {
console.log(this.UN);
}
I use this.UN to handle any time I need the username for the rest of the app.
Related
I am attempting to perform an update to a MongoDB document (using mongoose) by first using .findById to get the document, then updating the fields in that document with new values. I am still a bit new to this so I used a tutorial to figure out how to get it working, then I have been updating my code for my needs. Here is the tutorial: MEAN App Tutorial with Angular 4. The original code had a schema defined, but my requirement is for a generic MongoDB interface that will simply take whatever payload is sent to it and send it along to MongoDB. The original tutorial had something like this:
exports.updateTodo = async function(todo){
var id = todo.id
try{
//Find the old Todo Object by the Id
var oldTodo = await ToDo.findById(id);
}catch(e){
throw Error("Error occured while Finding the Todo")
}
// If no old Todo Object exists return false
if(!oldTodo){
return false;
}
console.log(oldTodo)
//Edit the Todo Object
oldTodo.title = todo.title
oldTodo.description = todo.description
oldTodo.status = todo.status
console.log(oldTodo)
try{
var savedTodo = await oldTodo.save()
return savedTodo;
}catch(e){
throw Error("And Error occured while updating the Todo");
}
}
However, since I don't want a schema and want to allow anything through, I don't want to assign static values to specific field names like, title, description, status, etc. So, I came up with this:
exports.updateData = async function(update){
var id = update.id
// Check the existence of the query parameters, If they don't exist then assign a default value
var dbName = update.dbName ? update.dbName : 'test'
var collection = update.collection ? update.collection : 'testing';
const Test = mongoose.model(dbName, TestSchema, collection);
try{
//Find the existing Test object by the Id
var existingData = await Test.findById(id);
}catch(e){
throw Error("Error occurred while finding the Test document - " + e)
}
// If no existing Test object exists return false
if(!existingData){
return false;
}
console.log("Existing document is " + existingData)
//Edit the Test object
existingData = JSON.parse(JSON.stringify(update))
//This was another way to overwrite existing field values, but
//performs a "shallow copy" so it's not desireable
//existingData = Object.assign({}, existingData, update)
//existingData.title = update.title
//existingData.description = update.description
//existingData.status = update.status
console.log("New data is " + existingData)
try{
var savedOutput = await existingData.save()
return savedOutput;
}catch(e){
throw Error("An error occurred while updating the Test document - " + e);
}
}
My original problem with this was that I had a lot of issues getting the new values to overwrite the old ones. Now that that's been solved, I am getting the error of "TypeError: existingData.save is not a function". I am thinking the data type changed or something, and now it is not being accepted. When I uncomment the static values that were in the old tutorial code, it works. This is further supported by my console logging before and after I join the objects, because the first one prints the actual data and the second one prints [object Object]. However, I can't seem to figure out what it's expecting. Any help would be greatly appreciated.
EDIT: I figured it out. Apparently Mongoose has its own data type of "Model" which gets changed if you do anything crazy to the underlying data by using things like JSON.stringify. I used Object.prototype.constructor to figure out the actual object type like so:
console.log("THIS IS BEFORE: " + existingData.constructor);
existingData = JSON.parse(JSON.stringify(update));
console.log("THIS IS AFTER: " + existingData.constructor);
And I got this:
THIS IS BEFORE: function model(doc, fields, skipId) {
model.hooks.execPreSync('createModel', doc);
if (!(this instanceof model)) {
return new model(doc, fields, skipId);
}
Model.call(this, doc, fields, skipId);
}
THIS IS AFTER: function Object() { [native code] }
Which showed me what was actually going on. I added this to fix it:
existingData = new Test(JSON.parse(JSON.stringify(update)));
On a related note, I should probably just use the native MongoDB driver at this point, but it's working, so I'll just put it on my to do list for now.
You've now found a solution but I would suggest using the MongoDB driver which would make your code look something along the lines of this and would make the origional issue disappear:
// MongoDB Settings
const MongoClient = require(`mongodb`).MongoClient;
const mongodb_uri = `mongodb+srv://${REPLACE_mongodb_username}:${REPLACE_mongodb_password}#url-here.gcp.mongodb.net/test`;
const db_name = `test`;
let db; // allows us to reuse the database connection once it is opened
// Open MongoDB Connection
const open_database_connection = async () => {
try {
client = await MongoClient.connect(mongodb_uri);
} catch (err) { throw new Error(err); }
db = client.db(db_name);
};
exports.updateData = async update => {
// open database connection if it isn't already open
try {
if (!db) await open_database_connection();
} catch (err) { throw new Error(err); }
// update document
let savedOutput;
try {
savedOutput = await db.collection(`testing`).updateOne( // .save() is being depreciated
{ // filter
_id: update.id // the '_id' might need to be 'id' depending on how you have set your collection up, usually it is '_id'
},
$set: { // I've assumed that you are overwriting the fields you are updating hence the '$set' operator
update // update here - this is assuming that the update object only contains fields that should be updated
}
// If you want to add a new document if the id isn't found add the below line
// ,{ upsert: true }
);
} catch (err) { throw new Error(`An error occurred while updating the Test document - ${err}`); }
if (savedOutput.matchedCount !== 1) return false; // if you add in '{ upsert: true }' above, then remove this line as it will create a new document
return savedOutput;
}
The collection testing would need to be created before this code but this is only a one-time thing and is very easy - if you are using MongoDB Atlas then you can use MongoDB Compass / go in your online admin to create the collection without a single line of code...
As far as I can see you should need to duplicate the update object. The above reduces the database calls from 2 to one and allows you to reuse the database connection, potentially anywhere else in the application which would help to speed things up. Also don't store your MongoDB credentials directly in the code.
This is my first time using firebase. My database looks like:enter image description here
I have the key and would like to remove the node for that key:
var dataKey = $("#trainClicked").attr("data-key");
var ref = database.ref("trains/" + changeTrain);
ref.on('value', function (snapshot) {
console.log(snapshot)
if (snapshot === null) {
console.log("does not exist")
} else {
return database.ref().remove(dataKey)
}
});
This removes the entire database and gives an error:
return database.ref().remove(dataKey)
I have read through firebase docs and through many posts here but I still cant get it to work. Thanks in advance.
Reference.remove() doesn't take any arguments, but instead removes the data at the reference you call it on. Since you're calling remove() on the root of the database, all data gets removed.
You're looking for ref.remove() or snapshot.ref.remove() here.
Simply run an empty .set() command on that node. That will delete everything. If you run an empty .set() you will delete your entire database. :-)
This is the code that works:
var dataKey = $("#train-clicked").attr("data-key");
var ref = database.ref("trains/" + dataKey)
ref.once('value', function (snapshot) {
if (snapshot === null) {
console.log("does not exist")
} else {
snapshot.ref.remove();
}
Changing the snapshot.ref.remove() line but also changing ref.on to ref.once.... when it was .on it worked once but it was giving an error because it was trying to read something that wasnt there any more (I think).
This question already has answers here:
Create an empty child record in Firebase
(2 answers)
Closed 6 years ago.
I'm working on a chrome extension and pushing data from a Google-authenticated user to Firebase.
I'm listening for a message coming from another JS file and when it comes in, I want to take the "user profile" object (request) and tack on a property to it, called "visitedLinks". visitedLinks should be set to an empty object.
I do 3 console.logs throughout the code, and in all three cases, the console.logs show "visitedLinks" set to an empty object, yet when I push to Firebase, "visitedLinks" isn't a property.
//Relevant 3 console statements are the following
//console.log('request.accountData = ', request.accountData)
//console.log('userObject test #1 = ', userObject)
//console.log('userObject = ', userObject)
var rootRef = new Firebase("https://search-feed-35574.firebaseio.com/");
if (localStorage.userIsAuthenticated) {
console.log('user is authenticaled')
chrome.runtime.onMessage.addListener(
//listen for messages
function(request, sender, sendResponse) {
//url is coming in from a content script, use localStorage.uid to make database call
if (request.url) {
console.log('message coming from content script')
var uid = localStorage.uid;
var url = request.url;
var userRef = rootRef.child(uid);
newLinkRef = userRef.push(url);
//otherwise, we're getting a message from popup.js, meaning they clicked it again, or they've signed in for the first time
} else {
console.log('message coming from popup')
//here we're passing in all the data from the person's Google user account
var googleUID = request.accountData.uid
//then we make a new object with the key as their google UID and the value all the account data
request.accountData.visitedLinks = {}
console.log('request.accountData = ', request.accountData)
var userObject = {};
userObject[googleUID] = request.accountData;
console.log('userObject test #1 = ', userObject)
//here were checking to see if the UID is already a key in the database
//basically, if they've ever logged in
rootRef.once('value', function(snapshot) {
if (snapshot.hasChild(googleUID)) {
//user has authenticated before, they just happened to click the popup again
console.log('already authenticated, you just clicked the popup again')
} else {
console.log('users first DB entry');
//if they're not in the database yet, we need to push userObject to the DB
//and push their current url to the publicLinks array
rootRef.set(userObject, function(error) {
console.log('error = ', error);
console.log('userObject after DB insert = ', userObject)
});
}
})
}
//figure out if this user has entries in the DB already
//just push the link information onto the "links" node of the db object
//if not, push a ref (to the right place)
// console.log(sender)
});
} else {
console.log('user isnt authenticated')
}
So it turns out that you can't insert empty objects into the database. Similar question answered here: Create an empty child record in Firebase
I have a data like this:
"customers": {
"aHh4OTQ2NTlAa2xvYXAuY29t": {
"customerId": "xxx",
"name": "yyy",
"subscription": "zzz"
}
}
I need to retrive a customer by customerId. The parent key is just B64 encoded mail address due to path limitations. Usually I am querying data by this email address, but for a few occasions I know only customerId. I've tried this:
getCustomersRef()
.orderByChild('customerId')
.equalTo(customerId)
.limitToFirst(1)
.once('child_added', cb);
This works nicely in case the customer really exists. In opposite case the callback is never called.
I tried value event which works, but that gives me whole tree starting with encoded email address so I cannot reach the actual data inside. Or can I?
I have found this answer Test if a data exist in Firebase, but that again assumes that you I know all path elements.
getCustomersRef().once('value', (snapshot) => {
snapshot.hasChild(`customerId/${customerId}`);
});
What else I can do here ?
Update
I think I found solution, but it doesn't feel right.
let found = null;
snapshot.forEach((childSnapshot) => {
found = childSnapshot.val();
});
return found;
old; misunderstood the question :
If you know the "endcodedB64Email", this is the way.:
var endcodedB64Email = B64_encoded_mail_address;
firebase.database().ref(`customers/${endcodedB64Email}`).once("value").then(snapshot => {
// this is getting your customerId/uid. Remember to set your rules up for your database for security! Check out tutorials on YouTube/Firebase's channel.
var uid = snapshot.val().customerId;
console.log(uid) // would return 'xxx' from looking at your database
// you want to check with '.hasChild()'? If you type in e.g. 'snapshot.hasChild(`customerId`)' then this would return true, because 'customerId' exists in your database if I am not wrong ...
});
UPDATE (correction) :
We have to know at least one key. So if you under some circumstances
only know the customer-uid-key, then I would do it like this.:
// this is the customer-uid-key that is know.
var uid = firebase.auth().currentUser.uid; // this fetches the user-id, referring to the current user logged in with the firebase-login-function
// this is the "B64EmailKey" that we will find if there is a match in the firebase-database
var B64EmailUserKey = undefined;
// "take a picture" of alle the values under the key "customers" in the Firebase database-JSON-object
firebase.database().ref("customers").once("value").then(snapshot => {
// this counter-variable is used to know when the last key in the "customers"-object is passed
var i = 0;
// run a loop on all values under "customers". "B64EmailKey" is a parameter. This parameter stores data; in this case the value for the current "snapshot"-value getting caught
snapshot.forEach(B64EmailKey => {
// increase the counter by 1 every time a new key is run
i++;
// this variable defines the value (an object in this case)
var B64EmailKey_value = B64EmailKey.val();
// if there is a match for "customerId" under any of the "B64EmailKey"-keys, then we have found the corresponding correct email linked to that uid
if (B64EmailKey_value.customerId === uid) {
// save the "B64EmailKey"-value/key and quit the function
B64EmailUserKey = B64EmailKey_value.customerId;
return B64UserKeyAction(B64EmailUserKey)
}
// if no linked "B64EmailUserKey" was found to the "uid"
if (i === Object.keys(snapshot).length) {
// the last key (B64EmailKey) under "customers" was returned. e.g. no "B64EmailUserKey" linkage to the "uid" was found
return console.log("Could not find an email linked to your account.")
}
});
});
// run your corresponding actions here
function B64UserKeyAction (emailEncrypted) {
return console.log(`The email-key for user: ${auth.currentUser.uid} is ${emailEncrypted}`)
}
I recommend putting this in a function or class, so you can easily call it up and reuse the code in an organized way.
I also want to add that the rules for your firebase must be defined to make everything secure. And if sensitive data must be calculated (e.g. price), then do this on server-side of Firebase! Use Cloud Functions. This is new for Firebase 2017.
UPDATE: In a nutshell, I would like to use the Master key, because I need to write an other user object with my current user, but I don't want to override all security, I just wanna use it in one function. The accepted answer in this question gave a very nice starting point, however I couldn't make it to work. It's the last code block in this question.
I have two separated functions. The first is pure objective-c, it deletes users from the currentUser's firstRelation. It worked well without any problems until i added a different CloudCode function into a different view controller. The CloudCode function uses the master key and adds currentUser to otherUser's sampleRelation & adds otherUser to currentUser's sampleRelation (firstRelation and sampleRelation is two different column inside the User class).
So the problem is when I delete a user from currentUser's firstRelation (with current user) my app crashes, because the user must be authenticated via logIn or signUp. Actually i don't understand this, because in this case I'm writing the currentUser with the currentUser instead of another user, so it must work without any problems (and worked before the CloudCode).
I'm almost sure that it's because I'm using the master key with the CloudCode, but have no idea how can I avoid it. Everything else is still working, for example I can upload images with currentUser.
Here is the code that I'm using for the CloudCode, JavaScript is totally unknown for me, maybe somebody will see what causes the problem.
Parse.Cloud.define('editUser', function(request, response) {
Parse.Cloud.useMasterKey();
var userQuery = new Parse.Query(Parse.User);
userQuery.get(request.params.userId)
.then(function (user) {
var relation = user.relation("sampleRelation");
relation.add(request.user);
// chain the promise
return user.save();
}).then(function (user) {
var currentUser = request.user;
var relation = currentUser.relation("sampleRelation");
relation.add(user);
// chain the new promise
return currentUser.save();
}).then(function () {
response.success();
}, function (error) {
response.error(error);
});
});
It crashes when i try to remove the object:
PFUser *user = [self.friends objectAtIndex:indexPath.row];
PFRelation *myFriendsRel = [self.currentUser relationForKey:#"simpleRelation"];
if ([self isFriend:user]) {
for (PFUser *friendName in self.friends) {
if ([friendName.objectId isEqualToString:user.objectId]){
[self.friends removeObject:friendName];
break; // to exit a loop
}
}
// remove from parse
[myFriendsRel removeObject:user];
NSLog(#"deleted: %#", user.username);
}
[self.currentUser saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error){
NSLog(#"Error %# %#", error, [error userInfo]);
}
}];
This is the newest attempt, that based Fosco's answer from the other question. It works, but the same way as the earlier versions.
Parse.Cloud.define('editUser', function(request, response) {
var userId = request.params.userId;
var User = Parse.Object.extend('_User'),
user = new User({ objectId: userId });
var currentUser = request.user;
var relation = user.relation("friendsRelation");
relation.add(currentUser);
user.save(null, { useMasterKey:true}).then(function(user) {
response.success(user);
}, function(error) {
response.error(error)
});
});
At a quick glance it looks like its failing because you're trying to remove an object from an array whilst it is being iterated. I know this causes a crash in Objective C regardless of whether you're using Parse objects or not.
Try re-writing this segment:
for (PFUser *friendName in self.friends) {
if ([friendName.objectId isEqualToString:user.objectId]){
[self.friends removeObject:friendName];
break; // to exit a loop
}
}
To something like this:
NSMutableArray *tempArray = [[NSMutableArray alloc]init];
for (PFUser *friendName in self.friends) {
if (![friendName.objectId isEqualToString:user.objectId]) {
[tempArray addObject:friendName];
}
self.friends = [NSArray arrayWithArray:tempArray];
Again, only had a quick glance so not 100% if that is your problem but it looks like it, let me know if it helps