How to merge/partial update on sub document which is an array - node.js

const projectSchema = mongoose.Schema({
title: { type: String, required: true, trim: true },
projectId: { type: String, required: true, trim: true, unique: true },
company: { type: mongoose.Schema.Types.ObjectId, ref: 'Company', required: true },
description: { type: String, trim: true },
remark: { type: String, trim: true },
active: { type: Boolean, required: true },
participants: [{
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
active: { type: Boolean, required: true },
timeSpent: [{
date: { type: Date, required: true },
hour: { type: Number, required: true }
}]
}]
});
I've searched through a lot of SO questions, but none of them seems to be able to solve my problem. Sometimes I need to update one of the field from one participant, I want to update it in a way that when
//this data to apply update on the first element of the participants array
const partialParticipantUpdate = {
active: false
}
can be easily update to the sub document without rewriting all the other property. Basically like findByIdAndUpdate not findByIdAndReplace. I've tried aggregation but its only for reading data not writing data. I've also tried
let p = await Project.update({
"_id": mongoose.Types.ObjectId("5ee22f90e550c32194fb7a91"),
"participants._id": mongoose.Types.ObjectId("5ee4b5bd4fdcdb0c8813f5f5")
}, {
"$set": { "participants.0": partialParticipantUpdate } })
console.log(p);
However some of the participants property is removed after the update
{
"_id": { "$oid": "5ee22f90e550c32194fb7a91" },
"title": "Geo Chart Example",
"projectId": "myId",
"company": { "$oid": "5ee22ca46c7fc4358ced20a1" },
"description": "asd",
"remark": "re",
"active": true,
"participants": [{
"_id": { "$oid": "5ee4f37f4d8c234cc02d405a" },
"active": false,
"timeSpent": []
},
{
"_id": { "$oid": "5ee4c8a955ed7f23445a3cbc" },
"user": { "$oid": "5ee0bdc318998236706b5e5a" },
"active": true,
"timeSpent": []
}
],
"__v": 0
}
The update did change the first participant's "active" property from true to false, but it also lost some of the property like "user" and "timeSpent" array will get emptied as well

Finally I found the solution
const partialParticipantUpdate = {
active: false
}
let p = await Project.findById("5ee22f90e550c32194fb7a91");
let x = p.participants.id("5ee7224be4e4386084b87d3c");
x.set(partialParticipantUpdate);
let response = await p.save({ validateBeforeSave: false });
console.log(response);
The only downside is that I have to communicate with mongoose twice

Related

Api to list ordered product count based on date using node js and mongoDB express

Node.js:
how to create api for list ordered product count based on date .this order model and how to i create the controller for that...
const mongoose = require('mongoose')
const schema = mongoose.Schema
const orderSchema = new schema({
orderID:{type:String,required:true},
customer: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'userdatas',
},
orderItems:
{
// productName: { type: String, required: true },
// qty: { type: Number, required: true },
// amount: { type: String, required: true },
product: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'productsdatas',
},
},
shippingAddress : {
address: { type: String, required: true },
city: { type: String, required: true },
postalCode : { type: String, required: true },
country: { type: String, required: true },
},
orderDate:{type: String,
required: true},
totalAmount: {
type: Number,
required: true,
default: 0.0,
},
},
{
timestamps: true,
}
)
const Order = mongoose.model('Order', orderSchema)
module.exports = Order
mongoDB:
this is mongoDB data for order i should already created and customer and product details i populateded to show the output that time..
{
"_id": {
"$oid": "630f47d44a772822d2370794"
},
"orderID": "1",
"customerName": "vikiram",
"customer": {
"$oid": "630ccd62121a7425d6918e95"
},
"orderItems": {
"product": {
"$oid": "630d95d659998496b12f4f36"
}
},
"shippingAddress": {
"address": "123 IT street",
"city": "chennai",
"postalCode": "6000100",
"country": "india"
},
"orderDate": "25/04/2022",
"totalAmount": 11700000,
"createdAt": {
"$date": {
"$numberLong": "1650886612732"
}
},
"updatedAt": {
"$date": {
"$numberLong": "1661945812732"
}
},
"__v": 0
}
json:
i want json output in postman like this..
{
"data" : {
"May-2019" : 1,
"January-2020" : 1,
"February-2020" : 2,
"April-2020" : 1
}
}
You can achieve it by using aggregation. https://www.mongodb.com/docs/manual/aggregation/

