Update document in MongoDB via NodeJS - node.js

So my knowledge of NodeJS and MongoDD are non-existent (just need to do a small code update for a friend) and I'm stuck.
Need to update a single document inside a collection via a unique id but can't seem to do it.
Here's the Model (I've trimmed it down and cut out all unnecessary data). I'm trying to update the field notes inside a transaction.
In short each entry in the given (an Agent) table will have a collection of multiple Transactions & Documents. I need to update a specific Transaction with the unique _id that is auto generated.
import { Schema, model } from 'mongoose';
interface Transaction {
first_name: string;
last_name: string;
type: string;
notes: string;
}
interface Agent {
org_id: number;
transactions: Array<Transaction>;
documents: Array<string>;
}
const transactionSchema = new Schema<Transaction>({
first_name: { type: String },
last_name: { type: String },
type: { type: String },
notes: String,
});
const transactionsSchema = new Schema<Agent>({
org_id: { type: Number },
transactions: [transactionSchema],
documents: [documentTypesSchema],
});
const AgentTransaction = model<Agent>(
'agent_transaction_table',
transactionsSchema
);
export default AgentTransaction;
Here's what I tried but didn't work (obviously), again I've trimmed out all unnecessary data. Just to clarify, the endpoint itself works, but the DB update does not.
import AgentTransaction from '../models/transaction'; // the above model
transaction.put('/notes', async (req, res) => {
const { org_id, transaction_id, notes } = req.body;
try {
const notesResult = await AgentTransaction.updateOne({
'transactions._id': transaction_id,
}, {
$set: {
'notes': notes
},
});
res
.status(200)
.json({ message: 'Updated', success: true, notesResult });
} catch (error) {
res.status(400).send(error);
}
});

So I figured it out. Maybe it'll help someone else as well.
const notesResult = await AgentTransaction.updateOne({
'transactions._id': { $in: [trunc2] },
}, {
$set: {
'transactions.$.notes': notes
},
});
The main issue was that the payload object needed to target the collection folder + the wildcard + the field, not just only the field.

Related

Updating values in an object inside an array with Mongoose

I have a schema nested inside another(main) schema. I'd like to increase a Number field in the first schema, however it is an array. So I'd need to access that specific object in that array, and increase a field inside it which is a number. I think what I am looking for is the $inc operator, however I couldn't seem to get it to work.
My schema's:
const chainSchema = new mongoose.Schema({
chainName: String,
streak: Number,
});
const userSchema = new mongoose.Schema({
email: String,
password: String,
googleId: String,
pomodoroStreak: Number,
chains: [chainSchema],
});
Post route:
app.post("/chainDisplay", function (req, res) {
const clickedChain = req.body.secret;
const clickedButton = req.body.submit;
if (clickedButton === "increase") {
Chain.findOneAndUpdate(
{ chainName: clickedChain },
{ $inc: { streak: 1 } },
function (err, foundChain) {
if (!err) {
res.redirect("/chain");
}
}
);
} else if (clickedButton === "decrease") {
Chain.findOneAndUpdate(
{ chainName: clickedChain },
{ $inc: { streak: -1 } },
function (err, foundChain) {
if (!err) {
res.redirect("/chain");
}
}
);
}
});
I obtain the chainName and trying to use that as a parameter to find that specific object, and increase or decrease the streak by 1. Thank you for your help in advance.
I've tried using the $inc operator alongside mongoose's findOneandUpdate method. I am wondering if I should be updating the User, rather than the Chain itself. I was expecting to increase or decrease the "streak" key by 1 when the relevant button is clicked.

Insert same records multiple times to the Mongo database with NodeJs

