Query a reference field in Firestore document - javascript

I am trying to write a function that will, after data in a document (within a Firestore 'artists' collection) is changed, will have Google Cloud Functions find all the documents in another collection ('shows') that have a reference field ('artist') that points to the document (within the 'artists' collection) that was just changed.
I can't seem to figure out how to query the reference field. Ive tried everything from using the ID of the artist document, to the path, to the full URL. But I get an error in the Google Cloud Function console:
Error getting documents Error: Cannot encode type ([object Undefined]) to a Firestore Value
Here's a sample of my code:
exports.updateReferenceArtistFields = functions.firestore
.document('artists/{artistId}').onWrite(event => {
var artistRef = event.data.data();
var artistId = artistRef.id;
var ShowsRef = firestore.collection('shows');
var query = ShowsRef.where('artist', '==', artistId).get()
.then(snapshot => {
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
});
})
.catch(err => {
console.log('Error getting documents', err);
});
});

I would get the artistId from the params directly like this:
var artistId = event.params.artistId;
Example:
exports.updateReferenceArtistFields = functions.firestore
.document('artists/{artistId}').onWrite(event => {
var artistId = event.params.artistId;
var showsRef = firestore.collection('shows');
var query = showsRef.where('artist', '==', artistId).get()
.then(snapshot => {
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
});
})
.catch(err => {
console.log('Error getting documents', err);
});
});

Related

How do I return each collection and its documents in firestore with cloud functions?

Would like to return an array that has [1year, 1month, etc] and each of those are arrays that contain each document.
Currently, this returns an empty array but when I print the size of the snapshots I get the correct values. Not sure if i'm using push() correctly or if this is an async issue. Thanks.
exports.getStockPrices = functions.https.onRequest((req, res) => {
cors(req, res, () => {
const currentUser = {
token: req.headers.authorization.split('Bearer ')[1]
};
// ! this is a post request
admin
.auth()
.verifyIdToken(currentUser.token)
.then(decodedToken => {
// keep this just in case we want to add anything to do with the user
const user = decodedToken;
// array of collections e.g [1year, 1mo, etc]
const data = [];
// array of documents e.g [18948901, 1984010471, etc]
const documents = [];
db.collection('historical')
.doc(`${req.body.ticker}`)
.listCollections()
.then(collections => {
// each collection is the 1year, 1mo, etc
collections.forEach(collection => {
collection.get().then(querySnapshot => {
console.log('number of documents: ' + querySnapshot.size);
querySnapshot.forEach(doc => {
// doc.data is each piece of stock data
documents.push(doc.data());
});
// each document e.g 1year, 1mo, etc
data.push(documents);
});
});
return data;
})
.then(data => {
return res.json({ data });
})
.catch(err => {
console.log(err);
return res.status(500).send({ error: 'error in getting data' });
});
})
.catch(err => {
console.log(err);
return res.status(500).send({
error: 'error authenticating user, please try logging in again'
});
});
});
});
Due the nature of async calls, your return occurs before your array is being filled.
You can try my example, my firebase function is defined as async this allows me to use await, this statement allows to add a kind of sync for your firestore operations by waiting for the promises.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.eaxmple = functions.https.onRequest(async (req, res) => {
var datax = []
var collections = await db.collection('collection').doc('docid').listCollections()
for (collection in collections) {
content = await collections[collection].get().then(querySnapshot => {
console.log('number of documents: ' + querySnapshot.size);
return querySnapshot.docs.map(doc => doc.data());
});
datax.push(content)
}
return res.json({datax});
});

Problem retrieving download url of Firebase storage image

