mongodb insert data into the array of objects and update it - node.js

I need to make a vote, it looks like an array of objects, look like the user’s ID and the value that he set.
If the user has already voted, but changed his value, you need to change the value of the rate in the array of objects for this user.
I need to make an array of objects into which data will be inserted like this {rate: 3, user: "asdr2r24f2f42f24"} and if the user has already voted in this array, then you need to change the value rate of the given user
I already tried to do something, but it seems to me you can write something better, can you help?
JSON https://jsoneditoronline.org/?id=442f1dae0b2d4997ac69d44614e55aa6
router.post('/rating', (req, res) => {
console.log(req.body)
// { id: 'f58482b1-ae3a-4d8a-b53b-ede80fe1e225',
// rating: 5,
// user: '5e094d988ddbe02020e13879' }
Habalka.find({
_id: req.body.id
})
.then(habalka => {
// here I need to check whether the user has already voted or not, and from this whether to add an object with it or update the number
Habalka.updateOne(
{_id: req.body.id},
{$push: {rating: {rate: req.body.rating, user: req.body.user}}}
)
.then(e => {
console.log(e)
})
});
});
Schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const HabalkaSchema = new Schema({
_id: {
type: String
},
bio: {
firstname: String,
lastname: String,
middlename: String,
company: String
},
rating: [
],
files: [
{
_id: {
type: String
},
destination: {
type: String
},
filename: {
type: String
},
path: {
type: String
},
folder: {
type: String
},
info: {
size: {
type: Number
},
mimetype: {
type: String
},
encoding: {
type: String
},
originalname: {
type: String
},
fieldname: {
type: String
},
},
date: {
type: Date,
default: Date.now
},
bio: {
type: Object
},
userId: String,
guessId: {},
}
],
date: {
type: Date,
default: Date.now
}
});
module.exports = Habalka = mongoose.model('habalka', HabalkaSchema);

This is an aggregation query which inserts a new user or updates the rating of existing user in the rating array:
The req.body.id, req.body.user and req.body.rating are set as follows for the example code:
var ID = 1, INPUT_USER = "new user", INPUT_RATE = 5;
const matchStage = { $match: { _id: ID } };
const facetStage = {
$facet: {
new_user: [
{ $match: { "rating.user": { $not: { $eq: INPUT_USER } } } },
{ $addFields: { rating: { $concatArrays: [ "$rating", [ { user: "new user", rate: INPUT_RATE } ] ] } } },
],
user: [
{ $match: { "rating.user": INPUT_USER } },
{ $addFields: {
rating: {
$map: {
input: "$rating",
as: "r",
in: {
$cond: [ { $eq: [ "$$r.user", INPUT_USER ] },
{ user: "$$r.user", rate: { $add: [ "$$r.rate", INPUT_RATE ] } },
"$$r"
]
}
}
}
} }
]
}
};
const projectStage = {
$project: {
result: { $arrayElemAt: [ { $concatArrays: [ "$user", "$new_user" ] }, 0 ] }
}
};
const queryPipeline = [
matchStage,
facetStage,
projectStage
];
// Run the aggregation query and get the modified document
// after applying the user and rate data in the rating array.
// The result of the aggregation is used to update the collection.
col.aggregate(queryPipeline).toArray( ( err, docs ) => {
console.log("Aggregation output:");
console.log( JSON.stringify( docs[0] ) );
// Update the aggregate result to the collection.
col.updateOne( { _id: docs[0].result._id },
{ $set: { rating: docs[0].result.rating } },
( err, updateResult ) => {
console.log( 'Updated count: ', updateResult.matchedCount );
}
);
callback(docs);
} );
Example collection document:
{ "_id" : 1, "rating" : [ { "user" : "user1", "rate" : 2 } ] }
If the input is var ID = 1, INPUT_USER = "new user", INPUT_RATE = 5; the updated document will be:
{ "_id" : 1, "rating" : [ { "user" : "user1", "rate" : 2 }, { "user" : "new user", "rate" : 5 } ] }
If the input is var ID = 1, INPUT_USER = "user1", INPUT_RATE = 5; the updated document will be:
{ "_id" : 1, "rating" : [ { "user" : "user1", "rate" : 7 } ] }

Related

how to update the specific field in mongoose