I want to achive funcionality, where user in the frontend writes how many posts he want to insert in the database.. He can write 1, 5, 10, 15,.. up to 50 same posts.
The posts are then the SAME in the database, just this manually generated _id is different.
At first I thought that it can be done just like that:
exports.addPost = async (req: any, res: any) => {
try {
const newPost = new Post({
title: req.body.title,
description: req.body.description,
author: req.body.author,
});
for (let i: number = 0; i < 5; i++) {
await newPost .save();
}
res.status(201).json(newContainer);
} catch (err) {
res.status(400).json(err);
}
};
Post schema:
const PostSchema = new mongoose.Schema({
title: { type: String, required: true },
description: { type: String, required: true },
author: {
type: Schema.Authors.ObjectId,
ref: "Authors",
required: true,
},
});
module.exports = mongoose.model("Posts", PostSchema);
but I am not sure, if this is really the way to go.. What is some good practice for this (assuming that the number 5 in for loop will come in req.body.. So from user input.
Thanks
You can just use the following code:
try {
await Post.create(
new Array(5).fill(true).map((_) => ({
title: req.body.title,
description: req.body.description,
author: req.body.author,
}))
);
res.status(201).json(newContainer);
} catch (err) {
res.status(400).json(err);
}
model.create does accept passing an array of (new) documents to it. By mapping a new array of size 5 (or depending on user input) to your custom document and passing it to the create function will result in multiple documents created. A huge benefit is that you only have to perform one single database call (and await it).

Update multiple documents instance in MongoDb collection with Mongoose. save() is not a function

Mongoose newbe here. I got the following function to update the references (deleting them) in the document Post when a Tag is deleted. When I call my GraphQl API this is what I got:
message": "posts.save is not a function"
The function in my gql resolver:
async deleteTag(root, { id }, context) {
const posts = await Post.find();
const tag = await Tag.findById(id);
if(!tag){
const error = new Error('Tag not found!');
error.code = 404;
throw error;
}
posts?.forEach(async (post) => {
await post.tags.pull(id);
})
await posts.save()
await Tag.findByIdAndRemove(id);
return true;
}
This is the Post model:
const PostSchema = new Schema({
body: {
type: String,
required: true
},
tags: {
type: [Schema.Types.ObjectId],
ref: 'Tag',
required: false
},
});
and this is the Tag model:
const TagSchema = new Schema(
{
name: {
type: String,
required: true
},
},
{ timestamps: true }
);
Looks like I can't call the method save() on the array of objects returned by Exercise.find()
I used the same pattern in other functions, the difference is that there I used .findById()
Any solution? Advice and best practice advide are super welcome.
You have to save the posts individually:
posts?.forEach(async (post) => {
await post.tags.pull(id);
await post.save();
})
Or use Model.updateMany() combined with the $pull operator.
FWIW, you should probably limit the number of matching Post documents by selecting only documents that have the specific tag listed:
await Post.find({ 'tags._id' : id });

mongoose filter by multiple conditions and execute to update data

I am wondering what would be the best approach to make schema functions using mongoose. I have never used this so the way I think is somewhat limited, same goes for looking for docs, without knowing what's available, is not very efficient.
Through docs I found that either using findOneAndUpdate might solve the problem; but there are some constraints.
Here is the code I am planning to run:
models/Bookmark.js
const mongoose = require('mongoose')
const bookmarkItemSchema = new mongoose.Schema({
restaurantId: String,
cachedAttr: {
name: String,
latitude: Number,
longitude: Number,
},
})
const bookmarkListSchema = new mongoose.Schema({
listName: String,
items: [bookmarkItemSchema],
})
const bookmarkSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
lists: [bookmarkListSchema],
})
// const add = (lists, userId) => {
// let bookmark = Bookmark.findOne({userId})
// bookmark.lists.listName === lists.listName //current, new
// ? bookmark.lists.items.push(lists.items)
// : bookmark.lists.push(lists)
// return bookmark
// }
mongoose.model('Bookmark', bookmarkSchema)
Routes/bookmark.js
router.post('/bookmarks', async (req, res) => {
const {lists} = req.body
console.log(lists)
if (!lists) {
return res.status(422).send({error: 'You must provide lists'})
}
let bookmark = Bookmark.findOne({"userId": req.user._id})
if (bookmark.lists.listName === lists.listName){
let item = lists.items
bookmark.lists.items.push(item)
await bookmark.save()
res.send(bookmark)
}
try {
// const bookmark = Bookmark.add(lists, req.user._id, obj)
// await bookmark.save()
// res.send(bookmark)
let bookmark = Bookmark.findOne({"userId": req.user._id})
if (bookmark.lists.listName === lists.listName){ // THIS IS UNDEFINED. How to get this object?
let item = lists.items
bookmark.lists.items.push(item)
await bookmark.save()
res.send(bookmark)
}
} catch (e) {
res.status(422).send({error: e.message})
}
})
The req.body looks like this:
{
"lists": {
"listName": "My Saved List",
"items": {
"restaurantId": "abcdefg",
"cachedAttr": {
"name": "abcdefg",
"latitude": 200,
"longitude": 200
}
}
}
}
Basically what I commented out in the models/Bookmark.js file is what I would really like to do.
If the userId's list name already exists, then I would like to just add an item to the list.
Otherwise, I would like to add a new list to the object.
What is the best approach for doing this? Is there a straight forward mongoose api that I could use for this problem? or do I need to make two separated function that would handle each case and make that as schema methods and handle it in the routes file?

