How can I update nested array in Firestore? - javascript

Is there a way to update the likes and replies with firebase firestore?
I have reached that moment when I press on like on a single comment but I have only the reference to the document of the whole post , so I don't have a reference to the comments itself. I also reached the comment with the find method of the arrays but it's not a reference its the comment itself.
Thanks in advance, I am thinking I just have to reconsider how i structure my DB using firestore?
const addLikeToComment = async (
postID: string,
commentID: string,
userID: string
): Promise<void> => {
const currentPost = await getPostById(postID)
const currentPostDocId = currentPost[0].docID!
const currentPostRef = doc(db, 'posts', currentPostDocId)
const commentToAddLikeTo = currentPost[0].comments.find(
(currComment) => currComment.commentID === commentID
)
console.log(commentToAddLikeTo)
if (!commentToAddLikeTo?.likes.includes(userID)) {
await updateDoc(currentPostRef, {
// add the like to comments - currentComment - likes array inside it push the userID
})
} else {
await updateDoc(currentPostRef, {
// remove the like
})
}
}

const addLikeToComment = async (
postID: string,
commentID: string,
userID: string
): Promise<void> => {
const currentPost = await getPostById(postID)
const currentPostDocId = currentPost[0].docID!
const currentPostRef = doc(db, 'posts', currentPostDocId)
const commentToAddLikeTo = currentPost[0].comments.find(
(currComment) => currComment.commentID === commentID
)
const newLikes = commentToAddLikeTo?.likes
if (!commentToAddLikeTo?.likes.includes(userID)) {
newLikes?.push(userID)
} else {
const indexOfUserID = newLikes?.indexOf(userID)
newLikes?.splice(indexOfUserID!, 1)
}
await updateDoc(currentPostRef, {
...currentPost[0],
likes: newLikes,
})
}
This is how I solved this with some help from a co-worker. Basically I am manipulating the likes array directly and then updating the whole post information.

Related

Firestore - Web version 9 - get all documents in collection whose id is in array of ids

I'm new to Firestore and trying to form a query to return all documents whose doc.id is in an array of ids.
So something like this:
const getAlbumSongs = async (songUids) => {
let albumSongs = [];
const q = query(collection(db, 'songs'), where('id', 'in', songUids));
const snap = await getDocs(q);
if (snap) {
for (const doc of snap) {
albumSongs.push({
songId: doc.id,
albumId: doc.data().album_id,
authorId: doc.data().author_id,
songTitle: doc.data().song_title,
filePath: doc.data().file_path,
});
}
}
return albumSongs;
};
I should note I'm calling the getAlbumSongs() function from within a getAlbums() function, like this:
async function getAlbums() {
setIsLoadingAlbums(true);
const snap = await getDocs(collection(db, 'albums'));
setIsLoadingAlbums(false);
let albumsData = [];
if (snap) {
snap.forEach(async (doc) => {
albumsData.push({
ablumId: doc.id,
albumTitle: doc.data().album_title,
albumCoverUrl: doc.data().cover_url || 'https://picsum.photos/300',
albumSongs: await getAlbumSongs(doc.data().song_uids), // this might be problematic?
});
});
console.log('albumsData', albumsData);
return albumsData;
}
}
Your current code queries on a field called id in each document. If you want the document ID itself, that is part of the metadata of the document and you'll want to query on documentId() for that:
const q = query(collection(db, 'songs'), where(documentId(), 'in', songUids));

How to push data to firestore document?

