How do I update individual fields in nested objects that are in arrays in MongoDB using Mongoose? - arrays

This is my first post so please bear with me. I am building a LinkedIn clone and I am trying to keep track of the work experience, projects and courses of the users, and those will be kept in an array of objects inside of the User schema. Now let's say a user will try to add or update one of the elements in one of those arrays, I have the user ID and I am passing it to findOneAndUpdate() as the filter.
Here is my User schema:
const userSchema = new mongoose.Schema({
user_id: {
type: String,
required: [true, 'User ID required.'],
unique: true,
immutable: true,
},
name: {
type: String,
required: [true, 'Name required.'],
},
email: {
type: String,
required: [true, 'Email required.'],
unique: true,
lowercase: true,
immutable: true,
},
title: {
type: String,
},
location: {
type: String,
},
phone_number: {
type: String,
},
contact_email: {
type: String,
},
photo: {
type: String,
},
website: {
type: String,
},
backdrop: {
type: String,
},
summary: {
type: String,
},
work: {
type: String,
},
connections: {
type: Number,
},
projects: [
{
title: {
type: String,
},
description: {
type: String,
},
start_date: {
type: Date,
},
end_date: {
type: Date,
},
technologies: {
type: String,
},
picture: {
type: String,
},
},
],
skills: [{
skill: {
name: {
type: String,
},
level: {
type: String,
},
},
}],
experience: [
{
company: {
type: String,
},
logo: {
type: String,
},
title: {
type: String,
},
location: {
type: String,
},
start_date: {
type: Date,
},
end_date: {
type: Date,
},
description: {
type: String,
},
},
],
education: [
{
school: {
type: String,
},
logo: {
type: String,
},
degree: {
type: String,
},
location: {
type: String,
},
start_date: {
type: Date,
},
end_date: {
type: Date,
},
description: {
type: String,
},
},
],
languages: [
{
name: {
type: String,
},
level: {
type: String,
},
},
],
awards: [
{
title: {
type: String,
},
date: {
type: Date,
},
awarder: {
type: String,
},
summary: {
type: String,
},
},
],
courses: [
{
title: {
type: String,
},
number: {
type: String,
},
school: {
type: String,
},
start_date: {
type: Date,
},
end_date: {
type: Date,
},
description: {
type: String,
},
},
],
});
And in my UserController.ts file, I tried using this:
const updateUser = async (req: Request, res: Response) => {
try {
const filter = { user_id: req.body.user_id };
const update = req.body;
const updatedUser = await User.findOneAndUpdate(filter, update, {
new: true,
upsert: true,
});
res.status(201).json({
status: 'success',
data: {
user: updatedUser,
},
});
} catch (err) {
res.status(400).json({
status: `ERROR: ${err}`,
message: 'error updating user',
});
}
};
And in my request using the format of the schema but that didn't work out as expected. I know mongoose will automatically give it an _id field to each of the individual objects in the array, but again, I have had no luck updating them. I tried sending a PATCH request with this as the body to add a skill like so:
{
"user_id": "xxxxxxxxxxxxxxxx",
"title": "Mr.",
"skills" : {
"name": "Flute",
"level": "Novice"
}
}
And this was the response I got. It created a skill but didnt add the data in the skill object:
{
"status": "success",
"data": {
"user": {
"_id": "63d3715f2ef9698667230a53",
"user_id": "xxxxxxxxxxxxxxxx",
"name": "Jonathan Abitbol",
"email": "yoniabitbol1#gmail.com",
"projects": [],
"skills": [
{
"_id": "63d4068d2df30c9e943e4608"
}
],
"experience": [],
"education": [],
"languages": [],
"awards": [],
"courses": [],
"__v": 0,
"title": "Mr."
}
}
}
Any help on how to add/edit the nested objects would be appreciated.

Related

Mongoose updating subdocument array

