mongoose update array and add new element - arrays

I pretty sure this question was asked many items, however I cannot find an answer to my particular problem, which seems very but I just can't seem to get it working. I have a user model with cart schema array embedded in it. I am trying to add an object to an array and if it exists only update quantity and price, if it is doesn't add to an array. what happens with my code is that it adds a new item when array is empty, it updates the item's quantity and price but it doesn't want to add a new item. I read a bit a bout and as far as I understood I cannot use two different db methods in one request. I would appreciate any help on this, this is the first time I am actually using mongooose.
const CartItem = require('../models/cartModel');
const User = require('../models/userModel');
exports.addToCart = (req, res) => {
const cartItem = new CartItem.model(req.body);
const user = new User.model();
User.model
.findById(req.params.id)
.exec((err, docs) => {
if (err) res.sendStatus(404);
let cart = docs.cart;
if (cart.length == 0) {
docs.cart.push(cartItem);
}
let cart = docs.cart;
let isInCart = cart.filter((item) => {
console.log(item._id, req.body._id);
if (item._id == req.body._id) {
item.quantity += req.body.quantity;
item.price += req.body.price;
return true;
}
});
if (isInCart) {
console.log(cart.length)
} else {
cart.push(cartItem);
console.log(false);
}
docs.save(function (err, docs) {
if (err) return (err);
res.json(docs);
});
});
};
I actually managed to get it working like this
exports.addToCart = (req, res) => {
const cartItem = new Cart.model(req.body);
const user = new User.model();
User.model
.findById(req.params.id)
.exec((err, docs) => {
if (err) res.sendStatus(404);
let cart = docs.cart;
let isInCart = cart.some((item) => {
console.log(item._id, req.body._id);
if (item._id == req.body._id) {
item.quantity += req.body.quantity;
item.price += req.body.price;
return true;
}
});
if (!isInCart) {
console.log(cart.length)
cart.push(cartItem);
}
if (cart.length == 0) {
cart.push(cartItem);
}
docs.save(function (err, docs) {
if (err) return (err);
res.json(docs);
});
});
};
don't know if this is the right way to do it, but I can both add a new product into my array and update values of existing ones

Maybe your problem hava a simpler solution: check this page at the mongoose documentation: there is a compatibility issue with some versions of mongoDB and mongoose, maybe you need to edit your model code to look like this:
const mongoose = require("mongoose");
const CartSchema = new mongoose.Schema({
//your code here
}, { usePushEach: true });
module.exports = mongoose.model("Cart", CartSchema);
You can find more information here: https://github.com/Automattic/mongoose/issues/5924
Hope it helps.

Related

Firebase's onValue only get data once and does not update when placed inside a loop in React's useEffect

My goal is to get multiple data based on a list of data the customer requested so I put the codes inside useEffect. If the array contains the list of things the customer wants, then it grab those data from the server so the user can manipulate it. So far, it works fine but when the database updates, onValue is not triggered to grab the new data to update the render.
Here is my code. Thank you for helping me in advance.
// Getting data
useEffect(() => {
if (empDataArr.length > 1) {
let fromDay = parseInt(dateHandler(startDate).dateStamp);
let toDay = parseInt(dateHandler(endDate).dateStamp);
let tempLogArr = [];
empDataArr.forEach((emp) => {
let qLogEvent = query(child(shopRef(shopId), emp.id + "/log_events"), orderByChild("dateStamp"), startAt(fromDay), endAt(toDay));
// This is the part I need help
onValue(qLogEvent, (snap) => {
let logEventArr = [];
let val = snap.val();
if (val === null) {
} else {
Object.keys(val).forEach((key) => {
let id = key;
let dateStamp = val[key].dateStamp;
let direction = val[key].direction;
let time = val[key].timeStamp + "";
let timeStamp = time.substring(8, 10) + ":" + time.substring(10, 12);
logEventArr.push({ direction: direction, timeStamp: timeStamp, dateStamp: dateStamp, id: id });
});
tempLogArr.push({
id: emp.id,
logEvent: logEventArr,
});
}
});
});
setLogDataArr(tempLogArr.map((x) => x));
}
}, [empDataArr, shopId, startDate, endDate]);
useEffect(() => {
console.log(logDataArr);
}, [logDataArr]);
I have tried using return onValue() and const logData = onValue() but they do not work (and I do not expect the former one to work either).

