Firestore Cloud functions looping for unknown reason - firebase

I have some basic code to retrieve data from my firestore data base. I am getting the correct data but for some reason that operation appears to be occurring twice.
The logs show all the documents printed out twice. This could be really troublesome with more complex operations. I feel life I'm probably doing something goofy.
exports.deleteProject = functions.firestore.document('{userID}/projects/easy/{projectID}').onDelete(event => {
.........
console.log(db)
var collectionRef = db.collection(userID).doc(xxx).collection(yyy);
console.log(collectionRef)
var getDoc = collectionRef.get()
.then(snapshot => {
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
});
})
.catch(err => {
console.log('Error getting documents', err);
});
}
The "........" is just string stuff to reference to proper point in that database
Below "=>" indicates a doc pointing to its data. I cleaned it up for brevity.
Logs:
12:04:37.820 PM
info
deleteProject
tail => {
info => {
xxxxxxxxxxxxxxx => {
tail => {
info => {
xxxxxxxxxxxxxxx => {
yyyyyyyyyyyyyyy => {

Related

How to ensure that a cloud function is running every time a new document gets created?

I am uploading my questions and answers to my quiz to Firestore. For that I am using following function:
const firestore = admin.firestore();
const settings = { timestampsInSnapshots: true };
firestore.settings(settings);
if (data && (typeof data === "object")) {
Object.keys(data).forEach(docKey => {
var data_to_push = data[docKey];
data_to_push['category'] = "Wirtschaft";
firestore.collection(collectionKey).add(data_to_push).then((res) => {
console.log("Document " + docKey + " successfully written!");
}).catch((error) => {
console.error("Error writing document: ", error);
});
});
This function works fine, all the documents I need are created but whenever a document get created I have another function that is running:
// This function adds the doc ids of newly created questions to an arrayList
exports.AddKeyToArray = functions.region('europe-west1').firestore.document('Questions/{nameId}').onCreate(async (snp, context) => {
console.log(snp.id);
console.log(context.params);
await db.collection("Questions_keys").doc(snp.data().category).update({ "questions": admin.firestore.FieldValue.arrayUnion(snp.id) }).then(() => {
return console.log("Key added");
}).catch(async (e) => {
console.log(e);
if (e.code === 5) {
await db.collection("Questions_keys").doc(snp.data().category).set({ "questions": admin.firestore.FieldValue.arrayUnion(snp.id) }).then(() => {
return console.log("First time key added");
}).catch(e => {
return console.log(e);
})
}
})
return "okay";
})
This function basically gets the document id of the previously added question/answer and creates an array with all the document ids of that quiz category (so I then later can get a random question without much reading operations). The problem is that not all document ids are added to the array so I wanted to know if there is a better way to ensure that all the document ids are added into the array.
I upload sometimes 500 documents at once, would be a solution to reduce the documents I upload at once to ensure a better performance of the second function?
Any help is much appreciated!
I suggest that rather than using cloud functions here is to create another collection in your database. This way you can add more questions to that collection easily. This design will increase performance as what you will need is only query the new collection directly and this way you will avoid all the complication needed to manage and work with Cloud Functions.
With help I found a solution: The following function uploads data to firestore and gets the ids of the documents and sets it to an array:
...
const firestore = admin.firestore();
const settings = { timestampsInSnapshots: true };
firestore.settings(settings);
if (data && (typeof data === "object")) {
Object.keys(data).forEach(async docKey => {
var data_to_push = data[docKey];
data_to_push['category'] = "Deutschland";
await firestore.collection(collectionKey).add(data_to_push).then(async (res) => {
var key = (res['_path']['segments'][1]);
await firestore.collection("Questions_keys").doc(data_to_push['category']).update({ "questions": admin.firestore.FieldValue.arrayUnion(key) }).then(() => {
console.log("Key added: " + key);
}).catch(async (e) => {
if (e.code === 5) {
await firestore.collection("Questions_keys").doc(data_to_push['category']).set({ "questions": admin.firestore.FieldValue.arrayUnion(key) }).then(() => {
return console.log("First time key added");
}).catch(e => {
return console.log(e);
})
}
console.log(e);
})
}).catch((error) => {
console.error("Error writing document: ", error);
});
});
}

Having trouble updating a document in Vue JS using Firebase's Firestore

I'm building a simple single page web app using Vue JS (Vue Cli 3) with Firebase's Firestore as the back-end database. I've managed to add, and delete records with ease. I'm running into an issue when trying to 'update' a user's details.
My code for this function is as follows:
saveEditUser() {
db.collection('users')
.where('email', '==', this.form.email)
.get()
.then(snap => {
snap.forEach(doc => {
doc.ref.update({
email: this.form.email
})
})
})
.then(() => {
console.log('Successfully updated the record')
})
.catch(error => {
console.error('There was an error editing the record: ' + error)
})
}
Some things that I've discovered during my attempts at debugging this:
This is not a scope issue where 'this' in the this.form.email is not available inside the forEach loop.
I thought this could be the case and so I declared a 'const vm = this' before the loop and tried to use vm.form.email, but no dice.
Also, when I try to update the email field to a simple string like 'abc' instead of a dynamic value such as this.form.email, it works!
After several spent hours on this ridiculous problem, I am officially stumped folks. Please send help!
Thank you.
TL;DR: the OP was updating a record with the same value, hence nothing appeared to change in the Firestore DB. However, in his code, there was the need to return the promise returned by the single asynchronous operation (or by the set of asynchronous operations)
Since your are potentially going to execute several asynchronous operations to the database in parallel (using the update() method, which return a promise, see doc) , you need to use Promise.all(), as follows.
saveEditUser() {
const email = this.form.email;
const= promises = [];
db.collection('users')
.where('email', '==', email )
.get()
.then(snap => {
snap.forEach(doc => {
promises.push(
doc.ref.update({
email: email //Actually the problems comes from here, see below
})
);
return Promise.all(promises);
})
})
.then(() => {
console.log('Successfully updated the record')
})
.catch(error => {
console.error('There was an error editing the record: ' + error)
})
}
If you are 100% sure your query will return only one doc you could update the doc directly, but then you have to return the promise returned by update(), as follows:
saveEditUser() {
const email = this.form.email;
db.collection('users')
.where('email', '==', email)
.get()
.then(snap => {
return snap.docs[0].ref.update({
email: email
});
})
.then(() => {
console.log('Successfully updated the record')
})
.catch(error => {
console.error('There was an error editing the record: ' + error)
})
}
Note: by declaring the email const at the beginning of the function, you should not encounter any problem of context anymore.
Update following our comments and discussion:
Actually you are updating with the SAME value of email. So it is normal you don't see any result. Just try to update with another value, like in the following code:
saveEditUser() {
const email = this.form.email;
db.collection('users')
.where('email', '==', email)
.get()
.then(snap => {
return snap.docs[0].ref.update({
email: 'john.doe#gmail.com'
});
})
.then(() => {
console.log('Successfully updated the record')
})
.catch(error => {
console.error('There was an error editing the record: ' + error)
})
}
If you want to test with a value from your form, just use two fields: one with the value to query and one with the new value, like:
<input v-model="form.mail" placeholder="mail to search for">
<input v-model="form.newMail" placeholder="new email">
.....
saveEditUser() {
const emailToQuery = this.form.email;
const newEmail = this.form.newMail;
db.collection('users')
.where('email', '==', emailToQuery )
.get()
.then(snap => {
return snap.docs[0].ref.update({
email: newEmail
});
})
.then(() => {
console.log('Successfully updated the record')
})
.catch(error => {
console.error('There was an error editing the record: ' + error)
})
}

Can't access to a collection from a looped collection

I want to access to a collection inside another collection in a for loop.
Is it possible? I'm getting an error at the 4th line Error getting documents TypeError: cookUser is not a function
var mealsOnline = [];
return db.collection('users').get().then(function (snapshot) {
snapshot.forEach(cookUser => {
cookUser.collection('meals').get().then(function (snapshot2) {
snapshot2.forEach(meal => {
if (meal.data().portion > 0) {
var mealObject = meal.data();
mealObject.id = meal.id;
mealObject.address = cookUser.data().address;
mealObject.cookName = cookUser.data().displayName;
mealsOnline.push(mealObject);
}
});
});
});
return Promise.all(mealsOnline);
}).catch(err => {
console.log('Error getting documents', err);
});
With the forEach() method, your cookUser object is a QueryDocumentSnapshot.
As detailed in the documentation (link above), "QueryDocumentSnapshot offers the same API surface as a DocumentSnapshot". Therefore you should use the ref abstract type of a DocumentSnapshot, as follows:
snapshot.forEach(cookUser => {
cookUser.ref.collection('meals').get().then(snapshot2 => {
.....
})
})
https://firebase.google.com/docs/reference/js/firebase.firestore.QuerySnapshot#forEach

ionic3 SQLite unit testing

I'm using ionic3 and am trying to unit test (using Jasmine/karma) the following code. Can anyone offer any guidance.
createTable(databaseName: string): Promise<any> {
let sql = 'CREATE TABLE IF NOT EXISTS table)';
return new Promise((resolve, reject) => {
this.sqlite.create({
name: databaseName + '.db',
location: 'default'
}).then((db: SQLiteObject) => {
db.executeSql(sql, [])
.then(() => {
console.info('Executed SQL');
})
.catch((e) => {
console.error('createTable Error', e.message);
reject(e);
});
});
})
}
Here's my work in progress attempt to test
describe('createTable()', () => {
let db: SQLiteObject;
it('Should call function createTable', () => {
spyOn(service.sqlite, 'create').and.returnValue(Promise.resolve(
));
let databaseName = 'testDatabase';
service.createEncounterTable(databaseName);
expect(service.sqlite.create).toHaveBeenCalled();
});
});
I'm getting the following error return ERROR: 'Unhandled Promise rejection:', 'Cannot read property 'executeSql' of undefined', and dont seem to be able to gain access to executeSql.
As far as I can tell, from the lack of online resource on testing SQLite I have to assume that most people don't test this. Thanks in advance to any guidance.

What is the suitable query for the following case?

This is my database structure:
I am trying to list all users with "locale" equal to "Cairo, Egypt" so I made the following query:
exports.calculateMatches = functions.https.onRequest((request, response) => {
// Access users' profiles that are located in the locale of the requesting user
databaseRef.child("users").orderByChild("locale").equalTo(request.query.locale).once("value")
.then(snap => {
snap.forEach(profile => {
console.log(profile);
});
});
});
Note this function is deployed to firebase cloud functions and this is what I get in the logs:
HTTPS type functions require that you send a response to the client in order to terminate the function. Without that, they will always time out, and the client will be waiting the whole time.
For example:
const databaseRef = admin.database().ref('')
exports.calculateMatches = functions.https.onRequest((request, response) => {
databaseRef.child("users").orderByChild("locale").equalTo(request.query.locale).once("value")
.then(snap => {
const profiles = []
snap.forEach(profile => {
profiles.push(profile.val())
});
response.send(profiles)
})
.catch(error => {
response.status(500).send(error)
});
});

Resources