Mongoose: update one document out of a bunch - mongodb

I have the following code which nearly works. It will check if a status for a domain is already set, if not, it will push a new status. If there already is one, it will update it, unless there's multiple statuses for different domains stored, in which case it will replace all of the other documents for the other domains and not just the one entry for the specific domain.
if (Boolean(checkStat) === true) {
await userModel.findByIdAndUpdate(
{
_id: ctx.userId,
domain: args.domain,
},
{
["preferences.domain.status"]: {
domain: args.domain,
status: args.status,
},
},
);
return {
message:
"sucessfully updated status of " +
args.domain +
" to " +
args.status,
};
} else {
await userModel.findByIdAndUpdate(
ctx.userId,
{
$push: {
["preferences.domain.status"]: {
domain: args.domain,
status: args.status,
},
},
},
{
upsert: true,
new: true,
}
);
return {
message:
"sucessfully set status of " + args.domain + " to " + args.status,
};
}
preferences:
domain:
labels:
status:
0:
domain: "x.wtf"
status: "online"
_id: "Jz-ttsjEXKBSVxAN91CzD"
1:
domain: "a.lol"
status: "online"
_id: "Jz-ttsjEXKBSVxAN91CzD"
const userSchema = new mongoose.Schema({
preferences: {
domain: {
status: [
{
_id: {
type: String,
default: () => nanoid(),
},
domain: String,
status: String,
},
],
labels: [
{
_id: {
type: String,
default: () => nanoid(),
},
domain: String,
label: String,
},
],
},
},
});

You can use $ operator and findOneAndUpdate instead of findByIdAndUpdate to do that:
await userModel.findOneAndUpdate(
{
_id: ctx.userId,
"preferences.domain.status.domain": args.domain,
},
{
"preferences.domain.status.$.status": args.status,
}
);

Related

Full Text Search Fastify with Mongoose