I want to update rooms subdocument which is in Property document, but I get this error:
MongoServerError: Updating the path 'rooms.$.updatedAt' would create a
conflict at 'rooms.$'
Error which i got in postman:
{
"ok": 0,
"code": 40,
"codeName": "ConflictingUpdateOperators",
"$clusterTime": {
"clusterTime": {
"$timestamp": "7137698220989218823"
},
"signature": {
"hash": "34spx6E0zZFa5bVYFSL2JyjFszQ=",
"keyId": {
"low": 2,
"high": 1646669662,
"unsigned": false
}
}
},
"operationTime": {
"$timestamp": "7137698220989218823"
}
}
My property model:
import { IReview, ReviewSchema } from './reviews.model'
import { IRoom, RoomSchema } from './room.model'
type TCheapestRoom = {
roomType: string
bedType: string
lastPrice: number
actualPrice: number
}
export interface IProperty {
propertyType: string
name: string
description: string
city: string
photos: [string]
cheapestRoom: TCheapestRoom
adress: string
distance: number
cancellationPolicy: string
meals: string
funThingsToDo: [string]
accessibility: [string]
rooms: IRoom[]
reviews: IReview[]
}
interface IPropertyDocument extends IProperty, Document {
createdAt: Date
updatedAt: Date
// rooms: Types.Array<IRoom>
}
const PropertySchema = new Schema<IPropertyDocument>(
{
propertyType: { type: String, required: true },
name: { type: String, required: true },
description: { type: String, required: true },
city: { type: String, required: true },
photos: { type: [String] },
cheapestRoom: {
roomType: { type: String },
bedType: { type: String },
lastPrice: { type: Number },
actualPrice: { type: Number },
},
adress: { type: String, required: true },
distance: { type: Number, required: true },
cancellationPolicy: { type: String, required: true },
meals: { type: String, required: true },
funThingsToDo: { type: [String] },
accessibility: { type: [String] },
rooms: [RoomSchema],
reviews: [ReviewSchema],
},
{
timestamps: true,
}
)
const PropertyModel = model<IPropertyDocument>('Property', PropertySchema)
export default PropertyModel
Subdocument room model:
export interface IRoom {
roomType: string
bedTypes: [string]
roomFacilities: [string]
sleeps: number
lastPrice: number
actualPrice: number
cancellation: string
payment: string
breakfast: string
unavailableDates: [Date]
}
export interface IRoomDocument extends IRoom, Document {
createdAt: Date
updatedAt: Date
}
export const RoomSchema = new Schema<IRoomDocument>(
{
roomType: { type: String, required: true },
bedTypes: { type: [String], required: true },
roomFacilities: { type: [String], required: true },
sleeps: { type: Number, required: true, default: 2 },
lastPrice: { type: Number, required: true },
actualPrice: { type: Number, required: true },
cancellation: { type: String, required: true },
payment: { type: String, required: true },
breakfast: { type: String, required: true },
unavailableDates: { type: [Date] },
},
{
timestamps: true,
}
)
const RoomModel = model<IRoomDocument>('Room', RoomSchema)
export default RoomModel
room controller:
import PropertyModel from '../models/property.model'
import RoomModel from '../models/room.model'
const updateRoom = async (req: Request, res: Response) => {
try {
const updatedProperty = await PropertyModel.findOneAndUpdate(
{ _id: req.params.propertyId, 'rooms._id': req.params.roomId },
{
$set: { 'rooms.$': req.body },
},
{
new: true,
}
)
res.status(201).json(updatedProperty)
} catch (error) {
console.log(error)
res.status(400).json(error)
}
}
export { addNewRoom, deleteRoom, updateRoom }
I want to be able updating my subdocuments, in this case rooms using property id and room id.
How can i update this subdocument ?

Get Request using mongoose and Express