How to get all subcollection documents with subcollection name as a date? [duplicate]

Say I have this minimal database stored in Cloud Firestore. How could I retrieve the names of subCollection1 and subCollection2?
rootCollection {
aDocument: {
someField: { value: 1 },
anotherField: { value: 2 }
subCollection1: ...,
subCollection2: ...,
}
}
I would expect to be able to just read the ids off of aDocument, but only the fields show up when I get() the document.
rootRef.doc('aDocument').get()
.then(doc =>
// only logs [ "someField", "anotherField" ], no collections
console.log( Object.keys(doc.data()) )
)
It is not currently supported to get a list of (sub)collections from Firestore in the client SDKs (Web, iOS, Android).
In server-side SDKs this functionality does exist. For example, in Node.js you'll be after the ListCollectionIds method:
var firestore = require('firestore.v1beta1');
var client = firestore.v1beta1({
// optional auth parameters.
});
// Iterate over all elements.
var formattedParent = client.anyPathPath("[PROJECT]", "[DATABASE]", "[DOCUMENT]", "[ANY_PATH]");
client.listCollectionIds({parent: formattedParent}).then(function(responses) {
var resources = responses[0];
for (var i = 0; i < resources.length; ++i) {
// doThingsWith(resources[i])
}
})
.catch(function(err) {
console.error(err);
});
It seems like they have added a method called getCollections() to Node.js:
firestore.doc(`/myCollection/myDocument`).getCollections().then(collections => {
for (let collection of collections) {
console.log(`Found collection with id: ${collection.id}`);
}
});
This example prints out all subcollections of the document at /myCollection/myDocument
Isn't this detailed in the documentation?
/**
* Delete a collection, in batches of batchSize. Note that this does
* not recursively delete subcollections of documents in the collection
*/
function deleteCollection(db, collectionRef, batchSize) {
var query = collectionRef.orderBy('__name__').limit(batchSize);
return new Promise(function(resolve, reject) {
deleteQueryBatch(db, query, batchSize, resolve, reject);
});
}
function deleteQueryBatch(db, query, batchSize, resolve, reject) {
query.get()
.then((snapshot) => {
// When there are no documents left, we are done
if (snapshot.size == 0) {
return 0;
}
// Delete documents in a batch
var batch = db.batch();
snapshot.docs.forEach(function(doc) {
batch.delete(doc.ref);
});
return batch.commit().then(function() {
return snapshot.size;
});
}).then(function(numDeleted) {
if (numDeleted <= batchSize) {
resolve();
return;
}
// Recurse on the next process tick, to avoid
// exploding the stack.
process.nextTick(function() {
deleteQueryBatch(db, query, batchSize, resolve, reject);
});
})
.catch(reject);
}
This answer is in the docs
Sadly the docs aren't clear what you import.
Based on the docs, my code ended up looking like this:
import admin, { firestore } from 'firebase-admin'
let collections: string[] = null
const adminRef: firestore.DocumentReference<any> = admin.firestore().doc(path)
const collectionRefs: firestore.CollectionReference[] = await adminRef.listCollections()
collections = collectionRefs.map((collectionRef: firestore.CollectionReference) => collectionRef.id)
This is of course Node.js server side code. As per the docs, this cannot be done on the client.

Nested onSnapshot problem in react-native-Firebase Firestore