I'm trying to retrieve the download URL of a file stored in my firebase storage. I'm storing some data along with each file, hence the firestore "files" collection. Each doc in the files collection is one file in the storage:
const [loading, setLoading] = useState(true);
const [itemData, setItemData] = useState(['']);
const dataRef = firebase.firestore().collection("files");
const fileRef = firebase.storage().ref();
// Load initial data
useEffect(() => {
return dataRef.get().then(querySnapshot => {
const list = [];
querySnapshot.forEach(doc => {
const { name, uri } = doc.data();
let imageRef = fileRef.child('/' + uri);
const imageUrl = imageRef
.getDownloadURL()
.then((url) => {
return url;
})
.catch((e) => console.log('Error getting image download URL: ', e));
console.log(imageUrl);
list.push({
id: doc.id,
name,
imageUrl: imageUrl
});
});
setItemData(list);
if (loading) {
setLoading(false);
}
})
.catch(function(error) {
console.log("Error getting documents: ", error);
alert("Error: no document found."); // It doesnt goes here if collection is empty
})
}, []);
Problem is, that console.log(imageUrl); returns a strange object:
But my itemData contains a correct download URL, but nested deeper in some object thing?
Not sure what's going on here. I just need the url so I can display the image.
This is because you are not correctly managing the parallel calls to the asynchronous getDownloadURL() method. You should wait the promises returned by the getDownloadURL() method calls are fulfilled before pushing their result to the list array. You should use Promise.all() for that.
The following should do the trick (untested!):
useEffect(() => {
const initialArray = [];
return dataRef
.get()
.then((querySnapshot) => {
const promises = [];
querySnapshot.forEach((doc) => {
const { name, uri } = doc.data();
let imageRef = fileRef.child('/' + uri);
promises.push(imageRef.getDownloadURL());
initialArray.push({
id: doc.id,
name,
});
});
return Promise.all(promises);
})
.then((urlsArray) => {
// urlsArray has the same number of elements than initialArray
const fullArray = [];
urlsArray.forEach((url, index) => {
const initialObj = initialArray[index];
const finalObj = Object.assign(initialObj, url);
fullArray.push(finalObj);
});
setItemData(fullArray);
if (loading) {
setLoading(false);
}
})
.catch(function (error) {
console.log('Error getting documents: ', error);
alert('Error: no document found.'); // It doesnt goes here if collection is empty
});
}, []);

Update firestore array from another collection

I am trying to update the id color field of a firestore collection from another one, both within the firestore and I am having difficulties.
see my collections ...
1 - colors
2 - imports
this was my last attempt, see if you can get an idea ...
let dataImportRef = firestore.db
.collection(process.env.COLLECTION_IMPORT)
.where("id", "==", importId); //import utility
let dataColorsRef = firestore.db.collection("colors");
//import collection
dataImportRef
.get()
.then((snapshot) => {
snapshot.forEach((doc) => {
let arrayImport = doc.data().documentsDetail;
for (var i = 0; i < arrayImport.length; i++) {
//console.log(arrayImport[i].cor);
let tmpColor = arrayImport[i].cor;
dataColorsRef
.where("name", "==", tmpColor)
.get()
.then((snap) => {
if (!snap.empty) {
snap.forEach((res) => {
//console.log(tmpColor, "=>", res.data().id);
const thing = arrayImport[0];
thing.ref.update({ id_color: res.data().id });
});
}
});
}
});
})
.catch((err) => {
console.log("Error getting documents", err);
});
error: TypeError: Cannot read property 'update' of undefined
any tips on how to update the id_cor field from the colors collection using as key name (colors collection) and color (imports collection)?

check if data exists in firestore document else push to an array

I want to check if the data exists in the firestore document, if it does not, then I want to push it to an array.
In the code below, I want to check if q1,q2,q3,q4,q5 exists in the database. The database is structured like this:
firestore /users/userID/post/PlMGiyQRDZuKGipTu5sl/
{ question:'q2'
answer: 'answer to q2'
}
let questions = [q1,q2,q3,q4,q5];
let questions_subset = [];
questions.forEach((item, index) => {
let query = db.collection('users').doc('userID').collection('post').where('question', '==', item).get();
query.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
questions_subset.push(item);
return;
}
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
});
return
}).catch(err => {
console.log('Error getting documents', err);
});
console.log(questions_subset);
})
Right now, console.log(questions_subset) gives me an empty array. What am I doing wrong?

How to delete document from firestore using where clause