the present Object is :-
{
_id: "abc12344",
renderId: '123456789',
concernTo: [{
name: 'vijay 4',
id: 'snhdkjn786786',
commons: []
},
{
name: 'ak',
id: 'sdkfg787877',
commons: []
}
]
}
Output needs to be like:
{
_id: "abc12344",
renderId: '123456789',
concernTo: [{
name: 'vijay 4',
id: 'snhdkjn786786',
commons: [{to: "xyz", from:"abc"}, {to: "xyz", from:"abc"}]
},
{
name: 'ak',
id: 'sdkfg787877',
commons: []
}
]
}
So need to push data in concernTo in commons field
In query i'm trying to search by renderId and id in "concernTo" array field and trying to update the object.
Query for that:---
let obj = { to: "xyz", from: "abc" };
let filter = {
renderId: "123456789",
"concernTo.id": "snhdkjn786786",
};
let update = {
$push: {
"concernTo.id.$": { subComment: obj },
},
};
let doc = await blogsCommentModel.findOneAndUpdate(filter, update, {
returnOriginal: false,
});
console.log(doc);
You want to be using arrayFilters for this, like so:
let doc = blogsCommentModel.findOneAndUpdate(filter,
{
"$push": {
"concernTo.$[elem].commons": obj
}
},
{
arrayFilters: [
{
"elem.id": "snhdkjn786786"
}
]
})
Mongo Playground

Remove one field from mongoDB collection

I have a mongoDB collection which I use with a mongoose Schema :
const balanceSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId, ref: 'user'
},
incomes: { Number },
fees: { Number },
},
{ strict: false })
I use the strict mode to false, so I can push any 'key' I want with its value.
I would like to delete just one of the "incomes" category, but I can't specify the line because there is no 'defined key'.
Here is an exemple of the data inside :
{
"_id": {
"$oid": "60c763df3d260204865d2069"
},
"incomes": {
"income1": 1300,
"anyKeyNameIWant": 400
},
"fees": {
"charge1": 29,
"charge2": 29,
"chargetest": 29,
"charge7": 29
},
"__v": 0,
}
I tried this, but no success :
module.exports.deleteOneBalance = (req, res) => {
let data = req.body
if (!ObjectID.isValid(req.params.id))
return res.status(400).send('ID unknown : ' + req.params.id);
BalanceModel.update(
{ _id: req.params.id },
{
$unset: { "incomes.salairetest": "400" }
}), (err, docs) => {
if (!err) res.send('Deleted. ' + data)
else console.log('Error : ' + err)
}
}
Any idea ?
There are several ways to delete fields with dynamic field names.
One solution is this one:
var unset = {};
unset["incomes." + "anyKeyNameIWant"] = null;
db.balanceModel.updateOne({ _id: req.params.id }, { $unset: unset })
Or you can use an aggregation pipelinie like this:
db.balanceModel.updateOne(
{ _id: req.params.id },
[
{ $set: { incomes: { $objectToArray: "$incomes" } } },
{ $set: { incomes: { $filter: { input: "$incomes", cond: { $ne: ["$$this.k", "anyKeyNameIWant"] } } } } },
{ $set: { incomes: { $arrayToObject: "$incomes" } } }
]
)
If you want to remove/unset specific value/(s) from the documents then you have to provide the complete path of that key.
Let's take an example if you want to remove anyKeyNameIWant then your path will be incomes.anyKeyNameIWant and the update query will be like this
db.sample.update(
{
_id: ObjectId("60c763df3d260204865d2069")},
{
$unset: {"incomes.anyKeyNameIWant":""}
})
In your code, you are passing an object having the key incomes in $unset which will remove the complete incomes key from the document
Here is the link to the official document in case you want more details $unset

How to find records in nested documents?

So I'm learning mongoose and I've implemented a Customer model like this:
let CustomerSchema = new Schema({
stripe_id: {
type: String,
required: true
},
telegram_id: {
type: Number
},
email: {
type: String,
required: true
},
subscriptions: [SubscriptionSchema],
created_at: {
type: Date,
default: Date.now,
required: true
}
});
essentially I would like to return all the subscriptions of a customer, but how can I search in nested document, in this case subscriptions?
This is the subscription model:
let SubscriptionSchema = new Schema({
status: {
type: String,
required: true
},
plan_id: {
type: String,
required: true
}
});
I would like to return only the subscriptions which have as status active, at the moment I'm able to search for customer as:
let customer = await CustomerModel.findOne({telegram_id: ctx.chat.id});
You can use the filter aggregation to filter in a nested array.
Playground
Sample express route with mongoose:
router.get("/customers/:id", async (req, res) => {
let result = await Customer.aggregate([
{
$match: {
telegram_id: 1 //todo: req.params.id
}
},
{
$project: {
_id: "$_id",
stripe_id: "$stripe_id",
telegram_id: "$telegram_id",
email: "$email",
subscriptions: {
$filter: {
input: "$subscriptions",
as: "item",
cond: {
$eq: ["$$item.status", "active"]
}
}
}
}
}
]);
//todo: result will be an array, you can return the result[0] if you want to return as object
res.send(result);
});
Let'a say we have the following document:
{
"_id" : ObjectId("5e09eaa1c22a8850c01dff77"),
"stripe_id" : "stripe_id 1",
"telegram_id" : 1,
"email" : "email#gmail.com",
"subscriptions" : [
{
"_id" : ObjectId("5e09eaa1c22a8850c01dff7a"),
"status" : "active",
"plan_id" : "plan 1"
},
{
"_id" : ObjectId("5e09eaa1c22a8850c01dff79"),
"status" : "passive",
"plan_id" : "plan 2"
},
{
"_id" : ObjectId("5e09eaa1c22a8850c01dff78"),
"status" : "active",
"plan_id" : "plan 3"
}
],
"created_at" : ISODate("2019-12-30T15:16:33.967+03:00"),
"__v" : 0
}
The result will be like this:
[
{
"_id": "5e09eaa1c22a8850c01dff77",
"stripe_id": "stripe_id 1",
"telegram_id": 1,
"email": "email#gmail.com",
"subscriptions": [
{
"_id": "5e09eaa1c22a8850c01dff7a",
"status": "active",
"plan_id": "plan 1"
},
{
"_id": "5e09eaa1c22a8850c01dff78",
"status": "active",
"plan_id": "plan 3"
}
]
}
]
If you don't want to project the items one by one, you can use addFields aggregation like this:
router.get("/customers/:id", async (req, res) => {
let result = await Customer.aggregate([
{
$match: {
telegram_id: 1
}
},
{
$addFields: {
subscriptions: {
$filter: {
input: "$subscriptions",
as: "item",
cond: {
$eq: ["$$item.status", "active"]
}
}
}
}
}
]);
res.send(result);
});
You can do something like this.
await CustomerModel.findOne({telegram_id: ctx.chat.id})
.populate({
path: 'subscriptions',
match: {status: 'active'}
});
here path is used to join the next model and match is used to query inside that model.