Im trying to get a Full Text Search working through Fastify and Mongoose. But it returns an empty array.
There is no info online about this, so I just played with it for a while.
in db.ts I set the:
useCreateIndex: true
The Model (Benefit.ts):
export class Benefit extends BaseModel {
_id: ObjectID;
#prop({required: true, unique: true, index: true, text: true}) <-- set index & text to true
name: string;
#prop({index: true, text: true})
details: string;
const BenefitModel = getModelForClass(Benefit);
BenefitModel.createIndexes(err => console.log('ERROR', err)); <-- this is what I tried // err=null
export default BenefitModel;
The actual search (Benefit.controller.ts):
const searchResults = await benefitService.find(
{$text: {$search: textSearch}}, <-- textSearch is valid and comes as a string
{score: {$meta: 'textScore'}},
);
the return is with status 200:
{
"status": 1,
"results": []
}
I think I dont set the index correctly?
Any help will be greatly appriciated.
And also will live long, because there is no info about it anywhere.
Thanks.
EDIT:
current route setup
export class BenefitController {
protected routes: fastify.RouteOptions[];
constructor() {
this.routes = [];
this.setupRoutes();
}
public getRoutes(): fastify.RouteOptions[] {
return this.routes;
}
protected setupRoutes(): void {
this.routes.push({
method: 'POST',
url: '/searchBenefit',
handler: this.searchBenefit,
});
}
new BenefitController().getRoutes().forEach((route: fastify.RouteOptions) => {
fastify.route(route);
});
BTW I did set the indexes properly cause when I do listIndexes()
I get:
{ v: 2, key: { _id: 1 }, name: '_id_', ns: 'some-address.db.benefits' },
{
v: 2,
unique: true,
key: { name: 1 },
name: 'name_1',
ns: 'some-address.db.benefits',
background: true
},
{
v: 2,
key: { details: 1 },
name: 'details_1',
ns: 'some-address.db.benefits',
background: true
},
{
v: 2,
key: { _fts: 'text', _ftsx: 1 },
name: 'details_text',
ns: 'some-address.db.benefits',
background: true,
weights: { details: 1 },
default_language: 'english',
language_override: 'language',
textIndexVersion: 3
}

Mongoose - Update/Find Specific Object in an Array Not Working As Expected

I am following the docs without luck and am at a standstill while trying to update an object in an object in an array using MongoDB and Mongoose.
Here is my document:
{
fields: [
{ id: 603d63086db2db00ab09f50f, data: [Object] },
{ id: 603d63086db2db00ab09f510, data: [Object] },
{ id: 603d63086db2db00ab09f511, data: [Object] },
{ id: 603d63086db2db00ab09f512, data: [Object] },
{ id: 603d63086db2db00ab09f513, data: [Object] },
{ id: 603d63086db2db00ab09f514, data: [Object] },
{ id: 603d63086db2db00ab09f515, data: [Object] }
],
layouts: [],
_id: 603d631a6db2db00ab09f517,
bandId: '603d63146db2db00ab09f516',
eventType: 'private',
ownerId: '6039354906410800c14934c1',
__v: 0
}
I am trying to updateOne of the fields.data in the fields array. fields.data is an object as well.
I call my Express/Node Backend to this route.
//Update
router.put("/:id", async (req, res) => {
try {
let updating = await QuoteGenerator.updateOne(
{ _id: req.params.id, "fields.id": req.body.id },
{
"$set": {
"fields.$.data": req.body.data,
},
}
);
let item = await QuoteGenerator.findOne({ _id: req.params.id });
res.json({ success: "Item Updated.", item });
} catch (err) {
console.log(err);
res.json({ error: "Something went wrong when updating this item." });
}
});
Where req.body is:
{ id: '603d63086db2db00ab09f50f', data: { type: 1, rate: '200.30' } }
**Just in case it's helpful, here is what one of the fields objects looks like in the document,
{"id":"603d63086db2db00ab09f50f","data":{"type":1,"rate":300}}
I have even tried changing my route to find this document - which I have confirmed exists - Truly at a loss why it won't find the document.
Here is how I changed the above route to find the document.
//Update
router.put("/:id", async (req, res) => {
try {
let updating = await QuoteGenerator.find(
{ _id: req.params.id, "fields.id": req.body.id },
);
console.log(updating) //returns []
let item = await QuoteGenerator.findOne({ _id: req.params.id });
res.json({ success: "Item Updated.", item });
} catch (err) {
console.log(err);
res.json({ error: "Something went wrong when updating this item." });
}
});
The Model
//Create Schema - QG
const QuoteGeneratorSchema = new Schema({
bandId: {
type: String,
required: true,
},
ownerId: {
type: String,
required: true,
},
fields: {
type: Array,
default: defaultFields,
required: true,
},
eventType: {
type: String,
required: false,
},
layouts: {
type: Array,
required: false,
},
});
let QuoteGenerator = mongoose.model("QuoteGenerator", QuoteGeneratorSchema);
module.exports = QuoteGenerator;
Any nudge in the right direction to replacing that data object with a new data object would be extremely helpful! Thanks!

How do you update a nested array with Mongoose?

This is what I have so far. This is my AnswerSchema with a comments array nested within that I am trying to update.
const AnswerSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'user',
},
question: {
type: Schema.Types.ObjectId,
ref: 'question',
},
text: {
type: String,
required: true,
},
name: {
type: String,
},
avatar: {
type: String,
},
views: {
type: Number,
},
date: {
type: Date,
default: Date.now,
},
answerLikes: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'user',
},
},
],
comments: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'user',
},
text: {
type: String,
required: true,
},
name: {
type: String,
},
avatar: {
type: String,
},
commentLikes: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'user',
},
},
],
date: {
type: Date,
default: Date.now,
},
},
],
})
and here is my update route that I am trying to use to update the comments array text field
try {
const updatedAnswer = await Answer.findOneAndUpdate(
{ _id: req.params.answer_id },
{
$set: { 'comments.$[comment].text': formattedAnswer },
},
{
arrayFilters: [{'comment._id': req.params.comment_id }],
},
{ new: true }
)
res.json(updatedAnswer)
I keep getting the error 'Callback must be a function, got [object Object]' and cant figure out a fix.
Any ideas?
Thanks!
The problem in your code is that you are passing 4 parameters to the findOneAndUpdate function.
The 4th argument is a callback which accepts a function:
(err /* an error if occurred */, doc /* the updated document */) => {}
In order to solve that you need to combine your last 2 arguments into one object like:
{
arrayFilters: [{'comment._id': req.params.comment_id }],
new: true
}
Final query:
const updatedAnswer = await Answer.findOneAndUpdate(
{ _id: req.params.answer_id },
{
$set: { 'comments.$[comment].text': formattedAnswer },
},
{
arrayFilters: [{'comment._id': req.params.comment_id }],
new: true
}
)
The 4th argument in findOneAndUpdate function takes in a callback function that was where your error was.
Try this
try{
const updatedAnswer = await Answer.findOneAndUpdate(
{ _id: req.params.answer_id },
{
$set: { 'comments.$[comment].text': formattedAnswer },
},
{
arrayFilters: [{'comment._id': req.params.comment_id }],
new: true
}
);
res.json(updatedAnswer);
}catch(err){
//console.log(err)
}

Get results of aggregation query in mongoose using objectId, virtual types (it works in mongo shell)

My code on the backend, in case it matters (NodeJS and MogoDB):
//my includes at the top of the file
const mongoose = require('mongoose');
const Appt = mongoose.model('Appt');
const ApptType = mongoose.model('ApptType');
const ApptStatus = mongoose.model('ApptStatus');
var moment = require('moment-timezone');
moment().tz('America/New_York');
now = moment(); // add this 2 of 4
dayStart = now.startOf('day');
dayEnd = now.endOf('day');
// the aggregation query that's not returning correctly
Appt.aggregate([
{
$match: {
patientID: appt.patientID._id,
scheduled: {
$gte: new Date(start),
$lt: new Date(appt.pmtduedate)
}
}
},
{
$group: {
_id: 'id',
payment: { $sum: '$payment' },
pmtdue: { $sum: '$pmtdue' },
visits: { $sum: 1 }
}
}
]).exec(
err => {
console.log(`Error finding past payments`, err);
callback(err);
},
result => {
console.log(`RESULT: ${result}`);
pastPayments = result;
if (!pastPayments || pastdueamt === 0) {
pastdueamt = 0;
console.log(`2. getCurrentDue ${pastdueamt}`);
this.getCurrentDue(appt, pastdueamt, today, callback);
} else {
console.log(`pastPayments ${pastPayments}`);
console.log(
`planamt ${planamt} pmtdue ${pastPayments.pmtdue} payments: ${pastPayments.payment}`
);
pastdueamt =
pastPayments.pmtdue === 0
? planamt - pastPayments.payment
: pastPayments.pmtdue - pastPayments.payment;
console.log(`pastdueamt calculated: ${pastdueamt}`);
console.log(`2. getCurrentDue`);
this.getCurrentDue(appt, pastdueamt, today, callback);
}
}
);
When I run my query in mongo, the expected results return. In my app, the results of this query above return nothing (no error, either). I've tried doing the following:
$match: {
patientID: new mongoose.types.ObjectId(appt.patientID._id),
I've also tried:
$match: {
patientID: { $toObjectId: appt.patientID._id },
but I get errors on both of these options. The first returns an error of
TypeError: Cannot read property 'ObjectId' of undefined.
The second returns some sort of mongo error
errmsg: 'unknown operator: $toObjectId',
code: 2,
codeName: 'BadValue',
name: 'MongoError',
[Symbol(mongoErrorContextSymbol)]: {} }
How do I do mongoose aggregation successfully using objectIds, virtual types, etc.?
EDITED TO ADD MY SCHEMAS:
const apptSchema = new mongoose.Schema(
{
ID: Number,
patientID: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Patient'
},
oldPatientID: Number,
status: {
type: mongoose.Schema.Types.ObjectId,
ref: 'ApptStatus'
},
type: {
type: mongoose.Schema.Types.ObjectId,
ref: 'ApptType'
},
scheduled: Date,
note: String,
reminder: Boolean,
cell: Boolean,
email: Boolean,
subjective: String,
assessment: String,
plan: String,
planamt: Number,
objective: {
clearUC: Boolean,
UCcheck: String,
thompson: String,
activator: String,
other: String
},
updated: {
type: Date,
default: new Date()
},
pmtdue: Number,
pmtduedate: Date,
payment: Number,
pmttype: String,
paid: Boolean,
pmtnote: String
},
{ toJSON: { virtuals: true } }
);

How can I get total length(count) of all comments inside Post?

Let's say I have this Schema:
const PostSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
text: {
type: String,
required: true
},
comments: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
text: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
}
],
date: {
type: Date,
default: Date.now
}
});
And let's say I save 10 posts to the database. How can I get total length(count) of all comments they have?
This works only for whole collection of Post. It returns 10.
router.get( '/total', ( req, res ) => {
Post.estimatedDocumentCount().then( ( totalCount) => {
res.json( totalCount );
}).catch( ( err ) => {
console.log( err );
});
});
I don't want to use .count() method since it's deprecated.
Thank you
You can use $group and find total counts of comments as below:
db.collection.aggregate([
{
$group: {
_id: null,
count : { $sum: { $size: "$comments"}}
}
}
])