I'm trying to access the array of Message Models that is stored in my Conversation Model. However, when I use the populate method to try to store the Message models as an array, only the first Message is showing up.
socket.on('connected', function (data) {
//load all messages
const filter = { roomId: data.roomid };
(async () => {
console.log('searching for Schema');
let conversation = await Conversation.findOne(filter)
.populate('messages')
.exec(function (err, message) {
if (err) console.log('no schema found');
var array = message.messages;
console.log(array);
// printing only first Message
});
})();
});
Conversation Schema
const ConversationSchema = new mongoose.Schema(
{
roomId: {
type: String,
required: true
},
messages: {
type: mongoose.Schema.Types.ObjectId, ref: 'Message'
}
},
{
timestamps: true
}
);
populate method not store message as an array.Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s).Refer this for more detail
To solve your problem modify declaration of messages field in Conversation Schema
messages: [{
type: mongoose.Schema.Types.ObjectId, ref: 'Message'
}]
Related
I am making a social media backend.
I save post added by the used in a Post model and user data in a User model.
GITHUB_REPO_LINK_AT_END
NOTE: UserSchema have a Schema.TypesOf.ObjectId Reference To POST Model. User_Model_&_Post_Model_are_provided_in_the_end
To get all posts of a particular user, I make a GET request to the route "/post" with body:
{ "id" : "6399d54c00308a2fe0bdf9fc"} //sending user id to fetct all the ID of the post from USER model, so i can then query the POST model for the posts
This the function I am getting problem with:
const getPost = async(req, res)=>{
if(req.body.id){
try {
const user = await User.findById(req.body.id).select('-_id post');
//THIS IS THE PART I NEED HELP WITH-------------------------------------------
const posts = await user.post.map(async(postID) => {
const result = await Post.findById(postID).select('-_id title body');
//console.log(result) THIS PRINTS THE CORRECT OBJ FROM DB
return result; //THIS RETURNS AN EMPTY OBJECT HERE
});
//----------------------------------------------------------------------------
res.status(200).json(posts);
} catch (error) {
console.log(error);
res.status(500).json({message: error.message});
}
}
};
when sending a GET request it returns an empty array with empty objects.//PS: no. of empty obj = actual no. of obj in DB
//This is the response
[{},{},{},{},{},{},{},{},{},{},{}]
{
//This is the user object
"_id": "6399d54c00308a2fe0bdf9fc",
"createdAt": "2022-12-14T13:52:40.483Z",
"name": "ShivamUttam",
"username": "Fadedrifleman",
"post": [
"6399d57200308a2fe0bdfa00",
"6399d5c400308a2fe0bdfa06",
"6399d5ca00308a2fe0bdfa0a",
"6399d5d600308a2fe0bdfa0e",
"6399de29e8aa8697299941c5",
"6399dec6e9b79ac66c59cd7a",
"6399df0dbea937f8b3365979",
"6399df31bea937f8b336597d",
"6399df31bea937f8b3365981",
"6399df32bea937f8b3365985",
"6399df33bea937f8b3365989"
],
"__v": 5
}
Model for USER and POST:
User:
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
createdAt: {
type: Date,
default: Date.now()
},
name: {
type: String,
required: [true, 'name must be provided'],
},
username : {
type: String,
required: [true, 'Username must be provided'],
},
post:[{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post',
}],
});
module.exports = mongoose.model('User', userSchema)
Post:
const mongoose = require('mongoose')
const postSchema = new mongoose.Schema({
createdAt: {
type: Date,
default: Date.now()
},
title:{
type: String,
required: [true, "title cannot be empty"],
max: [20, "title cannot exceed 20 character"]
},
body: {
type: String,
max: [145, "body cannot exceed 145 character"],
},
tags:{
type: String,
},
});
module.exports = mongoose.model('Post', postSchema);
https://github.com/Fadedrifleman/socialMediaAppBackend/tree/master
Since you have used async callback function in the map method, a async function always return a promise, whatever the entity is returned by the function is wrapped inside a promise and that promise is returned.
If you want to use map function with async js code, you can try the following
const posts = await Promise.all(user.post.map(async(id)=>{
const result = await Post.findById(postID).select('-_id title body');
return result;
}));
and if you want to straightaway send the posts, you can also use .lean() method on posts, as in
await Post.findById(postID).select('-_id title body').lean()
You had some bugs that probably would interfere, I did a pull request to fix them: https://github.com/Fadedrifleman/socialMediaAppBackend/pull/1
But the main part would be this:
const getPost = async (req, res) => {
try {
if (req.body.id) {
const user = await User.findById(req.body.id);
await user.populate("post");
res.status(200).json(user.post);
return;
}
const posts = await Post.find({ access: 'public' }).select('-access');
res.status(200).json(posts);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
I'm developing a MERN chat app and trying to replicate the feature which WhatsApp provides -> message sent/seen status, so for that I need to check whether the message I/user sent is successfully synced/updated/created in my MongoDB, I know similar functionality can be achieved in AWS Amplify using the event outboxMutationEnqueued which says Dispatched when a local change has been newly staged for synchronization with the cloud, so it works like whenever we are trying to push something to synchronize with the cloud, this event is going to be fired, and once it is finished, outboxMutationProcessed is going to be triggered which says Dispatched when a local change has finished syncrhonization with the cloud and is updated locally.
So we can listen to these events whenever we are trying to send a message, and once our message mutation is processed we are going to receive outboxMutationProcessed, and then we can update the status of the message to sent or single tick or delivered.
import Amplify, {Hub} from 'aws-amplify';
useEffect(() => {
const listener = Hub.listen('datastore', async (hubData) => {
const {event, data} = hubData.payload;
if (event === 'networkStatus') {
console.log('User has a network connection: ', data.active);
}
if (event === 'outboxMutationProcessed') {
if (data.model === Message)
console.log('Mutation has been synced with the cloud: ', data);
// set the message status to delivered.
}
})
return () => listener();
}, []);
So, the question, do we have something similar in MongoDB? I'm currently using React Native, Node, Express, Sockets, Mongoose, MongoDB.
Currently, my API end point and collections (for creating a new message and saving in to db):
I have 3 collections: users, 'messages', 'chatRooms'.
const mongoose = require('mongoose');
const MessageSchema = mongoose.Schema({
roomId: String,
senderId: String,
text: String,
status: String, // INQUEUE, SENT, DELIVERED, READ
}, {
timestamps: true,
});
module.exports = mongoose.model('Message', MessageSchema);
router.post('/create_message', checkAuth, async (req, res) => {
const {chatRoomId, senderId, text} = req.body;
try {
const message = new Message({
chatRoomId,
senderId,
text,
});
const result = await message.save();
return res.status(200).json({
type: 'success',
data: result,
});
} catch (error) {
return res.status(422).send({error: `${error.message}`});
}
});
// SOCKET IMPLEMENTATION FOR REALTIME FEATURE
socket.on('listener', async data => {
io.to(id).emit('new_message', data);
const message = new Message({
chatRoomId: data.roomId,
senderId: data.senderId,
text: data.text,
status: data.status, // data.status = 'INQUEUE'
});
await message.save();
// maybe something here...? not sure
// data.status = 'SENT' after successful creation of document.
});
Maybe an event which we can fire, during the await message.save(...something here...), and if it is successfully saved in our DB, we can send it to our frontend or using socket?
If anyone could provide an example, it would be really helpful!
Edit: I changed this up a bit.
schemas:
const UserSchema = new mongoose.Schema({
name: String,
online: Boolean,
isActive: Boolean,
})
const RoomSchema = new mongoose.Schema({
name: String,
members: [{ type: mongoose.Schema.Types.ObjectId, ref: 'users' }],
})
const MessageSchema = new mongoose.Schema({
roomId: { type: mongoose.Schema.Types.ObjectId, ref: 'rooms' },
senderId: { type: mongoose.Schema.Types.ObjectId, ref: 'users' },
text: String,
deliveredTo: [{ user: { type: mongoose.Schema.Types.ObjectId, ref: 'users' }, timestamp: Date }],
readBy: [{ user: { type: mongoose.Schema.Types.ObjectId, ref: 'users' }, timestamp: Date }],
})
app.js
Mongo.connect(uri, (err, client) => {
mongoose.connect(uri)
socketClient.on('connection', socket => {
//
//on load all users for login select
User.find().then(users => socket.emit('users', users))
....
//
//setting pipeline for readStream to only watch for changes in the messages collection
//where the roomId field is the current room the user has loaded
//setting fulldocument : 'updateLookup' option will always return the fulldocuemnt with any updates
//without this, the fulldocument will only be returned on our insert change
const pipeline = [{ $match: { 'fullDocument.roomId': ObjectId(room.id) } }]
const collection = client.db('chat').collection('messages')
const messageStream = collection.watch(pipeline, { fullDocument: 'updateLookup' })
messageStream.on('change', async function (change) {
const updates = change.updateDescription?.updatedFields
//
//if a new document is created in messages, then push that message to the client
if (change.operationType.match(/insert/i)) {
const doc = await Message.findById(change.fullDocument._id).populate(
'deliveredTo.user readBy.user'
)
const status = await getStatus(doc, room.id)
socket.emit('push message', { doc, status: status })
//
//if delivered to has been updated on a message then send to client
} else if (updates?.deliveredTo) {
const status = await getStatus(change.fullDocument, room.id)
socket.emit('status change', {
id: change.fullDocument._id,
status: status,
user: change.fullDocument.senderId,
})
} else if (updates?.readBy) {
//
//if readby has been updated on a message then send to client
const status = await getStatus(change.fullDocument, room.id)
socket.emit('status change', {
id: change.fullDocument._id,
status: status,
user: change.fullDocument.senderId,
})
}
})
...
})
})
I'm quite newb to mongodb and mongoose, so I ask you to help me.
I've an API, which works and now I want to extend it with filtering for given params.
I've a Order model, which points to two different collections of documents Material & User Schemas and have a quantity element.
let Order = new Schema({
materials:
{
type: Array,
material: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Material'
},
qty: {
type: Number
}
},
userId: {
type: Schema.Types.ObjectId,
ref: 'User'
}
}, {
collection: 'orders'
})
Also I've method to create an order:
exports.createOrder = (req, res) => {
if (!req.body.user) {
res.status(400).send({message: 'Content can not be empty!'});
}
const order = new Order({
materials: req.body.materials,
userId: req.body.user
});
order
.save(order)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the Order."
});
});
}
If I create Order filling only Material ID, it creates and filtering by given material ID in filter request.
post request
filter request
But If I trying to point qty it isn't present in response.
post request with qty
filter request ending with previous document id
There is my question: How can I create Order exact way I need (Material ID and qty number must persist) and How can I perform a filtering operations on them?
Any help appriciated.
My mistake was in method how I create order as well as I make a filtering request.
Correct method to create order with data storing in array type is following
exports.createOrder = (req, res) => {
if (!req.body.user) {
res.status(400).send({message: 'Content can not be empty!'});
}
const order = new Order({
materials: {material: req.body.materials, qty: req.body.qty},
userId: req.body.user
});
order
.save(order)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the Order."
});
});
}
as you can see, difference is how I form materials array.
next thing is in filter request
exports.filterOrder = (req, res) => {
Order.find({"materials.material": req.body.material})
.then(data => {
console.log(data);
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving Orders."
});
});
}
If I need to filter orders contain necessary material I need to place subelement of array in quotes with dot notation. This will work also with "material.qty" parameter if needed.
I have a REST API built with Node JS and I'm currently using MongoDB as my database. I want to prevent the users from deleting another user's products and for this I checked if the userId from the decoded token is the same as the product userId.
Product schema
const mongoose = require("mongoose");
const productSchema = mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
userId: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
price: { type: Number, required: true },
productImage: { type: String, required: false },
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "Category",
required: true
},
gender: { type: String, required: true }
},
{ timestamps: { createdAt: "created_at" } }
);
module.exports = mongoose.model("Product", productSchema);
The delete product method:
const id = req.params.productId;
Product.findById({ _id: id }).then((product) => {
if (product.userId != req.user._id) {
return res.status(401).json("Not authorized");
} else {
Product.deleteOne({ _id: id })
.exec()
.then(() => {
return res.status(200).json({
message: "Product deleted succesfully",
});
})
.catch((err) => {
console.log(err);
return res.status(500).json({
error: err,
});
});
}
});
};
As you guys see first I'm searching executing the findByID method to access the userId property of the product, then I'm comparing the userId from the response with the userId from the decoded token.
I don't think my method is very efficient since it's running both findById and deleteOne methods.
Can you help me with finding a better solution for this?
as Guy Incognito mentioned, what you are trying to do is an OK thing and you may want to keep it this way in case you want to send a 404 status stating the product they are trying to remove does not exist.
however, if you are trying to do it with only one request
Product.deleteOne({ _id: id, userId: req.user._id })
hope it helps!
I have two models in my mongoDB. One is user model and one is event model. What I want to achieve is, when I will add a new event then, that event array will save to my user model's events column array. Till now, I've successfully saved that id of that event to specific user but the problem is only the **objectID** is coming not the property is showing up likeevent name,descriptions`. This is my code:
user model:
const userSchema = mongoose.Schema({
name: String,
events: [
{
type: Schema.Types.ObjectId,
ref: 'Event'
}
]
});
Event model:
const eventSchema = new Schema({
creator: {
type: Schema.Types.ObjectId,
ref: 'User'
},
ename: {
type: String
},
description: {
type: String
},
});
routes.js
router.post('/', (req, res) => {
const event = new Event();
event.creator = '5d9e1ba694f44227e1f54cc0',
event.ename = req.body.ename;
event.save( (err, result) => {
if (!err)
res.redirect('events');
else {
console.log('error', err);
}
});
User.findOneAndUpdate('5d9e1ba694f44227e1f54cc0', {$push: { events: [event] }}, (err, result) => {
if(err) {
console.log(err);
} else {
console.log('event is saved');
}
});
});
for simplicity, I've kept my user id hardcoded. Now, how can I update the $push method of the mongodb so that the whole properties of the event is to save in the user model not only the objectID
I think you may be getting a little confused with how you have set up your model & how mongoose Query Population works.
In userSchema, you have defined that events is an array of type Schema.Types.ObjectId and each 'Event' references a document in the eventSchema schema. Because of this, you cant expect events to be an array of Event.
However, because you have defined this reference in your schema when you query for a document, you can use the Model.Populate method.
const user = await findById("id").populate("events");
This method will find a User and for each Event will look up the Event collection to find a relating document with that ObjectID. This will return a User with the events array being populated with the corresponding Event