Basically what I want is that if a document doesn't exist then create a new one (it works fine now) but if a document does exist, push a new object to the existing array.
I was able to get data from documents and console.log them, but don't know how to push new ones to the existing document.
My FB structure looks like this:
favorites
someUserID
Videos [
0: {
name: SomeName
url: SomeUrl
},
/* I would like to push new objects like this: */
1: {
name: data.name
url: data.url
},
]
This is my current code:
const { user } = UserAuth();
const UserID = user.uid;
const favoritesRef = doc(db, "favorites", UserID);
const test = async (data) => {
try {
await runTransaction(db, async (transaction) => {
const sfDoc = await transaction.get(favoritesRef);
if (!sfDoc.exists()) {
setDoc(favoritesRef, {
Videos: [{name: data.name}]
});
}
/* I got my document content here */
const newFavorites = await getDoc(favoritesRef);
console.log("Document data:", newFavorites.data());
/* And would like to push new Data here */
transaction.update(favoritesRef, { name: data.name});
});
console.log("Transaction successfully committed!");
} catch (e) {
console.log("Transaction failed: ", e);
}
}
To update the array Firestore now has a function that allows you to update an array without writing your code again:
Update elements in an array
If your document contains an array field, you can use arrayUnion()
and arrayRemove() to add and remove elements. arrayUnion() adds
elements to an array but only elements not already present.
arrayRemove() removes all instances of each given element.
import { doc, updateDoc, arrayUnion, arrayRemove } from "firebase/firestore";
const washingtonRef = doc(db, "cities", "DC");
// Atomically add a new region to the "regions" array field.
await updateDoc(washingtonRef, {
regions: arrayUnion("greater_virginia")
});
// Atomically remove a region from the "regions" array field.
await updateDoc(washingtonRef, {
regions: arrayRemove("east_coast")
});
Not sure if this helps but what i usually do is:
I'm adding every user that signs in to an array.
const snapshot = await getDoc(doc(db, "allUsers", "list"));
const currentUsers = snapshot.data().users;
await setDoc(doc(db, "allUsers", "list"), {
users: [...currentUsers, { name, uid: userId, avatar: photo }],
});
I first get the items that exist in the list, and them i create a new one that has every previous item and the ones i'm adding. The currentUsers is the current list in that caso. Maybe you should try thist instead of Videos: [{name: data.name}]
setDoc(favoritesRef, {
Videos: [...currentVideos, {name: data.name}]
})
I just figured it out like this:
const { user } = UserAuth();
const UserID = user.uid
const favoritesRef = doc(db, "favorites", UserID)
const test = async (data) => {
try {
await runTransaction(db, async (transaction) => {
const sfDoc = await transaction.get(favoritesRef);
if (!sfDoc.exists()) {
await setDoc(favoritesRef, {
favs: [
{
name: data.name,
ytb: data.ytb,
url: data.url
}]})
}
const doesExists = sfDoc.data().favs.some((fav) => fav.name === data.name)
console.log(doesExists)
if (doesExists === true)
{
console.log("AlreadyExist")
}
else {
const currentData = sfDoc.data().favs
transaction.update(favoritesRef, {
favs: [...currentData,
{
name: data.name,
ytb: data.ytb,
url: data.url
}]}
)}
});
console.log("Transaction successfully committed!");
} catch (e) {
console.log("Transaction failed: ", e);
}
}

removing an object from firestore 9 array using arrayRemove()?

I am trying to remove an object from array in in firestore, but encountered an obstacle what are the requirement or the reference to do the removal ? does one key value in the object sufficient to do the remove or should the object by identical to the one that is getting removed ?
const deleteWeek = async () => {
const docRef = doc(db, 'Weeks', id);
await updateDoc(docRef, {
weeks: arrayRemove({
weekId: '7518005f-7b10-44b6-8e0a-5e41081ee064',
}),
});
};
deleteWeek();
}
however week in data base looks like this
{name ,"Week 2"
days : [/*data all kinds*/]
weekId : "7518005f-7b10-44b6-8e0a-5e41081ee064"}
If it's an array of object, then you need to know the whole object to use arrayRemove() For example, if the a document looks like this:
{
...data
weeks: [
{
name: "Week 2",
days: [/*data all kinds*/]
weekId: "7518005f-7b10-44b6-8e0a-5e41081ee064"}
}
]
}
You'll have to pass the entire week object in arrayRemove(). It might be better to store such data in sub-collections instead so you can query/delete a specific one.
Since there is no function in firestore to delete only a element in array, you need to make arrayRemove refer to the same object you want to delete, then create a new object and insert it with arrayUnion method
in my case, i use to below
const { leave,date,email } = req.body;
const attendanceRef = admin.firestore().collection('Attendance').doc(`${email}`);
const attendanceData = await attendanceRef.get();
const attendanceRecord = attendanceData.data().attendance;
const removeTarget = attendanceRecord.find((item) => item.date === date);
await attendanceRef.update({
attendance: admin.firestore.FieldValue.arrayRemove(removeTarget),
})
const obj = {
...removeTarget,
"leave": leave,
}
await attendanceRef.set({
attendance: admin.firestore.FieldValue.arrayUnion(obj),
},{ merge: true })
const newAttendance = await attendanceRef.get();
const newAttendanceRecord = newAttendance.data().attendance;
return await res.json({
message: '퇴근시간이 저장되었습니다.',
attendance:newAttendanceRecord
});
after update, it maybe if error occured.
if error occured, you need all working cancel.
this case, you may want to use batch method
const admin = require('firebase-admin');
module.exports = async function(req,res) {
const { leave,date,email } = req.body;
const batch = admin.firestore().batch();
const attendanceRef = admin.firestore().collection('Attendance').doc(`${email}`);
const attendanceData = await attendanceRef.get();
const attendanceRecord = attendanceData.data().attendance;
const removeTarget = attendanceRecord.find((item) => item.date === date);
// await attendanceRef.update({
// attendance: admin.firestore.FieldValue.arrayRemove(removeTarget),
// })
batch.update(
attendanceRef,{ attendance: admin.firestore.FieldValue.arrayRemove(removeTarget) }
)
const obj = {
...removeTarget,
"leave": leave,
}
// await attendanceRef.set({
// attendance: admin.firestore.FieldValue.arrayUnion(obj),
// },{ merge: true })
batch.set(
attendanceRef, { attendance: admin.firestore.FieldValue.arrayUnion(obj) },{ merge: true }
)
await batch.commit();
const newAttendance = await attendanceRef.get();
const newAttendanceRecord = newAttendance.data().attendance;
return await res.json({message: '퇴근시간이 저장되었습니다.',attendance:newAttendanceRecord});
}
hope help this for you