removing object id from an array not working in mongoose

I am trying to remove logged-in users objectId from an array field called interest but it doesnt work.
model
const projectSchema = new mongoose.Schema({
owner: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
},
isShowcase: {
type: Boolean,
required: false,
default: false
},
isCollab: {
type: Boolean,
required: false,
default: false
},
title: {
type: String,
required: true,
trim: true
},
description: {
type: String,
required: true,
trim: true
},
thumbnail: {
type: String,
required: true,
trim: true
},
media: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Media',
required: false,
}],
skillsNeeded: [{
type: mongoose.Schema.Types.ObjectId,
required: false,
ref: 'Skill'
}],
contributors: [{
user: {
type: mongoose.Schema.Types.ObjectId,
required: false,
ref: 'User'
},
role: {
type: mongoose.Schema.Types.ObjectId,
required: false,
ref: 'Skill'
},
required: false,
}],
isOpen: {
type: Boolean,
required: false,
default: false
},
interest: [{
type: mongoose.Schema.Types.ObjectId,
required: false,
ref: 'User'
}]
}, {
timestamps: true
})
the route to remove interest
// delete an interest on a collaboration
router.delete('/api/project/:id/apply', auth, async (req, res) => {
const project = await Project.findOne({ _id: req.params.id, isOpen: true })
if (!project) {
res.status(500).send({ "message": "you cannot show interest at the moment" })
}
try {
project.interest = project.interest.filter((objid) => {
return objid != req.user._id
})
await project.save()
console.log(project);
return res.status(204).send(project)
} catch (e) {
res.status(500).send()
}
})
below is the project I am trying to remove a users object id from the interest
{
"isShowcase": false,
"isCollab": true,
"media": [],
"skillsNeeded": [
"5f9fc49a0282a127e4aca0fa",
"5faa596e9d54a92ab805a7a9"
],
"isOpen": true,
"interest": [
"5fa35ac783fe7042fcbbf12a"
],
"_id": "5fb98a36cc3b62aa788adbe0",
"title": "American gods",
"description": "it a story about a war between new and old gods",
"thumbnail": "https://images-na.ssl-images-amazon.com/images/S/pv-target-images/fc3ef790bcbc143d2b5b76b454b7cdcc68a19d60f629e328121713590294a895._RI_V_TTW_.jpg",
"owner": "5fa4f0af4a7bf5471c41e225",
"contributors": [],
"createdAt": "2020-11-21T21:44:22.818Z",
"updatedAt": "2020-11-22T08:22:02.377Z",
"__v": 1
}
when I run the delete request to that route, I get status 204 but the project is not returned, and when I check back on the project, I still see the project with the user's objectID still in the array.
the logged-in users object
{
"skills": [],
"isEmailConfirmed": false,
"isAdmin": false,
"isActive": true,
"hasSpecialPrevilege": false,
"_id": "5fa35ac783fe7042fcbbf12a",
"fullname": "olayinka odedeyi",
"username": "yinkus",
"email": "olayinkaodedeyi#gmail.com",
"createdAt": "2020-11-05T01:52:07.378Z",
"updatedAt": "2020-11-22T10:37:08.307Z",
"__v": 4,
"bio": "a great girl"
}
Please, what could be preventing the logged-in user id from getting removed when the delete route to do that is run?
The problem lies in how you are comparing the two objectids in filter function. You need to use .equals() method available on objectId instance.
project.interest = project.interest.filter((objid) => {
return !objid.equals(req.user._id)
})

soft delete from an array of objects using mongoose