Setting a virtual field in a Model based on an async query from another model

I want to have a user setting (in a user model) that is derived from the sum of values in another model.
What I have tried to do is create a virtual value using a query like this:
var schemaOptions = {
toObject: {
virtuals: true
}
,toJSON: {
virtuals: true
}
};
/**
* User Schema
*/
var UserSchema = new Schema({
firstname: String,
lastname: String,
email: String,
username: String,
provider: String,
phonenumber: Number,
country: String,
emailverificationcode: {type:String, default:'verifyme'},
phoneverificationcode: {type:Number, default:4321 },
emailverified: {type:Boolean, default:false},
phoneverified: {type:Boolean,default:false},
}, schemaOptions)
UserSchema
.virtual('credits')
.get(function(){
//Load Credits model
var Credit = mongoose.model('Credit');
Credit.aggregate([
{ $group: {
_id: '5274d0e5a84be03f42000002',
currentCredits: { $sum: '$amount'}
}}
], function (err, results) {
if (err) {
return 'N/A'
} else {
return results[0].currentCredits.toString();
//return '40';
}
}
);
})
Now, this gets the value but it fails to work correctly (I cannot retrieve the virtual 'value' credits). I think this is because of the async nature of the call.
Can someone suggest the correct way to achieve this?
Once again many thanks for any input you can provide.
Edit:
So I am trying to follow the suggested way but no luck so far. I cannot get my 'getCredits' method to call.
Here is what I have so far:
UserSchema.method.getCredits = function(cb) {
//Load Credits model
var Credit = mongoose.model('Credit');
Credit.aggregate([
{ $group: {
_id: '5274d0e5a84be03f42000002',
currentCredits: { $sum: '$amount'}
}}
], function (err, results) {
cb(results);
}
);
};
var User = mongoose.model('User');
User.findOne({ _id : req.user._id })
.exec(function (err, tempuser) {
tempuser.getCredits(function(result){
});
})
Any ideas? Thanks again
There are a few issues with your implementation:
UserSchema.method.getCredits
^^^^^^ should be 'methods'
Also, you have to make sure that you add methods (and virtuals/statics) to your schema before you create the model, otherwise they won't be attached to the model.
So this isn't going to work:
var MySchema = new mongoose.Schema(...);
var MyModel = mongoose.model('MyModel', MySchema);
MySchema.methods.myMethod = ... // too late, model already exists
Instead, use this:
var MySchema = new mongoose.Schema(...);
MySchema.methods.myMethod = ...
var MyModel = mongoose.model('MyModel', MySchema);
I would also advise you to always check/propagate errors.

Resources