Changing order of an array in mongoose does not save - mongodb

I am using drag and drop library to change the order of my elements, and I need that order to be saved, the following method does change the document but it won't save it, I tried doc.save() and this update method, the logged doc is in changed order but it's not updated in the database.
module.exports.changeListOrder = async(req, res) => {
const { id, listID, oldIndex, newIndex } = req.body;
await Board.findById(id).then(async(doc) => {
let tmpdoc = doc.lists[oldIndex];
doc.lists[oldIndex] = doc.lists[newIndex];
doc.lists[newIndex] = tmpdoc;
await Board.updateOne({ _id: id }, { $set: { list: doc.lists } })
});
}
and my model has
lists: [{ header: String, listItems: Array }]

You are mixing up Promise and async syntax. The async syntax would be like this:
module.exports.changeListOrder = async (req, res) => {
const { id, listID, oldIndex, newIndex } = req.body;
const thisBoard = await Board.findById(id);
// this code won't run until thisBoard has returned a value
let [oldValue, newValue] = [thisBoard.lists[oldIndex], thisBoard.lists[newIndex];
thisBoard[oldIndex] = newValue;
thisBoard[newIndex] = oldValue;
let saveOperation = await Board.save(thisBoard);
// console.log(saveOperation);
// return saveOperation or do something with res()
};

module.exports.changeListOrder = async(req, res) => {
const { id, listID, oldIndex, newIndex } = req.body;
const doc = await Board.findById(id);
let [oldValue, newValue] = [doc.lists[oldIndex], doc.lists[newIndex]];
doc.lists.set(oldIndex, newValue);
doc.lists.set(newIndex, oldValue);
await doc.save();
};
Here is the working code, the problem was that Mongoose doesn't create getters/setters for array indexes; without them mongoose never gets notified of the change and so doesn't know to persist the new value, so you have to use set()

Related

use Effect not working to bring up my product, using axios use params,

This code is not working for me i am trying to pull data from my mongodb
const ProductScreen = ({ match }) => {
const [product, setProduct] = useState({});
const { id } = useParams();
useEffect(() => {
const fetchProduct = async () => {
const { data } = await axios.get(
`/api/product/${encodeURIComponent(id)}`
);
setProduct(data);
};
fetchProduct();
}, []);
};
pull data from server of mongo db
It is possible when the component first mounts, id is null and useParams() doesn't get it till the second render. So add an if statement in your useEffect to make sure the id is present. Also add id to the dependency array, so if the id changes, you will refetch the data for it. Otherwise, with an empty dependency array, the useEffect will only run on first mount.
const ProductScreen = ({ match }) => {
const [product, setProduct] = useState({});
const { id } = useParams();
useEffect(() => {
const fetchProduct = async () => {
const { data } = await axios.get(
`/api/product/${encodeURIComponent(id)}`
);
setProduct(data);
};
if (id) {
fetchProduct();
}
}, [id]);
};

How can I get Firebase Storage downloadable array of links and set it to object for storing in mongodb database?

My question is how do I set array of image links to my createAd function for storing the result into MongoDB database. Console log gives the getLinks result array as below. However, always I'm getting empty [] array for photos[] field in MongoDB database collection.
getlink function do the upload image to firestore and get downloadable url
const getLinks = (values) => {
const array = [];
values.adImgs.map((image: any) => {
const imgPath = `ad_images/${image.name + v4() }`;
const imageRef = ref(storage, imgPath);
uploadBytes(imageRef, image).then((snapshot) => {
getDownloadURL(snapshot.ref)
.then((url)=> array.push(url));
})
});
return array;
}
This is the function to store data into MongoDb database
const createAdd = async (values) => {
const newObj: any = {
title: values.title,
photos: getLinks(values)
}
await createPost(newObj);
}
The uploadBytes() and getDownloadURL() functions both return a promise. You are returning an empty before waiting for the files to upload. Try refactoring the code as shown below:
// async function
const getLinks = async (values) => {
const uploadPromises = values.adImgs.map((image) => {
const imgPath = `ad_images/${image.name + v4() }`;
const imageRef = ref(storage, imgPath);
return uploadBytes(imageRef, image);
})
// Upload all images
const res = await Promise.all(uploadPromises);
const links = await Promise.all(res.map((r) => getDownloadURL(r.ref)));
return links;
}
const createAdd = async (values) => {
const newObj: any = {
title: values.title,
photos: await getLinks(values) // <-- add await
}
await createPost(newObj);
}

Why my firebase update function is not always working?

Im trying to figuring out why my firebase funtion for cheat is always creating but when like open the chat where it call create function immediately send a message the message will not be saved, because my function is not ready so how can I sole this?.
Heres my function .
export const onConversationCreated = functions.firestore.
document("Conversations/{conversationID}").onCreate((snapshot, context) => {
const data = snapshot.data();
const conversationID = context.params.conversationID;
if (data) {
const members = data.members;
for (let index = 0; index < members.length; index++) {
const uid = members[index];
const remainingUserIDs = members.filter((u: string) => u !== uid);
remainingUserIDs.forEach((m: string) => {
return admin.firestore().
collection("profile").doc(m).get().then((_doc) => {
const userData = _doc.data();
if (userData) {
return admin.firestore().collection("profile")
.doc(uid).collection("Conversations").doc(m).create({
"conversationID": conversationID,
"url": userData.url,
"name": userData.username,
"unseenCount": 0,
});
}
return null;
}).catch(() => {
return null;
});
});
}
}
return null;
});
export const onConversationUpdated = functions.firestore
.document("Conversations/{conversationID}").onUpdate((change, context) => {
const data = change?.after.data();
if (data) {
const members = data.members;
const lastMessage = data.messages[data.messages.length - 1];
for (let index = 0; index < members.length; index++) {
const uid = members[index];
const remainingUserIDs = members.filter((u: string) => u !== uid);
remainingUserIDs.forEach((u: string) => {
return admin.firestore().collection("meinprofilsettings")
.doc(uid).collection("Conversation").doc(u).update({
"lastMessage": lastMessage.message,
"timestamp": lastMessage.timestamp,
"type": lastMessage.type,
"lastmessageuid": lastMessage.senderID,
"unseenCount": admin.firestore.FieldValue.increment(1),
});
});
}
}
return null;
});
So again creating is correct working. its just need some time . And when I immediately when calling create function write a message and send it this message will not be saved until the create function is finished then I have to send again the message
enter image description here
The reason for your bug is that you don't await the execution of your async task correctly. Unfortunately the forEach doesn't support async so we need to revrite your code to something like this:
export const onConversationCreated = functions.firestore
.document("Conversations/{conversationID}")
.onCreate((snapshot, context) => {
const data = snapshot.data();
const promises: Promise<any>[] = [];
const conversationID = context.params.conversationID;
if (data) {
const members = data.members;
for (let index = 0; index < members.length; index++) {
const uid = members[index];
const remainingUserIDs = members.filter((u: string) => u !== uid);
remainingUserIDs.forEach((m: string) => {
promises.push(
admin
.firestore()
.collection("profile")
.doc(m)
.get()
.then((_doc) => {
const userData = _doc.data();
if (userData) {
return admin
.firestore()
.collection("profile")
.doc(uid)
.collection("Conversations")
.doc(m)
.create({
conversationID: conversationID,
url: userData.url,
name: userData.username,
unseenCount: 0,
});
}
return null;
})
);
});
}
}
return Promise.all(promises);
});
export const onConversationUpdated = functions.firestore
.document("Conversations/{conversationID}")
.onUpdate((change, context) => {
const data = change?.after.data();
const promises: Promise<any>[] = [];
if (data) {
const members = data.members;
const lastMessage = data.messages[data.messages.length - 1];
for (let index = 0; index < members.length; index++) {
const uid = members[index];
const remainingUserIDs = members.filter((u: string) => u !== uid);
remainingUserIDs.forEach((u: string) => {
promises.push(
admin
.firestore()
.collection("meinprofilsettings")
.doc(uid)
.collection("Conversation")
.doc(u)
.update({
lastMessage: lastMessage.message,
timestamp: lastMessage.timestamp,
type: lastMessage.type,
lastmessageuid: lastMessage.senderID,
unseenCount: admin.firestore.FieldValue.increment(1),
})
);
});
}
}
return Promise.all(promises);
});
We use Promise.all() to even run all your async tasks in parallel to finish the function faster and save on execution time.

Firebase cloud functions not waiting for forEach to complete before jumping to the next then

Been trying to copy subcollections of a collection into another collection. The code below is aimed at that, but jumps from the first then and logs out "Done" without logging out anything before.
So the question is what is not correct here?
exports = module.exports = functions.https.onRequest(async (req, res) => {
let db = admin.firestore();
try {
await db.collection("users").get().then((query) => {
return query.forEach(async (doc) => {
console.log("Here"); //This doesn't print
const polCollection = await db.collection("users").doc(doc.id).collection("xyz").get();
if (polCollection.docs.length > 0) { //This checks if any subcollections
for (const x of polCollection.docs) { //This copies them into a doc in the copy collection
db.collection("CopyUsers")
.doc(doc.id)
.set({ x : x.data() }, { merge: true });
}
}
});
})
.then(() => {
console.log("Done"); //This is the only thing that prints in the console
res.end();
})
.catch((e) => {
console.log("e", e);
res.end();
});
} catch (error) {
console.log("error", error);
res.end();
}
});
After the suggestion below, it now looks as follows:
exports = module.exports = functions.runWith(runtimeOpts).https.onRequest(async (req, res) => {
const promises = [];
let count = 0;
let size = 0;
return await admin
.firestore()
.collection("testUsers")
.get()
.then((query) => {
console.log("query length:", query.size); //prints x of users
size = query.size;
query.forEach(async (doc) => {
const promise = async () => {
console.log("Here", doc.id); //This doesn't print
await admin
.firestore()
.collection("testUsers")
.doc(doc.id)
.collection("xyz")
.get()
.then(async (polCollection) => {
if (polCollection.docs.length > 0) {
for (const x of polCollection.docs) {
return await admin
.firestore()
.collection("testBackUpUsers")
.doc(doc.id)
.set(
{ xyz: x.data() },
{ merge: true }
);
}
} else {
return;
}
})
.catch((e) => console.log("error from then after get xyz", e));
};
count++;
return promises.push(promise);
});
return promises;
})
.then(async (promises) => {
if (size <= count) {
console.log("running return Promise.all(promises)", promises.length); //prints number of promises = users
return Promise.all(promises);
}
})
.catch((e) => console.log("err from the last catch", e));
});
Any thoughts?
Unfortunately the forEach iterator does not support async/await. Even if you write an await inside it will just go trough it without waiting on the execution.
I would recommend to use Promise.all. That would also execute the code in parallel and would finish faster.
If you would only change data you could also use a batch change but in your example you first get the data and then change it.
Here is an example how you could write your code:
exports = module.exports = functions.https.onRequest(async (req, res) => {
let db = admin.firestore();
const promises = [];
try {
const query = await db.collection("users").get();
query.forEach((doc) => {
console.log("doc", doc);
const promise = async () => {
console.log("Here", doc.id); //This doesn't print
const polCollection = await db
.collection("users")
.doc(doc.id)
.collection("xyz")
.get();
if (polCollection.docs.length > 0) {
//This checks if any subcollections
for (const x of polCollection.docs) {
//This copies them into a doc in the copy collection
await db
.collection("CopyUsers")
.doc(doc.id)
.set({ x: x.data() }, { merge: true });
}
}
};
promises.push(promise);
});
console.log("promises", promises);
await Promise.all(promises);
console.log("Done");
res.end();
} catch (error) {
console.log("error", error);
res.end();
}
});

Mongoose findOne variables

I have a function which passes a key val and then gets added as arguments for a .findOne() mongoose function.
getByKey = async (key, val) => {
console.log(key, val);
const user = await UserSchema.findOne({
key: val
});
console.log(user);
return user;
};
The problem is, I think mongoose is actually searching the collection for the word key instead of the what it stands for ie: "username" or "age"
It is looking for 'key' as opposed to the key your passing. You can accomplish what you're trying to do by doing something like this.
var query = {}
query[key] = value;
And then pass that query to your findOne function. See below.
getByKey = async (key, val) => {
var query = {}
query[key] = value;
const user = await UserSchema.findOne(query);
console.log(user);
return user;
};
You also might want to consider adding a callback function to in your findOne and try to log the data if it was found.
const user = await UserSchema.findOne(query, function(err, data){
if (err){
console.log(err)
} else {
console.log(data)
}
});
You can use Computed property names to handle this. Example:
const user = await UserSchema.findOne({
[key]: val
});