Why Mongoose createdAt / updatedAt output a wrong time(only return current time not the time in db)

The Data stored in my db is:
{
"_id" : ObjectId("58da135cfc80bc44f7653fd4"),
"updatedAt" : ISODate("2017-03-28T08:00:59.541Z"),
"createdAt" : ISODate("2017-03-28T07:40:12.742Z"),
"name" : "hello",
"delete" : false,
"enabledPlugins" : [
ObjectId("58c24f65b363502f907738f9")
],
"__v" : 0
}
My Schema Like:
const mongoose = require('./db');
const { Schema } = mongoose;
const templateSchema = new Schema({
name: { type: String, index: true, unique: true },
enabledPlugins: [
{ type: Schema.Types.ObjectId }
],
delete: { type: Boolean, default: false }
}, {
timestamps: true
});
const Template = mongoose.model('Template', templateSchema);
module.exports = Template;
But When I want to get templates, I get the wrong timestamp:
exports.getAllTemplates = async function() {
return await Template.aggregate(
{ $match: { delete: false } },
{ $project: { id: '$_id', _id: 0, name: 1, enabledPlugins: 1, createdAt: 1 } }
);
};
The result like :
[
{
"createdAt": "2017-03-28T17:04:30.502+08:00",
"name": "hello",
"enabledPlugins": [
"58c24f65b363502f907738f9"
],
"id": "58da135cfc80bc44f7653fd4"
}
]
And I found before toJSON, the output has been wrong. I don't use any plugins. All the date type has the same problem.
Thanks, the problem is that I rewrite Date.prototype.toISOString

Find object id in object ids array returns empty array using Mongoose

I have two Mongoose schemas:
var EmployeeSchema = new Schema({
name: String,
servicesProvided: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Service'
}]
});
var ServiceSchema = new Schema({
name: String
});
I'm trying to find employees who provide a specified service with the service ID I send into the http request. This is my code:
Employee
.find({
servicesProvided: req.params.service_id
})
.exec(function(err, employees) {
if (err) {
console.log(err);
res.send(err);
} else {
res.json(employees);
}
});
The problem is that this code returns an empty array and I don't know why. I've tried a lot of things like casting the service id to mongoose.Schema.Types.ObjectId but it doesn't work.
Any idea? I'm using Mongoose 3.8.39. Thanks!
In your EmployeeSchema, servicesProvided is an array, to filter employees by that field you should use $in operator:
var services = [req.params.service_id];
Employee.find({
servicesProvided: {
$in: services
}
}, ...
I think you need $elemMatch! From docs:
{ _id: 1, results: [ { product: "abc", score: 10 }, { product: "xyz", score: 5 } ] },
{ _id: 2, results: [ { product: "abc", score: 8 }, { product: "xyz", score: 7 } ] },
{ _id: 3, results: [ { product: "abc", score: 7 }, { product: "xyz", score: 8 } ] }
Search like:
db.survey.find({ results: { $elemMatch: { product: "xyz", score: { $gte: 8 } } } })
Results in:
{ "_id" : 3, "results" : [ { "product" : "abc", "score" : 7 }, { "product" : "xyz", "score" : 8 } ] }
But since you're doing a single query condition (look at the docs again) you can replace
db.survey.find(
{ results: { $elemMatch: { product: "xyz" } } }
)
with
db.survey.find(
{ "results.product": "xyz" }
)
So in your case it should be something like:
find({
'servicesProvided': ObjectId(req.params.service_id)
})

Resources