I am trying to soft delete an object from array using mongoose-delete lib
I Have a schema like that :
const mongooseDelete = require('mongoose-delete');
const number = new Schema({
allocation_date: { type: Date, required: true, default: Date.now() },
number: { type: String, required: true },
virtual_number: { type: String, required: true },
virtual_number_id: { type: Number, required: true, unique: true },
});
const schema = new Schema(
{
allocation_id: { type: String, required: true, unique: true },
numbers: { type: [number], required: true },
},
{
timestamps: true,
},
);
number.plugin(mongooseDelete, { deletedAt : true });
exemple data :
{
"_id": "5f7c14f9388ee9ebc0b28abc",
"allocation_id": "5_2_9387323_1"
"numbers": [
{
"allocation_date": "2020-10-06T06:55:41.197Z",
"_id": "5f7c14f997d996f7988fe8cf",
"number": "*********",
"virtual_number": "**********",
"virtual_number_id": *******,
},
{
"allocation_date": "2020-10-06T06:59:41.197Z",
"_id": "5f7c14f997d996f7988fe8cf",
"number": "*********",
"virtual_number": "**********",
"virtual_number_id": *******
}
],
"createdAt": "2020-10-06T06:55:53.367Z",
"updatedAt": "2020-10-06T06:55:53.367Z",
}
When I tried to use the delete method as follow :
const deleteNumber = await VirtualNumberAllocation.findOne(
{
'numbers.virtual_number': virtualNumber.virtualNumber,
allocation_id: virtualNumber.allocationID,
});
const activeVirtualNumber = deleteNumber.numbers.filter(
(number) => number.virtual_number==virtualNumber.virtualNumber,
);
await activeVirtualNumber[0].delete();
I'm getting this error in console:
mongoose: calling `save()` on a subdoc does **not** save the document to MongoDB, it only runs save middleware. Use `subdoc.save({ suppressWarning: true })` to hide this warning if you're sure this behavior is right for your app.
Does anyone have experience with soft deleting from an array using the library?

aggregate base on match in the second collection

I'm using mongodb.
And I have collection Treatments and collection Patients.
I want to find all treatments that their patient.createdBy is equal to the user who asking the data.
So I tried this
const reminders = await Treatment.aggregate([
{
$lookup: {
from: 'patients',
localField: 'patientId',
foreignField: '_id',
as: 'patient'
}
},
{ $project: { reminders: 1, reminderDate: 1 } },
{ $match: { 'patient.createdBy': { $eq: req.user._id } } }
]);
According to some examples that i saw, it's should work like this.
But it's return me an empty array
If I remove the $match its return me object like this
{
"_id": "5d1e64bdc1506a00045c6a6f",
"date": "2019-07-04T00:00:00.000Z",
"visitReason": "wewwe",
"treatmentNumber": 2,
"referredBy": "wewew",
"findings": "ewewe",
"recommendations": "ewew",
"remarks": "wwewewe",
"patientId": "5cc9a50fd915120004bf2f4e",
"__v": 0,
"patient": [
{
"_id": "5cc9a50fd915120004bf2f4e",
"lastName": "לאון",
"momName": "רןת",
"age": "11",
"phone": "",
"email": "",
"createdAt": "2019-05-01T13:54:23.261Z",
"createdBy": "5cc579d71c9d44000018151f",
"__v": 0,
"firstName": "שרה",
"lastTreatment": "2019-08-02T14:20:08.957Z",
"lastTreatmentCall": true,
"lastTreatmentCallDate": "2019-08-04T15:17:35.000Z"
}
]
}
this is patient schema
const patientSchema = new mongoose.Schema({
firstName: { type: String, trim: true, required: true },
lastName: { type: String, trim: true, required: true },
momName: { type: String, trim: true },
birthday: { type: Date },
age: { type: String, trim: true },
lastAgeUpdate: { type: Date },
phone: { type: String, trim: true },
email: { type: String, trim: true },
createdAt: { type: Date, default: Date.now },
createdBy: { type: mongoose.Schema.Types.ObjectId, required: true },
lastTreatment: { type: Date },
lastTreatmentCall: { type: Boolean },
lastTreatmentCallDate: { type: Date }
});
And this is treatment schema
const treatmentSchema = new mongoose.Schema({
date: { type: Date, default: new Date().toISOString().split('T')[0] },
visitReason: { type: String, trim: true },
treatmentNumber: { type: Number, required: true },
referredBy: { type: String, trim: true },
findings: { type: String, trim: true },
recommendations: { type: String, trim: true },
remarks: { type: String, trim: true },
reminders: { type: String, trim: true },
reminderDate: { type: Date },
patientId: { type: mongoose.Schema.Types.ObjectId }
});
what I'm missing
You have vanished your patient field in the second last $project stage. So instead use it at the end of the pipeline. Also you need to cast your req.user._id to mongoose objectId
import mongoose from 'mongoose'
const reminders = await Treatment.aggregate([
{
$lookup: {
from: 'patients',
localField: 'patientId',
foreignField: '_id',
as: 'patient'
}
},
{ $match: { 'patient.createdBy': { $eq: mongoose.Types.ObjectId(req.user._id) } } },
{ $project: { reminders: 1, reminderDate: 1 } }
])
I think you can add using the pipeline, like below
const reminders = await Treatment.aggregate([
{
$lookup: {
from: 'patients',
localField: 'patientId',
foreignField: '_id',
as: 'patient',
pipeline: [{ $match: { 'age': { $eq: "100" } } }]
}
},
]);