How to add mongoose transaction and create a document?

I want to add a mongoose transaction in the POST method. When creating the transaction it should be creating a document called stock. Can anybody help me figure out what should I do here? I have a node/express/mongoose app with the following:
GoodsRecivedNote controller
router.post('/', async (req, res) => {
const session = await mongoose.startSession()
try {
const _id = await getNextSequence('goodsReceivedNote')
req.body.id = _id
const goodsReceivedNote = new GoodsReceivedNote(req.body)
const stocks = new Stock(req.body)
await goodsReceivedNote.save()
//use mongoose transaction
//creates a loop(data get from the arry called cart in goodsrecivednote)
for (const item of data) {
//insert stock modal(orderNo, packingId, orderSize, poNumber)
item.create({})
//insert(data, {session})
}
await session.commitTransaction()
res.sendStatus(200)
} catch (error) {
await session.abortTransaction()
return res.sendStatus(500)
} finally {
session.endSession()
}
})
GoodsRecivedNote model
const goodsReceivedNoteSchema = new Schema(
{
id: Number,
poNumber: String,
orderedDate: String,
supplier: String,
orderNo: String,
cart: [
{
packingId: Number,
actualSize: String,
orderSize: String,
brandId: Number,
artWork: String,
receivedQty: Number,
grnDate: String,
},
],
},
)
module.exports = mongoose.model(
'GoodsReceivedNote',
goodsReceivedNoteSchema
)
Stock model
const stockSchema = new Schema(
{
id: Number,
poNumber: Number,
orderNo: String,
packingId: Number,
orderSize: String,
receivedQty: Number,
availableQty: Number,
},
)
module.exports = mongoose.model(
'Stock',
stockSchema
)
Maybe you can try something like this
const session = await mongoose.startSession()
session.startTransaction()
const opts = { session }
const stocks = await new Stock(req.body).save(opts)
await goodsReceivedNote.save(opts)
...the rest of your code
When ever you call Save Update or Delete please add opts as option
Answer by 21bn gets the work done but withTransaction() is way better than startTransaction().
I recommend you use withTransaction instead.
const session = await mongoose.startSession();
await session.withTransaction(async (session) => {
// For create..
collection.create({something:"something"},{session:session});
});
For insertmany, updatemany, the rule is basically the same..
collection.updateMany({find_something:"something"},{name:"Some name"},{session:session});
If you want to find a document using session..
collection.findOne({_id: "some_id"}).session(session));

How to query a firestore search for a name within a document?

