Get field from subcollection's parent document - node.js

I am creating a message application with Firestore
At the moment i have created a function that gets triggered when a user writes a message in a messageRoom. Each messageRoom document has a subcollection called messages which contains the messages of the room. The messageRoom document itself contains data related to the messageRoom itself e.g. users who are part of the messageRoom. The messageRoom contains a field called members which holds info about the user who are a part of the messageRoom
I want to get access to the data in the messageRoom document and not the messages subcollection. At the moment the following code does not work. An error tells me that the members field is undefined. What have i done wrong?
exports.sendNotification = functions
.firestore
.document('messageRooms/{messageRoomId}/messages/{messageId}')
.onCreate(
async (snapshot) => {
/*
Does something with the documents in the subcollection
*/
//Get the DocumentReference of the parent doc
const parentDocId = snapshot.ref.parent.id;
admin.firestore().collection('messageRooms').doc(parentDocId).get('members').get().then(doc => {
if (doc.exists) {
console.log("Document data:", doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
return; //return added to prevent crash
}).catch(error => {
console.log("Error getting document:", error);
});
}
);

I think the problems is here:
//Get the DocumentReference of the parent doc
const parentDocId = snapshot.ref.parent.id;
snapshot is DocumentSnapshot, so ref property give us DocumentReference. Property parent of document reference is CollectionReference (Google Doc). So you have to find not parent but "grandparent" :) I may say. Maybe try:
const parentDocId = snapshot.ref.parent.parent.id;
However I would not use .id property here just to get reference in .doc in next line. parent property already provides reference (doc). I don't think that argument in get is changing anything, so I would use .data().messages to get to the field. In the end I would use:
const parentDocRef = snapshot.ref.parent.parent;
parentDocRef.get().then(doc => { console.log(doc.data().members)})
I hope it will help!

Related

Firestore add Document ID to Data via functions

I'm looking to add the Firestore ID to the DocumentData, so that I can easily utilize the ID when referring to rows in a table, without having to use document.data().property everytime I call a property of a document. Instead, I want to be able to call document.id.... document.property... and so on.
Is there an easy way to do this? Possibly with a Cloud Function that adds the auto-generated ID to the document data?
Thanks!
Example:
export const getSpaces = async () => {
const spaceDocs = await getDocs(spacesCollection)
spaceDocs.docs.forEach((spaceDoc) => {
const spaceID = spaceDoc.id
const spaceData = spaceDoc.data()
console.log(spaceID)
spaces.value.push(spaceData)
})
}
Now, the spaces array has objects containing the data of the documents. But, I loose the ability to reference the ID of a document.
Alternatively, I can add the entire document to the array, but following that, I'll have to access the properties by always including the data() in between. I.e. space.data().name
I'm certain, theres a better way
You don't need Cloud Functions to add the document ID to the data of that document. If you look at the third code snippet in the documentation on adding a document, you can see how to get the ID before writing the document.
In some cases, it can be useful to create a document reference with an auto-generated ID, then use the reference later. For this use case, you can call doc() [without any arguments]:
const newCityRef = db.collection('cities').doc(); // 👈 Generates a reference, but doesn't write yet
// Later...
const res = await newCityRef.set({
newCityRef.id, // 👈 Writes the document ID
// ...
});
As others have commented, you don't need to store the ID in the document. You can also add it to your data when you read the documents, with:
spaceDocs.docs.forEach((spaceDoc) => {
const spaceID = spaceDoc.id
const spaceData = spaceDoc.data()
console.log(spaceID)
spaces.value.push({ id: spaceID, ...spaceData })
})
With this change your spaces contains both the document ID and the data of each document.
A DocumentSnapshot has id property that you are looking for. If you add something within the document, then you'll need to access the data first with doc.data().field_name.
Working on another problem, I came across the solution. Quite simple actually:
When declaring the new Object, you can add ...doc.data() to add all the properties of the DocumentData to the newly created Object, after initialising it with an id. This works for me anyways. Case closed.
onSnapshot(profilesCollectionRef, (querySnapshot) => {
const profilesHolder = [];
querySnapshot.forEach((doc) => {
const profile = {
id: doc.id,
...doc.data(),
}
profilesHolder.push(profile);
console.log(profile);
});
profiles.value = profilesHolder;
});
onSnapshot(doc(db, "profiles", userId), (doc) => {
const newProfile = {
id: doc.id,
...doc.data(),
}
myProfile.value = newProfile as Profile;
});

Firebase cloud functions : Delete multiple documents on userDelete

I would like to be able to delete all of a user's documents when deleting his account. I would like to do this on the backend side with cloud functions.
This similar post doesn't really answer my question (because I need to delete documents from multiple collections)
Here is the structure of my collections:
users
| uid
| email: userEmail#test.com
orders
| docId
| createdBy: uid
| …
| …
Here is my function:
const userDeleted = functions.auth.user().onDelete(user => {
// get user doc in users collection then remove it
const usersRef = db.collection("users").doc(user.uid);
usersRef.delete();
// get all user’s doc in orders collection
const ordersRef = db.collection("orders")
.where("createdBy", "==", user.uid)
.get()
.then(snapshot => {
snapshot.forEach(doc => {
ordersRef.doc(doc.id).delete();
});
})
.catch(err => {
console.log("Error getting documents", err);
});
return Promise.all(usersRef, ordersRef);
})
I need to iterate through the orders collection and find the documents that have the correct uid in the "createdBy" property and then delete them. I feel like I can't delete multiple things at once during onDelete, and I couldn't find anything concrete in the documentation.
Firebase logs return to me this error : object is not iterable (cannot read property Symbol(Symbol.iterator)) at Function.all when deleting a user.
The problem occurs on the following line: return Promise.all(ordersRef);
What am I doing wrong?
Promise.all expects an array of promises, so you need to put [] around the arguments:
return Promise.all([usersRef, ordersRef]);

sails beforeUpdate doesn't contains the object Id

I need to add some condition to beforeUpdate, sending to a specific event only if certain property was updated, and need to know its previous value.
I need the object Id to do that, but it is not appears in beforeUpdate values. How can I know the object id?
beforeUpdate(values, cb) {
// if another field was updated except of the lastActive field
if (values.length > 1) {
const id = this.update.arguments[0];
// id is undefined now. how can I get user id?
const oldUser = User.findOne({id: values.id});
// because I need to know user old values in sendUser function
myFunction.sendUser(oldUser);
}
cb();
}

Strange result when nesting query of two different collections

I have a need to check two separate collections in Mongo to see if a phone number exists.
I first created a global variable called 'ownerId'
I then look in one collection call 'Profile'. If the email value I pass exists in the 'emails' array of a document in that collection, I fill the 'ownerId' variable I created with a value in that document called 'owner_id'.
I then look in another collection called 'User', which has a single email field. If the email value I pass exists in a document in that collection, I fill the 'ownerId' variable I created with the '_id' of that document.
I have my queries nested in a couple 'then()' statements.
Here is my code:
Profile.findOne({'emails.email_address':req.body.invited_email}, function(err, result){
if(result)
ownerId = result.owner_id;
}).then(function(){
User.findOne({'email':req.body.invited_email}, function(err, result2){
if(ownerId !== null)
ownerId = result2._id;
})
}).then(function(){
console.log(' --------------- THIS IS AN EXISTING EMAIL OWNER ID: ' + ownerId);
})
The result is not as I expect.
If the 'Profile' query is true and finds a match, then it will console log the ownerId with a value.
If the second 'User' query is true, but there is not match for the 'Profile' it will console log 'null'. I expect it to console log the _id value of the User result.
Can anyone see the error in my logic?
I wouldn't mix callbacks and promises. I would recommend using async/await for this. It makes asynchronous code look synchronous. For example:
let owner_id;
try {
let profile = await Profile.findOne({ 'emails.email_address': req.body.invited_email });
let user = await User.findOne({ 'email': req.body.invited_email }):
if (profile) {
owner_id = profile.owner_id;
} else if (user) {
owner_id = user.owner_id;
} else {
console.log('no owner id found');
}
} catch(err) {
console.log(err);
}
The code is wrapped in try/catch to handle errors, just like the catch() method for ordinary promises. The functions using await needs the async keyword.
await is used to wait for promises to finish. No callbacks are needed. You can see the code looks like ordinary synchronous code.

Hoodie - Update a CouchDB document (Node.js)

I'm handling charges and customers' subscriptions with Stripe, and I want to use these handlings as a Hoodie plugin.
Payments and customer's registrations and subscriptions appear normally in Stripe Dashboard, but what I want to do is update my _users database in CouchDB, to make sure customer's information are saved somewhere.
What I want to do is updating the stripeCustomerId field in org.couchdb.user:user/bill document, from my _users database which creates when logging with Hoodie. And if it is possible, to create this field if it does not exist.
In hoodie-plugin's document, the update function seems pretty ambiguous to me.
// update a document in db
db.update(type, id, changed_attrs, callback)
I assume that type is the one which is mentioned in CouchDB's document, or the one we specify when we add a document with db.add(type, attrs, callback) for example.
id seems to be the doc id in couchdb. In my case it is org.couchdb.user:user/bill. But I'm not sure that it is this id I'm supposed to pass in my update function.
I assume that changed_attrs is a Javascript object with updated or new attributes in it, but here again I have my doubts.
So I tried this in my worker.js:
function handleCustomersCreate(originDb, task) {
var customer = {
card: task.card
};
if (task.plan) {
customer.plan = task.plan;
}
stripe.customers.create(customer, function(error, response) {
var db = hoodie.database(originDb);
var o = {
id: 'bill',
stripeCustomerId: 'updatedId'
};
hoodie.database('_users').update('user', 'bill', o, function(error) {
console.log('Error when updating');
addPaymentCallback(error, originDb, task);
});
db.add('customers.create', {
id: task.id,
stripeType: 'customers.create',
response: response,
}, function(error) {
addPaymentCallback(error, originDb, task);
});
});
}
And between other messages, I got this error log:
TypeError: Converting circular structure to JSON
And my file is not updated : stripeCustomerId field stays null.
I tried to JSON.stringify my o object, but It doesn't change a thing.
I hope than some of you is better informed than I am on this db.update function.
Finally, I decided to join the Hoodie official IRC channel, and they solved my problem quickly.
Actually user.docs need an extra API, and to update them you have to use hoodie.account instead of hoodie.database(name)
The full syntax is:
hoodie.account.update('user', user.id, changedAttrs, callback)
where user.id is actually the account name set in Hoodie sign-up form, and changedAttrs an actual JS object as I thought.
Kudos to gr2m for the fix; ;)

Resources