I'm struggling to overcome problems that I have on nested onSnapshot, so, in short, I have 3 nested onSnapshot and when parent/root onSnapshot updates it also creates new onSnapshot listeners and leaves old ones too, so old and new ones are listening to the changes. I know that I should unsubscribe it but I can't, I'm losing track of which listeners are added or already exist.
One solution is to create array of unsubscribing functions in parent onSnapshot, but there another problem comes, I'm using docChanges with forEach and it is hard to manage which would I unsubscribe from array.
I saw this on Stackoverflow but it doesn't fit mine or even doesn't explain correctly exactly this case: nested listeners
What can you suggest to me? I don't know what else should I do.
Thanks in advance.
here is an example of my code that I'm trying to implement unsubscribe stuff( I use Mobx):
// TODO: optimise this query
const id = auth().currentUser?.uid;
// get all channelId-s that user has
channelParticipantsRef
.where('user.id', '==', id)
.onSnapshot((userChannels) => {
userChannels?.docChanges().forEach(function (channelParticipant) {
const channelParticipantData = channelParticipant;
if (!channelParticipantData.doc.exists) return;
// get all channels data that user has
channelsRef
.where(
'channelId',
'==',
channelParticipantData.doc.data().channelId,
)
.onSnapshot((channels) => {
if (!channels || channels.empty) return;
channels.docChanges().forEach(function (channel) {
const channelDataObject = channel.doc.data();
console.logBeauty(channel.type, 'channelDataObject ');
if (channel.type === 'added' || channel.type === 'modified') {
const channelData: ChannelTransformedDataType = {
...channelDataObject,
language: channelParticipantData.doc.data().language,
channelParticipantId: channelParticipantData.doc.id,
lastMessageDate: {
...channelDataObject.lastMessageDate,
},
otherUsers: [],
};
// get all channels users
channelParticipantsRef
.where('user.id', '!=', id)
.where('channelId', '==', channelDataObject.channelId)
.onSnapshot((channelParticipants) => {
const participants: UserModelType[] = [];
if (channelParticipants.empty)
return self.removeChannel(
channelDataObject.channelId,
);
channelParticipants.docs.forEach(
(channelParticipant) => {
participants.push(channelParticipant.data().user);
},
);
channelData.otherUsers = participants;
console.log(channelData, 'channelDatachannelData');
if (channel.type === 'added')
self.pushChannel(channelData);
else self.editChannel(channelData);
});
} else if (channel.type === 'removed') {
self.removeChannel(channelDataObject.channelId);
}
});
});
});
});

How To Make Queue System - Discord.js