What i have set up for my firestore database is one collection called 'funkoPops'. That has documents that are genres of funkoPops, with an array of funkoData that holds all pops for that genre. it looks like this below
I should also note, that the collection funkoPops has hundreds of documents of 'genres' which is basically the funko pop series with the sub collections of funkoData that I web scraped and now need to be able to search through the array field of 'funkoData' to match the name field with the given search parameter.
collection: funkoPops => document: 2014 Funko Pop Marvel Thor Series => fields: funkoData: [
{
image: "string to hold image",
name: "Loki - with helmet",
number: "36"
},
{
image: "string to hold image",
name: "Black and White Loki with Helmet - hot topic exsclusive",
number: "36"
},
{
etc...
}
So how could i run a query in firestore to be able to search in collection('funkoPops'), search through the document fields for name.
I have the ability to search for genres like so, which gives the genre back and the document with the array of data below:
const getFunkoPopGenre = async (req, res, next) => {
try {
console.log(req.params);
const genre = req.params.genre;
const funkoPop = await firestore.collection("funkoPops").doc(genre);
const data = await funkoPop.get();
if (!data.exists) {
res.status(404).send("No Funko Pop found with that search parameter");
} else {
res.send(data.data());
}
} catch (error) {
res.status(400).send(error.message);
}
};
what i am trying to use to search by the field name is below and returns an empty obj:
const getFunkoPopName = async (req, res, next) => {
try {
const name = req.params.name;
console.log({ name });
const funkoPop = await firestore
.collection("funkoPops")
.whereEqualTo("genre", name);
const data = await funkoPop.get();
console.log(data);
res.send(data.data());
} catch (error) {
res.status(400).send(error);
}
};
Any help would be great, thanks!
So the way i went about answering this as it seems from top comment and researching a little more on firebase, you do you have to match a full string to search using firebase queries. Instead, I query all docs in the collection, add that to an array and then forEach() each funkoData. From there i then create a matchArray and go forEach() thru the new funkoData array i got from the first query. Then inside that forEach() I have a new variable in matches which is filter of the array of data, to match up the data field name with .inlcudes(search param) and then push all the matches into the matchArr and res.send(matchArr). Works for partial of the string as well as .includes() matches full and substring. Not sure if that is the best and most efficient way but I am able to query thru over probably 20k data in 1-2 seconds and find all the matches. Code looks like this
try {
const query = req.params.name.trim().toLowerCase();
console.log({ query });
const funkoPops = await firestore.collection("test");
const data = await funkoPops.get();
const funkoArray = [];
if (data.empty) {
res.status(404).send("No Funko Pop records found");
} else {
data.forEach((doc) => {
const funkoObj = new FunkoPop(doc.data().genre, doc.data().funkoData);
funkoArray.push(funkoObj);
});
const matchArr = [];
funkoArray.forEach((funko) => {
const genre = funko.genre;
const funkoData = funko.funkoData;
const matches = funkoData.filter((data) =>
data.name.toLowerCase().includes(query)
);
if (Object.keys(matches).length > 0) {
matchArr.push({
matches,
genre,
});
}
});
if (matchArr.length === 0) {
res.status(404).send(`No Funko Pops found for search: ${query}`);
} else {
res.send(matchArr);
}
}
} catch (error) {
res.status(400).send(error.message);
}
with a little bit of tweaking, i am able to search for any field in my database and match it with full string and substring as well.
update
ended up just combining genre, name, and number searches into one function so that whenver someone searches, the query param is used for all 3 searches at once and will give back data on all 3 searches as an object so that we can do whatever we like in front end:
const getFunkoPopQuery = async (req, res) => {
try {
console.log(req.params);
const query = req.params.query.trim().toLowerCase();
const funkoPops = await firestore.collection("test");
const data = await funkoPops.get();
const funkoArr = [];
if (data.empty) {
res.status(404).send("No Funko Pop records exsist");
} else {
data.forEach((doc) => {
const funkoObj = new FunkoPop(doc.data().genre, doc.data().funkoData);
funkoArr.push(funkoObj);
});
// genre matching if query is not a number
let genreMatches = [];
if (isNaN(query)) {
genreMatches = funkoArr.filter((funko) =>
funko.genre.toLowerCase().includes(query)
);
}
if (genreMatches.length === 0) {
genreMatches = `No funko pop genres with search: ${query}`;
}
// name & number matching
const objToSearch = {
notNullNameArr: [],
notNullNumbArr: [],
nameMatches: [],
numbMatches: [],
};
funkoArr.forEach((funko) => {
const genre = funko.genre;
if (funko.funkoData) {
const funkoDataArr = funko.funkoData;
funkoDataArr.forEach((data) => {
if (data.name) {
objToSearch.notNullNameArr.push({
funkoData: [data],
genre: genre,
});
}
if (data.number) {
objToSearch.notNullNumbArr.push({
funkoData: [data],
genre: genre,
});
}
});
}
});
// find name that includes query
objToSearch.notNullNameArr.forEach((funko) => {
const genre = funko.genre;
const name = funko.funkoData.filter((data) =>
data.name.toLowerCase().includes(query)
);
if (Object.keys(name).length > 0) {
objToSearch.nameMatches.push({
genre,
name,
});
}
});
// find number that matches query
objToSearch.notNullNumbArr.forEach((funko) => {
const genre = funko.genre;
const number = funko.funkoData.filter((data) => data.number === query);
if (Object.keys(number).length > 0) {
objToSearch.numbMatches.push({
genre,
number,
});
}
});
if (objToSearch.nameMatches.length === 0) {
objToSearch.nameMatches = `No funko pops found with search name: ${query}`;
}
if (objToSearch.numbMatches.length === 0) {
objToSearch.numbMatches = `No funko pop numbers found with search: ${query}`;
}
const searchFinds = {
genre: genreMatches,
name: objToSearch.nameMatches,
number: objToSearch.numbMatches,
};
res.send(searchFinds);
}
} catch (error) {
res.status(400).send(error.message);
}
};
If anyone is well suited in backend and knows more about firestore querying, please let me know!

Categories

Resources