Why is there a ReferenceError? Vue Firestore - firebase

Here is my code trying to fetch the user's information from the users collection in Firestore.
import db from "./firebaseInit";
import firebase from "firebase";
export default {
data() {
return {
uid: null,
user: null
};
},
created() {
if (firebase.auth().currentUser) {
// We're logged in
this.uid = firebase.auth().currentUser.uid;
console.log("The uid is: ", this.uid);
}
var docRef = db.collection("users").doc(this.uid);
docRef
.get()
.then(doc => {
if (doc.exists) {
this.user = doc.data(); // This is the line with the ReferenceError
console.log("This user is: ", user);
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
})
.catch(function(error) {
console.log("Error getting document:", error);
});
},
methods: {
updateProfile() {
var docRef = db
.collection("users")
.doc(this.uid)
.update(this.user);
}
}
};
I am getting the following error in the catch block, although I have verified that the doc exists and is getting fetched. However, I seem not to be able to access my user variable defined in the data. This is the error:
Error getting document:
ReferenceError: user is not defined
Please let me know if you have any idea what might be happening.

Related

Firebase listUsers fails to get All users after a certain page

I'm using a pubsub firebase function (cron), and inside this function Im calling firebase auth users, to fill some missing data in a profile collection
Im paginating with the pageToken, the first token passed is undefined then I save it in a config db and read the token to get the next page
The issue is that I have 170K records, and listusers returns an undefined token at the 6th page (6k users) which is frsutrating
here is the code:
functions.pubsub
.schedule('*/30 * * * *')
.onRun(async () => {
const page = firestore.collection('config').doc('pageToken');
const doc = (await page.get()).data();
// Check if last page don't run again
const last = doc?.last;
if (last) return;
// Load page
const pageToken = doc?.pageToken || undefined;
let pageNumber = doc?.pageNumber as number;
return firebaseAdmin
.auth()
.listUsers(1000, pageToken)
.then(async listUsersResult => {
for (const userRecord of listUsersResult.users) {
// Fetch Profile
try {
const profile = await firestore
.collection('profiles')
.doc(userRecord.uid);
// data foramtting here
// compared profile data & fixed data
const payload = JSON.parse(
JSON.stringify({
...profileData,
...{
lastName,
firstName,
language,
...(!userRecord.phoneNumber && {
phone,
}),
},
})
);
// Profile doesn't exist : Create
if (!profileData && payload) {
await profile.create({
...payload,
...{
Migrated: true,
},
});
} else if (profileData && payload) {
const data = compare(profileData, payload);
if (data) {
// Profile exists: Update
await profile.update(data);
if (userRecord.phoneNumber)
await profile.update({
phone: firebaseAdmin.firestore.FieldValue.delete(),
});
}
}
} catch (err) {
functions.logger.error('Some Error', err);
}
}
if (!listUsersResult.pageToken) {
return await firestore
.collection('config')
.doc('pageToken')
.update({
last: true,
});
}
// List next batch of users.
pageNumber++;
return await firestore
.collection('config')
.doc('pageToken')
.update({
pageToken: listUsersResult.pageToken,
pageNumber,
});
});
});
so after in page 6, I have a last:true property added to the firestore however there is 164k data are missing
any idea ?

Cloud Functions: check if document exists always return exists

I'm checking if a document exists by using this cloud function (Typescript).
The problem: the doc doens't exits and it returns exists....
Thank you very much for the help and effort!
export const repeat2 = functions.https.onCall((data, context) => {
console.log(data.message);
console.log(data.count);
const getDocument = admin.firestore().collection('key').doc(data.message).get();
if(getDocument != null) {
console.log('EXISTS');
}
else {console.log("doens't exist");}
return {
repeat_message: data.message,
}
});
get() returns a Promise, not the actual document. Also, you need to use .exists on the resolved value; check the docs here:
var docRef = db.collection("cities").doc("SF");
docRef.get().then(function(doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
if you want to check if is a column in your document is like this:
var docRef = db.collection("cities").doc("SF");
docRef.get().then(function(doc) {
if ('ColumnNAME' in doc.data()) {
//If exists Do somenthing
console.log("Document data:", doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No such column in the document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});

Delete Firestore documents based on where condtiion [duplicate]

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)
}
}

Firebase Cloud Functions Error with CanonicalRegistrationTokenCount

I am trying to send a signup notification to my app after registration and user has been saved in db. I am using firebase cloud functions for this purpose.
I have gotten the device token from firebaseinstanceidservice and saved that in the user's db with the path users/userid/deviceToken and referenced this path in the function like below code:
exports.sendNotification = functions.database.ref("users/{userId}/{instanceId_token}")
I have tried logging the devicetoken just to be sure but in cloud console, I keep getting other attributes for that log like : sendNotification
deviceToken name, sendNotification
deviceToken userId instead of the alphanumeric value saved in db. Is this path wrong?
Here's my full code:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification =
functions.database.ref("users/{userId}/{instanceId_token}")
.onWrite((changes, context) => {
const userId = context.params.userId;
console.log("user-id", userId);
const notificationToken = context.params.instanceId_token;
console.log("deviceToken", notificationToken);
var payload = {
data: {
title: "Welcome to My Group",
message: "You may have new messages"
}
};
return admin.messaging().sendToDevice(notificationToken, payload)
.then(function (response) {
return console.log("Successfully sent message: ", response);
})
.catch(function (error) {
return console.log("Error sending message: ", error);
})
});
Also, the function shows this mildly positive message, after the admin.messaging callback:
Successfully sent message: { results: [ { error: [Object] } ],
canonicalRegistrationTokenCount: 0,
failureCount: 1,
successCount: 0,
multicastId: 8880906162831350000 }
How do I resolve this, I am using an android client?
Here's my db structure:
You should trigger you function on the upper node, as follows. And access the instanceId_token through changes.after.val().
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification =
functions.database.ref("users/{userId}")
.onWrite((changes, context) => {
const userId = context.params.userId;
console.log("user-id", userId);
const notificationToken = changes.after.val().instanceId_token;
console.log("deviceToken", notificationToken);
var payload = {
data: {
title: "Welcome to My Group",
message: "You may have new messages"
}
};
return admin.messaging().sendToDevice(notificationToken, payload)
.catch(function (error) {
console.log("Error sending message: ", error);
})
});
In case you add the instanceId_token ONLY AFTER having created the user, then you should trigger with onUpdate() (which "triggers when data is updated", while onWrite() "triggers when data is created, updated, or deleted").
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification =
functions.database.ref("users/{userId}")
.onUpdate((changes, context) => {
const userId = context.params.userId;
console.log("user-id", userId);
const notificationToken = changes.after.val().instanceId_token;
console.log("deviceToken", notificationToken);
if (notificationToken === undefined) {
console.log("notificationToken === undefined");
return false;
} else {
var payload = {
data: {
title: "Welcome to My Group",
message: "You may have new messages"
}
};
return admin.messaging().sendToDevice(notificationToken, payload)
.catch(function (error) {
console.log("Error sending message: ", error);
})
}
});
Also note that you should not do
return admin.messaging().sendToDevice(notificationToken, payload)
.then(function (response) {
return console.log("Successfully sent message: ", response);
})
.catch(function (error) {
return console.log("Error sending message: ", error);
});
because in this case you are not returning a promise "that resolves when all the async work is done in the function". (see the answer of this question)
So just return the promise returned by sendToDevice (see doc), as follows:
return admin.messaging().sendToDevice(notificationToken, payload)
.catch(function (error) {
console.log("Error sending message: ", error); // here no return
});

What's the best way to check if a Firestore record exists if its path is known?

Given a given Firestore path what's the easiest and most elegant way to check if that record exists or not short of creating a document observable and subscribing to it?
Taking a look at this question it looks like .exists can still be used just like with the standard Firebase database. Additionally, you can find some more people talking about this issue on github here
The documentation states
NEW EXAMPLE
var docRef = db.collection("cities").doc("SF");
docRef.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!");
}
}).catch((error) => {
console.log("Error getting document:", error);
});
OLD EXAMPLE
const cityRef = db.collection('cities').doc('SF');
const doc = await cityRef.get();
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
Note: If there is no document at the location referenced by docRef, the resulting document will be empty and calling exists on it will return false.
OLD EXAMPLE 2
var cityRef = db.collection('cities').doc('SF');
var getDoc = cityRef.get()
.then(doc => {
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
})
.catch(err => {
console.log('Error getting document', err);
});
If the model contains too much fields, would be a better idea to apply a field mask on the CollectionReference::get() result (let's save more google cloud traffic plan, \o/). So would be a good idea choose to use the CollectionReference::select() + CollectionReference::where() to select only what we want to get from the firestore.
Supposing we have the same collection schema as firestore cities example, but with an id field in our doc with the same value of the doc::id. Then you can do:
var docRef = db.collection("cities").select("id").where("id", "==", "SF");
docRef.get().then(function(doc) {
if (!doc.empty) {
console.log("Document data:", doc[0].data());
} else {
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
Now we download just the city::id instead of download entire doc just to check if it exists.
Check this :)
var doc = firestore.collection('some_collection').doc('some_doc');
doc.get().then((docData) => {
if (docData.exists) {
// document exists (online/offline)
} else {
// document does not exist (only on online)
}
}).catch((fail) => {
// Either
// 1. failed to read due to some reason such as permission denied ( online )
// 2. failed because document does not exists on local storage ( offline )
});
2022 answer: You can now use the count() aggregation to check if a document exists without downloading it.
Here is a TypeScript example:
import { getCountFromServer, query, collection, documentId } from '#firebase/firestore'
const db = // ...
async function userExists(id: string): Promise<boolean> {
const snap = await getCountFromServer(query(
collection(db, 'users'), where(documentId(), '==', id)
))
return !!snap.data().count
}
I Encountered Same Problem recently while using Firebase Firestore and i used following approach to overcome it.
mDb.collection("Users").document(mAuth.getUid()).collection("tasks").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
if (task.getResult().isEmpty()){
Log.d("Test","Empty Data");
}else{
//Documents Found . add your Business logic here
}
}
}
});
task.getResult().isEmpty() provides solution that if documents against our query was found or not
Depending on which library you are using, it may be an observable instead of a promise. Only a promise will have the 'then' statement. You can use the 'doc' method instead of the collection.doc method, or toPromise() etc. Here is an example with the doc method:
let userRef = this.afs.firestore.doc(`users/${uid}`)
.get()
.then((doc) => {
if (!doc.exists) {
} else {
}
});
})
Hope this helps...
If for whatever reason you wanted to use an observable and rxjs in angular instead of a promise:
this.afs.doc('cities', "SF")
.valueChanges()
.pipe(
take(1),
tap((doc: any) => {
if (doc) {
console.log("exists");
return;
}
console.log("nope")
}));

Resources