I am working on a music and I would like to know how to add a queue system to the command; I have been looking around for hours and not been able to find anything on it,
If anyone can help that would be great and I don't need a queue command but I do need to add a queue system so it would really help I will be checking back in an hour to see if anyone has given me or an idea or the answer to my problem
This is my code so far:
const Discord = require('discord.js');
const ytdl = require('ytdl-core');
const YoutubeSearcher = new QuickYtSearch({
YtApiKey: '',
});
module.exports={
name: 'play',
category: 'music',
description: 'Joins and plays the song',
aliases: ['p'],
usage: '.play <song name or URL>',
run: async(client, message, args)=>{
try{
if (message.member.voice.channel) {
let args = message.content.split(' ').slice(1).join(' ');
if (!args) {
const error = new Discord.MessageEmbed()
.setTitle(`🔴 Looks like there is an Issue!`)
.setColor(0x2f3136)
.setDescription(`You have to provide me at least, the name or the url.\n\nExample :
\`\`\`fix
.play <url>
OR
.play <name>\`\`\``)
return message.channel.send(error);
};
message.member.voice.channel.join()
.then(connection => {
if (YoutubeSearcher.isVideoUrl(args) === false) {
YoutubeSearcher.getVideo(args).then(video => {
const volume = { volume: 10 };
const dispatcher = connection.play(ytdl(video.url, { filter: 'audioonly' }, volume));
const play1 = new Discord.MessageEmbed()
.setTitle('Song info')
.setURL(video.url)
.setDescription(`Name: ${video.title}, By: ${video.channelTitle}`)
.setThumbnail(video.highThumbnail)
message.channel.send(play1);
dispatcher.on("finish", () => {
dispatcher.end();
message.reply('End of the song.');
message.member.guild.me.voice.channel.leave();
});
});
} else {
const volume = { volume: 10 };
const dispatcher = connection.play(ytdl(args, { filter: 'audioonly' }, volume));
message.reply('Now playing ' + args);
dispatcher.on("finish", () => {
dispatcher.end();
message.reply('End of the song.')
message.member.guild.me.voice.channel.leave();
});
};
});
} else {
message.reply('You need to join a voice channel.');
};
}catch(err) {
console.log(err)
return message.channel.send(`Error: ${err.message}`)
}
}
}
In theory you could use the queue data structure (the last element is taken out and when a new one is added it is added to the start) and in the queue hold the music that is requested to be played. This is how it might roughly look like:
client.on("message", (msg) => {
var arrOfMusic = [];
if(msg.content.startsWith("!queue")){
msg.channel.send(arrOfMusic.join(" , ")
}
if(msg.content.startsWith("!play")){
arrOfMusic.push(msg.content.slice(6))
// you don't need to play the music
}
// your code to play the end of the array all you do is play the last element you also //need to check once it is over and use pop to remove last element
if(msg.content.startsWith("clear")){
arrOfMusic = []
}
})

How to for loop all documents in a collection - Azure CosmosDB - Nodejs

I have looked around at a few answers/questions regarding this issue but yet to find a solution.
I have a collection with documents (simplified) as such:
{
"id": 123
"stuff": "abc"
"array":[
{
"id2":456
"properties": [
{
"id3": 789
"important": true
}
]
}
]
}
I want to check for each document in my collection, for each array object within array, for each properties, if it has important: true for example. Then return:
"id": 123
"id2": 456
"id3": 789
I have tried using:
client.queryDocuments(self.collection._self, querySpec).toArray(function(err, results) {
if (err) {
callback(err);
} else {
callback(null, results[0]);
}
});
But the issue is an array has a maximum character limit. If my collection has millions of documents, this would presumably be exceeded. (Javascript Increase max array size)
Or, am I misunderstanding the above question? Is it talking about the number of objects in an array (of which, each can have unlimited object character length?)
Thus I am looking a for loop-esque solution, where each document is returned, I do my analysis, then move to then next/do them in parallel.
Any insight would be greatly appreciated.
But the issue is an array has a maximum character limit. If my
collection has millions of documents, this would presumably be
exceeded. (Javascript Increase max array size)
Based on my research,the longest possible array in js could have 232-1 = 4,294,967,295 = 4.29 billion elements. However, it is perfectly enough to meet your millions data volume requirements. In addition,you can't query such huge volume data directly surely,that's impossible you do that.
Whether about throughput constraints(RUs settings) or query efficiency factors, you should consider batching large volumes of data anyway.
Thus I am looking a for loop-esque solution, where each document is
returned, I do my analysis, then move to then next/do them in
parallel.
Maybe you could use v2 js sdk for cosmos db sql api.Please refer to the sample code:
const cosmos = require('#azure/cosmos');
const CosmosClient = cosmos.CosmosClient;
const endpoint = "https://***.documents.azure.com:443/"; // Add your endpoint
const masterKey = "***"; // Add the masterkey of the endpoint
const client = new CosmosClient({ endpoint, auth: { masterKey } });
const databaseId = "db";
const containerId = "coll";
async function run() {
const { container, database } = await init();
const querySpec = {
query: "SELECT r.id,r._ts FROM root r"
};
const queryOptions = {
maxItemCount : -1
}
const queryIterator = await container.items.query(querySpec,queryOptions);
while (queryIterator.hasMoreResults()) {
const { result: results, headers } = await queryIterator.executeNext();
console.log(results)
console.log(headers)
//do what you want to do
if (results === undefined) {
// no more results
break;
}
}
}
async function init() {
const { database } = await client.databases.createIfNotExists({ id: databaseId });
const { container } = await database.containers.createIfNotExists({ id: containerId });
return { database, container };
}
run().catch(err => {
console.error(err);
});
More details about continuation token ,please refer to my previous case.Any concern,please let me know.
I am using Cosmos DB SQL API Node.js library. I am unable to find the Continuation Token from this library so that I can return it to client. The idea is to get it back from the client for the next pagination request.
I have a working code which iterates multiple times to get all the documents. What changes will be required here to get the continuation token?
function queryCollectionPaging() {
return new Promise((resolve, reject) => {
function executeNextWithRetry(iterator, callback) {
iterator.executeNext(function (err, results, responseHeaders) {
if (err) {
return callback(err, null);
}
else {
documents = documents.concat(results);
if (iterator.hasMoreResults()) {
executeNextWithRetry(iterator, callback);
}
else {
callback();
}
}
});
}
let options = {
maxItemCount: 1,
enableCrossPartitionQuery: true
};
let documents = []
let iterator = client.queryDocuments( collectionUrl, 'SELECT r.partitionkey, r.documentid, r._ts FROM root r WHERE r.partitionkey in ("user1", "user2") ORDER BY r._ts', options);
executeNextWithRetry(iterator, function (err, result) {
if (err) {
reject(err)
}
else {
console.log(documents);
resolve(documents)
}
});
});
};

Resources