I currently have a Collection | Document | Collection Firestore.
I want to search for a specific collection within the document and if the user ID matches, then update. If the user ID is not in the collection, then I want to add a new collection.
The collection update aspect works, but getting a Promise Rejection when adding to the collection via .set().
if (!query.empty) {
console.log('event leaderboard exists in db');
for (let i in leaderboard) {
const result = leaderboard[i].data();
for (let y in result) {
const userLeaderboard = result[y];
if (userLeaderboard.uid === currentUser.uid) {
// User exists in record and update score
return query.docs[i].ref.update({
score: increaseBy,
});
} else {
// User does not exist and need to add to record
addUserToLeaderboard();
}
}
}
...
const addUserToLeaderboard = async() => {
let userLeaderboard = {
uid: currentUser.uid,
score: currentTrivia.points,
banned: false,
};
const leaderboardRef = db.collection('leaderboard').where(firebase.firestore.FieldPath.documentId(), '==', eventId);
await leaderboardRef.set(userLeaderboard, { merge: true });
}
You can't call set on a query.
If you want to update the one document with the given ID, use:
const leaderboardRef = db.collection('leaderboard').doc(eventId);
await leaderboardRef.set(userLeaderboard, { merge: true });
If you want to update (potentially multiple) items matching a query, you'll need to execute the query, loop over the results, and update each document in turn. But that' doesn't seem to be needed here.
Related
I'm trying to update a property value of a single document by sending a request to my NextJs API via fetch.
// Update items in state when the pending time in queue has passed, set allowed: true
items.map((item) => {
const itemDate = new Date(item.added)
const timeDiff = currentDate.getTime() - itemDate.getTime()
const dateDiff = timeDiff / (1000 * 3600 * 24)
if (dateDiff >= 7) {
const updateItem = async () => {
try {
// Data to update
const updatedItem = {
_id: item._id,
name: item.name,
price: item.price,
allowed: true
}
console.log(updatedItem)
await fetch ('/api/impulses', {
method: 'PATCH',
body: JSON.stringify(updatedItem)
})
} catch (error) {
alert('Failed to update items status')
}
}
updateItem()
}
})
API receives the data object as a whole and I am able to parse every piece of data I need for updating the MongoDb document from the req.body. However, when trying to use the item's _id (which is generated by MongoDb and values as _id: ObjectId('xx1234567890xx')) to filter the document I need to update, it seems to treat the ID differently.
I've tried to mess around with the format, forcing the object that gets sent to the API to include just the things I want to be updating (and the _id, of course) but still...
const jsonData = JSON.parse(req.body)
const { _id } = jsonData
// Fields to update
const { name, price, allowed } = jsonData
const data = {
name,
price,
allowed
}
const filter = {
_id: _id
}
const update = {
$set: data
}
const options = {
upsert: false
}
console.log("_id: ", filter) // returns { _id: 'the-correct-id-of-this-document' }
And finally, updating thedb.collection and returning responses:
await db.collection('impulses').updateOne(filter, update, options)
return res.json({
message: 'Impulse updated successfully',
success: true
})
So, as the _id clearly matches the document's id, I cannot figure out why it doesn't get updated? To confirm that this isn't an issue with database user privileges, if I set upsert: true in options, this creates a new document with the same _id and the updated data the request included...
Only difference to the other documents created through a submit form is that the id is in the following format: _id: 'xx1234567890xx' - so, comparing that to an ID with the ObjectId on front, it doesn't cause a conflict but I really don't get this behavior... After noticing this, I've also tried to include the 'ObjectId' in the ID reference in various ways, but it seemed like initiating a new ObjectId did exactly that - generate a new object ID which no longer referenced the original document.
Any ideas?
You compare an ObjectId object from _id with a string, this does not work.
Create proper filter object, e.g.
const filter = { _id: ObjectId(_id) }
or the other way around:
const filter = { $expr: {$eq: [{$toString: "$_id"}, _id] } }
but this will prevent to use the index on _id, so the first solution should be preferred.
I have a function that create guild entry for DiscordJS, but when the script start and also if the function is called multiple times, it create around 400 duplicate documents, it create by ID and the ID is unique, so it's not normal
My schema structure only have a ID type String and unique is true
client.createGuild = async guild => {
const exist = await Guild.findOne({ id: guild.id });
if(!exist) {
await Guild.create({ id: guild.id }); // new Guild().save() keep duplicate too
}
}
It look like the if statement doesn't exist
const Schema = mongoose.Schema;
const FooSchema = new Schema({
id: { type: String, index: true, unique: true }
});
const Foo = mongoose.model('Foo', FooSchema);
Foo.createIndexes();
If collection already exists. Create index manually to the collection via atlas or cmd.
You can combine getData and createData functions to one. Here is the example:
const mongoose = require('mongoose');
async function getData(Guild, guild) {
if (!mongoose.connection.readyState) await mongoose.connect('MONGO_URL'); // In case you haven't connect to database
const data = await Guild.findOne({ id: guild.id }); // get data from database
if (!data) {
return new Guild({
id: guild.id,
}); // If no data exists for the guild, return new model
}
return data; // If the data already exists, return that
}
Now if you want to get data from mongodb you just call the function. It automatically create and save a new one if there is not.
Comment if you still have any problem or you have got what you need.
Make sure to call the function with await or it won't return the data.
I have a user's collection, each user has a subscription array of objects. I want to add another subscription to that array. I'm getting the new subscription, updating it with findByIdAndUpdate, but it doesn't add the new subscription, however it shows that the document was updated. I tried several approaches but nothing worked well.
Here is the last approach:
...
const { user_id } = req.params;
const subscription = req.body; // Getting subscription
const user = await UserModel.findById(user_id).lean().exec(); // Getting user by id
const { push_subscriptions } = user; // Getting subscribtions with destructuring
const updated_subs = [...push_subscriptions, subscription];
// Getting user another time and updating the push_subscriptions array
const updated_user = await UserModel.findByIdAndUpdate(user_id,
{push_subscriptions: updated_subs},
{ new: true }
).exec();
...
Here are the logs of request body and params
// body
{
endpoint: 'https://fcm.googleapis.com/fcm/send/cQ6wlRJ8t-s:APA91bEjqdLMzQLsroJ7zHzdjrzoshdPD8IJy_iIeRa8qV_Yjt6N1jeMUtyMq73wSn9JJT-4WXr_8uwHXttj-XFxHPCPAOqgN7zALsmf_BeIRZowRBTRHf9YH8v3AlcaZXWAIQ0qJNdn',
expirationTime: null,
keys: {
p256dh: 'BBPC5h1QnBMPKMfPacgJu_2RFT7LAejyINh3CvP4pamkrlERr06YpRlSb7RbTUOn6MYW4adG93KfdEWXz68F9iQ',
auth: 'Zl3iaOdBvihXG2QVOb26IQ'
}
}
//params
{ user_id: '5fedc679f414663c693cf549' }
User schema, push_subscriptions part:
push_subscriptions: {
type: Array,
},
Your query looks good, you can do single query using $push instead of doing manual process,
const { user_id } = req.params;
const subscription = req.body;
const updated_user = await UserModel.findByIdAndUpdate(user_id,
{
$push: {
push_subscriptions: subscription
},
{ new: true }
).exec();
I'm using Firebase scheduled function to periodically check data in Firestore if users' detail are added to SendGrid contact list. After they're successfully added to SendGrid, I want to update the value in firestore addToContact from false to true.
exports.addContact = functions.region(region).pubsub.schedule('every 4 minutes').onRun(async(context) => {
// Query data from firestore
const querySnapshot = await admin.firestore().collection('users')
.where('metadata.addToContact', '==', false)
.get();
// Call SendGrid API to add contact
// If success, change metadata.addToContact to true
if (response) {
const docRef = admin.firestore().collection('users').doc(""+context.params.id);
await docRef.update( {'metadata.sendToSg': true }, { merge: true } );
}
}
I want to access context.params.id but I realise that context that passed in .onRun isn't the same with the context passed in a callable function.
NOTE: If I pass context.params.id to .doc without ""+, I got an error Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string. After google the answer, I tried using ""+context.params.id then I can console.log the context value. I only got
context {
eventId: '<eventID>',
timestamp: '2020-11-21T09:05:38.861Z',
eventType: 'google.pubsub.topic.publish',
params: {}
}
I thought I'd get document ID from params object here but it's empty.
Is there a way to get Document ID from firestore in a scheduled function?
The scheduled Cloud Function itself has no notion of a Firestore document ID since it is triggered by the Google Cloud Scheduler and not by an event occurring on a Firestore document. This is why the corresponding context object is different from the context object of a Firestore triggered Cloud Function.
I understand that you want to update the unique document corresponding to your first query (admin.firestore().collection('users').where('metadata.addToContact', '==', false).get();).
If this understanding is correct, do as follows:
exports.addContact = functions.region(region).pubsub.schedule('every 4 minutes').onRun(async (context) => {
// Query data from firestore
const querySnapshot = await admin.firestore().collection('users')
.where('metadata.addToContact', '==', false)
.get();
// Call SendGrid API to add contact
// If success, change metadata.addToContact to true
if (response) {
const docRef = querySnapshot.docs[0].ref;
await docRef.update({ 'metadata.sendToSg': true }, { merge: true });
} else {
console.log('No response');
}
return null;
});
Update following your comment:
You should not use async/await with forEach(), see here for more details. Use Promise.all() instead, as follows:
exports.addContact = functions.region(region).pubsub.schedule('every 4 minutes').onRun(async (context) => {
// Query data from firestore
const querySnapshot = await admin.firestore().collection('users')
.where('metadata.addToContact', '==', false)
.get();
// Call SendGrid API to add contact
// If success, change metadata.addToContact to true
if (response) {
const promises = [];
querySnapshot.forEach((doc) => {
const { metadata } = doc.data();
if (metadata.sendToSg == false) {
promises.push(doc.ref.update({ 'metadata.sendToSg': true }, { merge: true }));
}
})
await Promise.all(promises);
} else {
console.log('No response');
}
return null;
});
I am currently trying to sync my firestore documents with algolia upon a new document creation or the update of a document. The path to the collection in firestore is videos/video. The function seems to be triggering fine, however after triggering, the firebase function does not seem to relay any of the information to algolia (no records are being created). I am not getting any errors in the log. (I also double checked the rules and made sure the node could be read by default, and yes I am on the blaze plan). Does anyone know how to sync a firestore node and algolia? Thanks for all your help!
"use strict";
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const algoliasearch_1 = require("algoliasearch");
// Set up Firestore.
admin.initializeApp();
const env = functions.config();
// Set up Algolia.
// The app id and API key are coming from the cloud functions environment, as we set up in Part 1,
const algoliaClient = algoliasearch_1.default(env.algolia.appid, env.algolia.apikey);
// Since I'm using develop and production environments, I'm automatically defining
// the index name according to which environment is running. functions.config().projectId is a default property set by Cloud Functions.
const collectionindexvideo = algoliaClient.initIndex('videos');
exports.collectionvideoOnCreate = functions.firestore.document('videos/{uid}').onCreate(async(snapshot, context) => {
await savevideo(snapshot);
});
exports.collectionvideoOnUpdate = functions.firestore.document('videos/{uid}').onUpdate(async(change, context) => {
await updatevideo(change);
});
exports.collectionvideoOnDelete = functions.firestore.document('videos/{uid}').onDelete(async(snapshot, context) => {
await deletevideo(snapshot);
});
async function savevideo(snapshot) {
if (snapshot.exists) {
const document = snapshot.data();
// Essentially, you want your records to contain any information that facilitates search,
// display, filtering, or relevance. Otherwise, you can leave it out.
const record = {
objectID: snapshot.id,
uid: document.uid,
title: document.title,
thumbnailurl: document.thumbnailurl,
date: document.date,
description: document.description,
genre: document.genre,
recipe: document.recipe
};
if (record) { // Removes the possibility of snapshot.data() being undefined.
if (document.isIncomplete === false) {
// In this example, we are including all properties of the Firestore document
// in the Algolia record, but do remember to evaluate if they are all necessary.
// More on that in Part 2, Step 2 above.
await collectionindexvideo.saveObject(record); // Adds or replaces a specific object.
}
}
}
}
async function updatevideo(change) {
const docBeforeChange = change.before.data();
const docAfterChange = change.after.data();
if (docBeforeChange && docAfterChange) {
if (docAfterChange.isIncomplete && !docBeforeChange.isIncomplete) {
// If the doc was COMPLETE and is now INCOMPLETE, it was
// previously indexed in algolia and must now be removed.
await deletevideo(change.after);
} else if (docAfterChange.isIncomplete === false) {
await savevideo(change.after);
}
}
}
async function deletevideo(snapshot) {
if (snapshot.exists) {
const objectID = snapshot.id;
await collectionindexvideo.deleteObject(objectID);
}
}
Still don't know what I did wrong, however if anyone else is stuck in this situation, this repository is a great resource: https://github.com/nayfin/algolia-firestore-sync. I used it and was able to properly sync firebase and algolia. Cheers!
// Takes an the Algolia index and key of document to be deleted
const removeObject = (index, key) => {
// then it deletes the document
return index.deleteObject(key, (err) => {
if (err) throw err
console.log('Key Removed from Algolia Index', key)
})
}
// Takes an the Algolia index and data to be added or updated to
const upsertObject = (index, data) => {
// then it adds or updates it
return index.saveObject(data, (err, content) => {
if (err) throw err
console.log(`Document ${data.objectID} Updated in Algolia Index `)
})
}
exports.syncAlgoliaWithFirestore = (index, change, context) => {
const data = change.after.exists ? change.after.data() : null;
const key = context.params.id; // gets the id of the document changed
// If no data then it was a delete event
if (!data) {
// so delete the document from Algolia index
return removeObject(index, key);
}
data['objectID'] = key;
// upsert the data to the Algolia index
return upsertObject(index, data);
};