I'm trying to fetch the existing sample_mflix database from mongodb. At first I created a model for the movies collection.
This is my schema for movies
const mongoose = require("mongoose");
const movieSchema = new mongoose.Schema(
{
plot: {
type: String,
},
genres: [
{
type: String,
},
],
runtime: {
type: Number,
},
cast: [
{
type: String,
},
],
num_mflix_comments: {
type: Number,
},
title: {
type: String,
},
countries: [
{
type: String,
},
],
released: {
type: Date,
},
directors: [
{
type: String,
},
],
rated: {
type: String,
},
awards: {
wins: {
type: Number,
},
nominations: {
type: Number,
},
text: {
type: String,
},
},
lastupdated: {
type: String,
},
year: {
type: Number,
},
imdb: {
rating: {
type: Number,
},
votes: {
type: Number,
},
id: {
type: Number,
},
},
type: {
type: String,
},
poster: {
type: String,
},
writers: [
{
type: String,
},
],
tomatoes: {
viewer: {
rating: {
type: Number,
},
numReviews: {
type: Number,
},
meter: {
type: Number,
},
},
lastupdated: {
type: Date,
},
},
},
{ collection: "movies" }
);
const movies = mongoose.model("movies", movieSchema);
module.exports = movies;
this is my route to get all the movies from database
movieRoutes.js
const express = require("express");
const router = express.Router();
const movies = require("../models/movieModel");
router.get("/", async (req, res) => {
//res.send("Movie List");
try {
const allmovies = await movies.find({});
res.json(allmovies);
} catch (error) {
res.json(error);
}
});
module.exports = router;
But I'm unable to get any data in Postman. I'm pretty sure that the route is called but Postman loads forever and does not fetch.
How to fix this ?

MongoDB - How to Update or Insert object in array

I have the following collection
{
"likes": [],
"_id": "6086f47a3e8c0411f0a66d22",
"creator": "dimer",
"picture": "",
"title": "",
"body": "hello world",
"comments": [
{
"isReady": true,
"likes": [],
"_id": "6086fcf33e8c0411f0a66d25",
"creatorId": "607e50a16e852544d41a1d9d",
"creator": "dimer",
"body": "hello world",
"replies": [],
"timestamp": 1619459315854
},
],
"createdAt": "2021-04-26T17:12:26.632Z",
"updatedAt": "2021-04-27T04:22:28.159Z",
"__v": 0
},
I want to push into comment.replies a new reply if the comment and the post exists.
How to Update or Insert object into a nested array with conditions?
I tried this:
module.exports.createReply = async (req, res) => {
const user_ID = req.body.creatorId;
const post_ID = req.params.id;
const comment_ID = req.body.commentId;
if (!ID.isValid(user_ID) && !ID.isValid(post_ID) && !ID.isValid(comment_ID)) {
return res.status(400).send("ID unknown");
}
try {
console.log("hello woorld");
const reply = {
creatorId: user_ID,
creator: req.body.creator,
body: req.body.body,
timestamp: new Date().getTime(),
};
console.log("reply", reply);
await PostModel.findById(post_ID, (err, docs) => {
console.log(comment_ID);
const comment = docs.comments.find((comment) =>
comment._id.equals(comment_ID)
);
console.log("comment", comment);
if (!comment) return res.status(404).send("comment not found" + err);
comment.replies = [...comment.replies, reply];
return docs.save((err, docs) => {
if (!err) return res.status(200).send(docs);
return res.status(400).send(err);
});
});
} catch (error) {
return res.status(400).send(err);
}
};
I think I'm not reaching the replies because I'm getting this error:
{
"errors": {
"comments.4.creator": {
"name": "ValidatorError",
"message": "Path `creator` is required.",
"properties": {
"message": "Path `creator` is required.",
"type": "required",
"path": "creator"
},
"kind": "required",
"path": "creator"
}
},
"_message": "post validation failed",
"name": "ValidationError",
"message": "post validation failed: comments.4.creator: Path `creator` is required."
}
This is my model:
const nongoose = require("mongoose");
const PostSchema = nongoose.Schema(
{
creatorId: {
type: String,
// trim: true,
// required: true,
},
creator: {
type: String,
trim: true,
required: true,
},
title: {
type: String,
maxlength: 80,
},
body: {
type: String,
trim: true,
maxlength: 250,
required: true,
},
picture: {
type: String,
},
video: {
type: String,
},
likes: {
type: [String],
require: true,
},
comments: {
required: true,
type: [
{
isReady: {
type: Boolean,
default: true,
},
creatorId: {
type: String,
required: true,
},
creator: {
type: String,
required: true,
},
timestamp: Number,
body: {
type: String,
required: true,
trim: true,
},
likes: {
type: [String],
required: true,
},
replies: {
require: true,
type: [
{
isReady: {
type: Boolean,
default: true,
},
creatorId: {
type: String,
required: true,
},
creator: {
type: String,
required: true,
},
body: {
type: String,
required: true,
trim: true,
},
timestamp: Number,
},
],
},
},
],
},
},
{
timestamps: true,
}
);
module.exports = nongoose.model("post", PostSchema);
Like the error says, Path creator is required.
Make sure the reply has the 'creator' field.
To get the updated document in the update’s return value, you need to use findOneAndUpdate 1 or findAndModify methods. Both the methods have a parameter where you can specify to return the updated document. Note that the Mongoose ODM has corresponding methods, but may have slightly different syntax.
My solution:
module.exports.createReply = async (req, res) => {
const user_ID = req.body.creatorId;
const post_ID = req.params.id;
const comment_ID = req.body.commentId;
if (!ID.isValid(user_ID) && !ID.isValid(post_ID) && !ID.isValid(comment_ID)) {
return res.status(400).send("ID unknown");
}
try {
const reply = {
creatorId: user_ID,
creator: req.body.creator,
body: req.body.body,
timestamp: new Date().getTime(),
};
const query = { _id: post_ID };
const update = { $push: { "comments.$[elem].replies": reply } };
const options = { new: true, arrayFilters: [{ "elem._id": comment_ID }] };
await PostModel.findOneAndUpdate(query, update, options);
let updated = await PostModel.findOne({ _id: post_ID });
return res.status(200).send({
data: updated.comments.find((comment) => comment._id.equals(comment_ID)),
});
} catch (err) {
return res.status(400).send({ err: err });
}
};