Collections association with nested in Mongoose

I had a problem with the association of collections.
I spent 2 days and still did not solve the problem, it's new for me.
My models:
// Schema opened cases
const openedSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'user',
required: [true, 'user is required'],
index: true
},
weapon: {
type: Schema.Types.ObjectId,
ref: 'cases.weapons',
required: [true, 'weapon is required'],
index: true
},
sellPrice: {
type: Number,
default: null
},
status: {
type: Number,
default: 0
}
}, {
timestamps: true
});
const opened = mongoose.model('opened', openedSchema);
// list cases
const casesSchema = new Schema({
name: {
type: String,
unique: true,
required: [true, 'name is required']
},
price: {
type: Number,
required: [true, 'price is required']
},
weapons: [ {
weapon: {
type: Schema.Types.ObjectId,
ref: 'weapon',
index: true
}
} ]
}, {
timestamps: false
});
const cases = mongoose.model('cases', casesSchema);
// list weapons
const weaponSchema = new Schema({
name: {
type: String,
unique: true,
required: [true, 'name is required']
},
price: {
type: Number,
required: [true, 'price is required']
},
autoship: {
count: Number,
status: Boolean,
price: Number
}
}, {
timestamps: false
});
const weapon = mongoose.model('weapon', weaponSchema);
That's what documents look like
// cases
{
"_id": {
"$oid": "59653bcfa9ac622e1913e10c"
},
"name": "test case #1",
"price": 256,
"weapons": [
{
"weapon": {
"$oid": "59653bcfa9ac622e1913e10b"
},
"_id": {
"$oid": "59653bcfa9ac622e1913e10d"
}
},
{
"_id": {
"$oid": "59653d3279aeda2fda9fb490"
},
"weapon": {
"$oid": "59653c5d069f562eb0ba4ef3"
}
},
{
"_id": {
"$oid": "59653d38ba04de2fdddc459f"
},
"weapon": {
"$oid": "59653c893a772e2ef7b65a29"
}
}
],
"__v": 0
}
// opened
{
"_id": {
"$oid": "5965d134c8c95972a1a498f5"
},
"updatedAt": {
"$date": "2017-07-12T07:35:16.419Z"
},
"createdAt": {
"$date": "2017-07-12T07:35:16.419Z"
},
"user": {
"$oid": "5965d0d6ea9db872360db98b"
},
"weapon": {
"$oid": "59653bcfa9ac622e1913e10d"
},
"status": 0,
"sellPrice": null,
"__v": 0
}
// weapon
{
"_id": {
"$oid": "59653bcfa9ac622e1913e10b"
},
"name": "AWP | Fever Dream",
"price": 300,
"autoship": {
"status": true,
"price": 167,
"count": 5
},
"__v": 0
}
I need to get a list of open cases with weapons data.
opened -> cases -> weapon
So, I do this:
opened.find()
.populate('cases.weapons')
.then(_opened => {
console.log(_opened);
})
.catch(err => {
logger.error(err);
});
But populate does not work.
Unless I am mistaken, there is no relationship between openedSchema and casesSchema.
It is not opened -> cases -> weapon but opened -> weapon as openedSchema has no field called cases -- which means cases will never be populated.
Based on your schema definition, it should be opened.find().populate('weapon').

Resources