var jobskill_ref = db.collection('job_skills').where('job_id','==',post.job_id);
jobskill_ref.delete();
Error thrown
jobskill_ref.delete is not a function
You can only delete a document once you have a DocumentReference to it. To get that you must first execute the query, then loop over the QuerySnapshot and finally delete each DocumentSnapshot based on its ref.
var jobskill_query = db.collection('job_skills').where('job_id','==',post.job_id);
jobskill_query.get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
doc.ref.delete();
});
});
I use batched writes for this. For example:
var jobskill_ref = db.collection('job_skills').where('job_id','==',post.job_id);
let batch = firestore.batch();
jobskill_ref
.get()
.then(snapshot => {
snapshot.docs.forEach(doc => {
batch.delete(doc.ref);
});
return batch.commit();
})
ES6 async/await:
const jobskills = await store
.collection('job_skills')
.where('job_id', '==', post.job_id)
.get();
const batch = store.batch();
jobskills.forEach(doc => {
batch.delete(doc.ref);
});
await batch.commit();
//The following code will find and delete the document from firestore
const doc = await this.noteRef.where('userId', '==', userId).get();
doc.forEach(element => {
element.ref.delete();
console.log(`deleted: ${element.id}`);
});
the key part of Frank's answer that fixed my issues was the .ref in doc.ref.delete()
I originally only had doc.delete() which gave a "not a function" error. now my code looks like this and works perfectly:
let fs = firebase.firestore();
let collectionRef = fs.collection(<your collection here>);
collectionRef.where("name", "==", name)
.get()
.then(querySnapshot => {
querySnapshot.forEach((doc) => {
doc.ref.delete().then(() => {
console.log("Document successfully deleted!");
}).catch(function(error) {
console.error("Error removing document: ", error);
});
});
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
or try this, but you must have the id beforehand
export const deleteDocument = (id) => {
return (dispatch) => {
firebase.firestore()
.collection("contracts")
.doc(id)
.delete()
}
}
You can now do this:
db.collection("cities").doc("DC").delete().then(function() {
console.log("Document successfully deleted!");
}).catch(function(error) {
console.error("Error removing document: ", error);
});
And of course, you can use await/async:
exports.delete = functions.https.onRequest(async (req, res) => {
try {
var jobskill_ref = db.collection('job_skills').where('job_id','==',post.job_id).get();
jobskill_ref.forEach((doc) => {
doc.ref.delete();
});
} catch (error) {
return res.json({
status: 'error', msg: 'Error while deleting', data: error,
});
}
});
I have no idea why you have to get() them and loop on them, then delete() them, while you can prepare one query with where to delete in one step like any SQL statement, but Google decided to do it like that. so, for now, this is the only option.
If you're using Cloud Firestore on the Client side, you can use a Unique key generator package/module like uuid to generate an ID. Then you set the ID of the document to the ID generated from uuid and store a reference to the ID on the object you're storing in Firestore.
For example:
If you wanted to save a person object to Firestore, first, you'll use uuid to generate an ID for the person, before saving like below.
const uuid = require('uuid')
const person = { name: "Adebola Adeniran", age: 19}
const id = uuid() //generates a unique random ID of type string
const personObjWithId = {person, id}
export const sendToFireStore = async (person) => {
await db.collection("people").doc(id).set(personObjWithId);
};
// To delete, get the ID you've stored with the object and call // the following firestore query
export const deleteFromFireStore = async (id) => {
await db.collection("people").doc(id).delete();
};
Hope this helps anyone using firestore on the Client side.
The way I resolved this is by giving each document a uniqueID, querying on that field, getting the documentID of the returned document, and using that in the delete. Like so:
(Swift)
func rejectFriendRequest(request: Request) {
DispatchQueue.global().async {
self.db.collection("requests")
.whereField("uniqueID", isEqualTo: request.uniqueID)
.getDocuments { querySnapshot, error in
if let e = error {
print("There was an error fetching that document: \(e)")
} else {
self.db.collection("requests")
.document(querySnapshot!.documents.first!.documentID)
.delete() { err in
if let e = err {
print("There was an error deleting that document: \(e)")
} else {
print("Document successfully deleted!")
}
}
}
}
}
}
The code could be cleaned up a bit, but this is the solution I came up with. Hope it can help someone in the future!
const firestoreCollection = db.collection('job_skills')
var docIds = (await firestoreCollection.where("folderId", "==", folderId).get()).docs.map((doc => doc.id))
// for single result
await firestoreCollection.doc(docIds[0]).delete()
// for multiple result
await Promise.all(
docIds.map(
async(docId) => await firestoreCollection.doc(docId).delete()
)
)
delete(seccion: string, subseccion: string)
{
const deletlist = this.db.collection('seccionesclass', ref => ref.where('seccion', '==', seccion).where('subseccion', '==' , subseccion))
deletlist.get().subscribe(delitems => delitems.forEach( doc=> doc.ref.delete()));
alert('record erased');
}
The code for Kotlin, including failure listeners (both for the query and for the delete of each document):
fun deleteJobs(jobId: String) {
db.collection("jobs").whereEqualTo("job_id", jobId).get()
.addOnSuccessListener { documentSnapshots ->
for (documentSnapshot in documentSnapshots)
documentSnapshot.reference.delete().addOnFailureListener { e ->
Log.e(TAG, "deleteJobs: failed to delete document ${documentSnapshot.reference.id}", e)
}
}.addOnFailureListener { e ->
Log.e(TAG, "deleteJobs: query failed", e)
}
}

Categories