Difficulties to query nested arrays with multiple itens Mongoose

I`ve modeled a document to be:
let UserSchema = new Schema({
name: { type: String, required: true, max: 50, unique: true },
email: {type: String, required: true, max: 50, unique: true},
password: {type: String, required: true, max: 20},
monitoringProject: [
{
projectName: { type: String, required: true, max: 30},
token: { type: String, required: true},
dispositives: [
{
name: { type: String, required: true, max: 15},
value: { type: String, required: true},
token: { type: String}
}
]
}
],
controlProject: [
{
projectName: { type: String, required: true, max: 30},
token: { type: String, required: true},
dispositives: [
{
name: { type: String, required: true, max: 15},
value: { type: String, required: true},
token: { type: String}
}
]
}
]
});
In the monitoringProject, for example, I can log too many different projects, and, to each project, I can log too many dispositives. So, I have nested arrays. In other project, where I have just one project, and many dispositives, I can list them and search for just one dispositive. But in this case, like this:
[ { "_id": "5ee18ece1ea5af75bae55f91", "monitoringProject": [ { "dispositives": [ { "_id": "5ee18ef31ea5af75bae55f9c", "name": "Dispositivo 1", "token": "$2b$15$NntmY9ihNZ7R1Vg1WMtmzOSjlfCoKIuHLofF6g5tKXA57WLLc2At2" }, { "_id": "5ee18ef81ea5af75bae55f9e", "name": "Dispositivo 2", "token": "$2b$15$OZB7HbDLvonsv6A71ozW7.yVY2vJOipTCj0AH7XzoafQwwDho.jpm" }, { "_id": "5ee18efc1ea5af75bae55fa0", "name": "Dispositivo 3", "token": "$2b$15$0hpDzNeTO2qJzBVyAHc/UesrnIRi/YpeAlpCLewNhmfl6fe3SEN0i" }, { "_id": "5ee2278d0754817853134bf3", "name": "Dispositivo 4", "token": "$2b$15$ldC5/CyxITPJGUvtXJ.2S.D6a3FBftg6ctQeS4ne/9xpMP5PPiszK" }, { "_id": "5ee2288956fc4c7870044395", "name": "Dispositivo 6", "token": "$2b$15$3rJt.gXEi/gALvrwxVUsWuwF2RUcBmZcoFOQeXwF45ZuKQrB3AGHG" }, { "_id": "5ee6d23d1756b7a8384bcff0", "name": "Dispositivo 7", "token": "$2b$15$C3I9STKJpf9i/7hzhvHEs.IizXpOEmgRTD3hd7ZSdcvATb73Z0zNC" } ], "_id": "5ee18ede1ea5af75bae55f94", "projectName": "Projeto 2", "token": "$2b$15$xaytMbMnGd3BMAGapX8nn.imvkQKetqx0OIlUUyP3gCxlKz/G6DiG" } ] } ]
I can only list all the Dispositives. I want to return just, for example, "Dispositivo 2".
I used to querys:
await User.find({ email: req.body.email, monitoringProject: { $elemMatch: { token: req.body.projecttoken, dispositives: { $elemMatch: { token: req.body.dispositivetoken } } } } }, { "monitoringProject.$.dispositives": 1} )
or
await User.find({ email: req.body.email, 'monitoringProject.token': req.body.projecttoken, 'monitoringProject.dispositives.token': req.body.dispositivetoken}, { 'monitoringProject.dispositives.$': 1 })
Using find or findOne. How can I return just the document I want? I tried so many different ways, but with no lucky.

Angular is not sending objects in objects

I'm sending this JSON with Angular.js to my node.js/express.js service:
{
"name": "MyGuitar",
"type": "electric",
"userid": "123",
"likes": 0,
"dislike": 0,
"guitarParts": {
"body": {
"material": "/content/img/hout.jpg",
"_id": "5566d6af274e63cf4f790858",
"color": "#921d1d"
},
"head": {
},
"neck": {
"material": "/content/img/hout.jpg",
"_id": "556d9beed90b983527c684be",
"color": "#46921d"
},
"frets": {
},
"pickup": {
},
"bridge": {
},
"buttons": {
}
}
}
The guitarParts are not saved in the MongoDB database.
Mongoose inserts the following:
Mongoose: guitars.insert({ name: 'MyGuitar', type: 'electric', userid: '123', likes: 0, _id: ObjectId("557023af9b321b541d4d416e"), guitarParts: [], __v: 0})
This is my Mongoose model:
guitarPart = new Schema({
id: { type: String, required: true },
color: { type: String, required: true },
material: { type: String, required: true },
x: { type: Number, required: false },
y: { type: Number, required: false },
width: { type: Number, required: false },
height: { type: Number, required: false},
});
guitarParts = new Schema({
body: [guitarPart],
neck: [guitarPart],
head: [guitarPart],
bridge: [guitarPart],
frets: [guitarPart],
pickup: [guitarPart],
buttons: [guitarPart]
});
guitar = new Schema({
name: { type: String, required: true, unique: false },
type: { type: String, required: true },
userid: { type: String },
likes: { type: Number },
dislikes: { type: Number },
guitarParts: [guitarParts],
kidsguitar: { type: Boolean },
lefthanded: { type: Boolean },
assemblykit: { type: Boolean }
},
{
collection: 'guitars'
});
I don't know what's going wrong. Any ideas?
According to the mongoose documentation of Subdocuments says: Sub-documents are docs with schemas of their own which are elements of a parents document array
And in your schema you provided: guitarParts is not an array, it is an object and a guitarPart is not array it is an object too. So that's why it is not saving.
So the correct way to model your Schema it would be:
var guitarDefinition = {
_id: {type: String, required: true},
color: {type: String, required: true},
material: {type: String, required: true},
x: Number,
y: Number,
width: Number,
heigth: Number
};
var guitarSchema = Schema({
name: { type: String, required: true, unique: false },
type: { type: String, required: true },
userid: String,
likes: Number,
dislikes: Number ,
kidsguitar: Boolean,
lefthanded: Boolean ,
assemblykit: Boolean,
guitarParts: {
body: guitarDefinition,
neck: guitarDefinition,
head: guitarDefinition,
bridge: guitarDefinition,
frets: guitarDefinition,
pickup: guitarDefinition,
buttons: guitarDefinition
}
});
Actually I have run in my computer and it is saving you can see my complete code here: https://gist.github.com/wilsonbalderrama/f10c38f9fb510865edc2

Resources