How can I make a private message schema for mongoose? - javascript

I am trying to make a messages schema + routes for my backend. I want that two users can write a message to each other and the message has to be stored for both of them.
I made the the user-model-schema and the user-routes, they are working but I'm stuck with the messaging.
mongoDB should contain the message
how can I manage sending messages?
Here is what I tried so far
messages-route:
var express = require("express");
var User = require("../models/users.js");
var router = express.Router();
const message = require("../models/messages");
router.post("/:recipient", (request, response) => {
User.find({
username: [request.body.sender, request.params.recipient],
// }, {
// message: request.body.message
// }, {
// upsert: false,
// new: true,
})
.then((users) => {
users.forEach((user) => {
user.updateMany({
message: request.body.message
})
})
response.status(200).json(users);
})
.catch((error) => {
response.status(500).json(error);
});
});
module.exports = router;
and my messages-schema:
var mongoose = require("mongoose");
var UserMessage = new mongoose.Schema({
user: { "type": mongoose.Schema.ObjectId, "ref": "User" },
username: String,
view: {
inbox: Boolean,
outbox: Boolean,
archive: Boolean
},
content: {type: String},
read: {
marked: { "type": Boolean, default: false },
date: Date
}
});
var schemaMessage = new mongoose.Schema.ObjectId({
from: String,
to: [UserMessage],
message: String,
created: Date
});
module.exports = mongoose.model("Messages", UserMessage);
I'm very unsure with the schema, I put in some suggestions I found here on stackoverflow.
Thanks!

I'm now making a messages Schema, and i thought of something like this:
const ChatSchema = new mongoose.Schema({
participants: [{type: mongoose.Schema.objectId, ref: "users"}],
last_message: Number,
_id: {type: mongoose.Schema.objectId}
//...
})
Here you could use the same schema to make chat groups, and it will work having a separate document for each message with a field with the chatroom _id, so you could query them
And you might be thinking, why not put all the messages in an array on ChatSchema?
Well, because we don't want the users to recieve all their messages each time they enter a chat.
I know, you might be thinking about something like this to prevent that over-load of data to the client
MessageSchema.find(({ ... })
.sort({ updatedAt: -1 })
.limit(20)
But here you're still recieving all the messages from the database to the server, and users could have even 1000 messages per chat, so an idea that came to my mind was making MessageSchema like this:
const MessageSchema = new mongoose.Schema({
message: String,
chatRoom_id: {type: mongoose.Schema.objectId},
sentBy: {type: mongoose.Schema.objectId, ref: "users"},
seenBy: [{ user: {type: mongoose.Schema.objectId, ref: "users"}, seen: Boolean }],
numberOfMessage: Number
//...
})
And the magic is with numberOfMessage, because you could query the MessageSchema with comparison operators like this:
const currentMessage = 151 //this is the numberOfMessage number of the last message the user saw, and when the user scrolls up the chat you could send the numberOfMessage of the last loaded message.
//To know from wich numberOfMessage start, just use the ``last_message`` field from the chatroom's ``ChatSchema``
const quantityNewMessages = 10 //this is the quantity of new messages you want the user to recieve
MessageSchema.find({
chatRoom_id,
numberOfMessage:
{ $lt: currentMessage , $gte: currentMessage + quantityNewMessages } //we use less than operator because the user will always recieve the last message first, so the previous messages are going to have a numberOfMessage less than this first message
})
And it will return us an array of 10 messages, wich you can concat to the messages array you already have in your frontend.
take note:
With this MessageSchema, all the users will share the same document for each message, you could make a separate document of a message for each user in a chatroom, so each user can delete a message without affecting the others.
I don't recommend you saving a username field, because the user could change it's username and if he does that, you would have to update ALL message documents with that username to the new username, so just leave the ref and populate it
You shouldn't put the document object in the to field, make it a separate document and only save it's ref. But I don't recommend doing it that way either
Don't use users.forEach because it modifies the array, use users.map because it returns a new array and doesn't mutates the original array
I see you have a bug, you are asking the data with the returned variable of the forEach( user => user.update(...) ), it should be map( user => UserSchema.update({ _id: user._id }) )
But still, looping an array and making a call to the DB each time is very expensive and will lag the server, so use something like the ChatSchema I showed you, because you could get the chatroom information, and with the _id of that chatroom, query the newest messages
I've not tested this code, i'm about to do it. If you run into any problem feel free to comment

Related

How to find key with type objectId , ref to <some model>

I have created a Notes model having schema as shown below
const notesschema = new Schema({
user :{
type : Schema.Types.ObjectId,
ref : 'User',
required : true
},
problem : {
type : Schema.Types.ObjectId,
ref : 'Problems',
required : true
},
content : {
type : 'string'
}
},{
timestamps : true
})
To show the User his notes for a particular task/problem I am trying to fetch notes and show to him and possibly update if he do some changes and save, The problem is with this schema I dont know how to write <model.findById >API to find notes from my notes model having particular user and specific task/problem.Which I would know the Id of.
With this particular schema , and my current knowledge i would have to write So much code. So if there is any easier way to do this task is welcomed, I was also thinking to change my schema and just placing my user id in my schema instead of whole user and finding notes from my database
edit : as suggested by all the answers we can simply find using user.id which I thought initially would not word as that was just the path, but which stores actually user.id
You create the notes' collection the same way you're doing it,
const notesSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User', // # the name of the user model
required: true
},
problem: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Problem', // # the name of the user model
required: true
},
content: String
})
Then you'll create the model of the notesSchema as follows:
const NoteModel = mongoose.model('Note', notesSchema, 'notes')
export them so you can use them in your controllers:
module.exports = {
NoteModel,
notesSchema
}
or if you're using +es6 modules (think of, if you're using TypeScript):
export default {
NoteModel,
notesSchema
}
This will result in creating the following table (collection) in the database:
Let's think of the following challenges:
To get all the notes:
NoteModel.find({})
To get all the users:
UserModel.find({}) // you should have something like this in your code of course
To get all the problems:
ProblemModel.find({}) // you should have something like this in your code of course
To get all the notes of a user:
NotesModel.find({ user: USER_ID })
To search for notes by problems:
NotesModel.find({ problem: PROBLEM_ID })
Now, the above is how you do it in mongoose, now let's create a RESTFUL controller for all of that: (assuming you're using express)
const expressAsyncHandler = require('express-async-handler') // download this from npm if you want
app.route('/notes').get(expressAsyncHandler(async (req, res, next) => {
const data = await NotesModel.find(req.query)
res.status(200).json({
status: 'success',
data,
})
}))
The req.query is what's going to include the search filters, the search filters will be sent by the client (the front-end) as follows:
http://YOUR_HOST:YOUR_PORT/notes?user=TheUserId
http://YOUR_HOST:YOUR_PORT/notes?problem=TheProblemId
http://YOUR_HOST:YOUR_PORT/notes?content=SomeNotes
const notesschemaOfUser = await notesschema.findOne({user: user_id});

MongoDB index on ref is not taking effect

I have the following model
const PostSchema = new mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
workSpace: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Workspace'
}
}
);
After getting complains of slow requests, I went over to my MongoDB Atlas dashboard in production and created an index on the Post documents like this
{
'workSpace':1
}
Yet, when I queried for posts like this
let skip=userDefined ?? 0;
let limit=1000;
let postsInWorkSpace = await Post.find({workSpace})
.sort({ created_at: 'desc' })
.skip(limit * page)
.limit(limit);
The above query still takes up to 50seconds before returning results.
So, is there something else I need to do to make the above query faster, because right now it seems the index is not working?
Thank you
after your schema definition you can try to add this line
PostSchema .index({ workSpace: 1 });

Testing validation in Mongoose without hitting the database

If I have in my application the following model:
// employee.js
const mongoose = require('mongoose');
const validators = require('./validators');
const EmployeeSchema = new mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
referredBy: {
type: Schema.Types.ObjectId,
ref: 'Employee',
validate: {
isAsync: true,
validator: validators.referralValidator
}
}
});
module.exports = mongoose.model('Employee', EmployeeSchema);
Basically, it's a model for an employee in a firm. That employee could have been referred to work at the firm by a different, existing, employee, so there is a self-referencing in the Employees collection.
The validation makes sure that the user doesn't enter an id of an employee who doesn't exist in the database already. It looks like this:
// validators.js
const mongoose = require('mongoose');
function referralValidator(val, callback) {
if (val) {
const Employee = mongoose.model('Employee');
Employee.findById(val.toString(), (err, found) => {
callback(found !== null);
});
} else {
callback(true);
}
}
module.exports = {
referralValidator
};
Now, I would like to test that this validation work (could be one of many other validations that I'll write in the future for other fields that will be added to the model). However, I would like not to hit the database in the test, so I want to stab out the findById to control manually what the value of "found" will be for the test. I couldn't figure out how to go around the mongo driver that mongoose employs behind the scenes.
How could I stab this function? Or, alternatively, is there a better way to implement the validator in order to make it more testable?
This link should be a good starting point on how to test model validations. If you want to mock database, then check out mockgoose

Get collection by user id in MongoDB

The biggest problem is I'm totally new to MongoDB. I know how to do it in SQL but am unable to shift my thinking into NoSQL. I have this model:
var accountSchema = new mongoose.Schema({
isPremium: Boolean,
website: []
});
I'm using mongoose so it creates Id, username, and password automatically. My registered user looks like this:
{
_id: ObjectId('5a79c89b59b6042a5d89584b'),
websites: ['a.com', 'b.com', 'c.com'],
username: 'a#a.a',
isPremium: false,
hash:
'a long hash',
salt: 'a long salt',
__v: 0,
};
I want want to write out the websites array. I want to grab only the websites under a certain user. (Don't want others to just see all websites).
How would I do it? Would I pass the userId after a click or make it in a session? And would the 'query' look like?
You can do it like this.
First define your model as a separate module
var mongoose = require('mongoose');
const accountSchema = mongoose.Schema(
{
isPremium: Boolean,
website: []
});
module.exports = mongoose.model('account',accountSchema);
Then you can use this model everywhere in your code
const account = require('yourModulePath');
account.findOne({YouSearchParameters}).
then((account) => {
// let websites = account.website
// Do you logic
})
You also can filter result with .select
account.findOne({YouSearchParameters}).select({ "website": 1, "_id": 0}).then((account)
So you will just get your array of websites

Why are my Mongoose One-To-Many Relationships not associating properly?

Does anyone know why the following one-to-many relationship between "users" and "posts" (users can have many posts) is not working? It appears I have setup my mongoose associations correctly, but when a new post is created, not only is it not assigned a user, but the users themselves are also not associated with any posts. I'm not sure what I might be doing wrong here.
If you see the JSON object below, it should have a user value, denoting the user whom created the post. You'll see in the Post Model below, that a user value should be created, but does not.
What am I doing wrong?
Here's the JSON object after creating a new post
{
__v: 0
_id: "587ee8f5a99b1709b012ce8f"
createdAt: "2017-01-18T04:03:01.446Z"
message: "This is my first test post!"
updatedAt: "2017-01-18T04:03:01.446Z"
}
Question: Why is the user field missing from the JSON above despite being created in the Post Model below?
Here's my Post Model:
// Setup dependencies:
var mongoose = require('mongoose');
// Setup a schema:
var PostSchema = new mongoose.Schema (
{
message: {
type: String,
minlength: 2,
maxlength: 2000,
required: true,
trim: true,
}, // end message field
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
},
{
timestamps: true,
}
);
// Instantiate our model and export it:
module.exports = mongoose.model('Post', PostSchema)
Here's my User Model:
// Setup dependencies:
var mongoose = require('mongoose');
// Setup a schema:
var UserSchema = new mongoose.Schema (
{
username: {
type: String,
minlength: 2,
maxlength: 20,
required: true,
trim: true,
unique: true, // username must be unique
dropDups: true,
lowercase: true,
validate: {
validator: function(username) {
var regex = /^[a-z0-9_]+$/i;
return regex.test(username);
},
message: 'Username may contain only letters, numbers or underscores.',
},
}, // end username field
posts: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}],
},
{
timestamps: true,
});
// Instantiate our model and export it:
module.exports = mongoose.model('User', UserSchema)
Here's the Controller that queries the DB:
Note: This is the method that runs when the post form is submitted.
// Grab our Mongoose Models:
var User = require('mongoose').model('User');
var Post = require('mongoose').model('Post');
module.exports = {
// Creates a new post for logged in user:
newPost: function(req, res) {
Post.create(req.body)
.then(function(newPost) {
return res.json(newPost);
})
.catch(function(err) {
return res.json(err);
})
}
};
Does anyone know if my associations are improperly setup and this is why I'm not getting any actual posts or users to show up in their respective fields?
It seems that my server-side controller is firing properly, as the post is actually created. But the associations themselves are not linking up and I'm not sure what I'm doing wrong.
I'm adding just a simple answer below to follow up with the example above. Essentially, #cdbajorin was correct, I was absently thinking there was some automation going on and was not appropriately following through the proper mongoose commands to achieve my desired results.
The solution to my question is as follows:
In the User Model, update the UserSchema posts attribute to be an empty array, instead of a mongoose.Schema.Types.ObjectID, since an object ID is not stored here anyhow and I misunderstood how this works.
The code:
posts: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}],
Instead, should be written simply:
posts: [],
The newPost method, in the server Controller, should be modified as follows (see comments inline for clarification):
newPost: function(req, res) {
// creates new post:
Post.create(req.body)
.then(function(newPost) {
// look up current user based on session ID:
// note: session setup not shown in this example.
User.findById(req.session.userID)
.then(function(user) {
// push new post into users.posts array from model setup**:
user.posts.push(newPost);
user.save();
return res.json(newPost);
})
})
.catch(function(err) {
return res.json(err);
})
This does solve the issue of the new post being generated, and then pushed into a user's posts array (from the UsersSchema).
Though the issue from the initial post is solved, one may question if this is the best use of database management. Storing posts inside of a user, as this example does, can take up a lot of space as users and posts start to add up.
This post ends up being duplicated in the database twice: first, as a document itself in the posts collection, and secondly, as an object in the posts array within the UserSchema.
A better solution is to keep the post as a unique document in the posts collection, but add the userID from the session information to it. Then, if all of user's posts are needed for any reason, a query to the Posts collection, based on the userID, would return all posts with that userID assigned to it. Then, only one copy of the post exists in the DB instead of two.
** Additional Note: Another way to modify the existing document would be to use an instance method, where an actual method would be inserted into the User Model (Schema) file, and called when needed:
For example, inserting the following code before the module.exports line in the UserSchema Model above, allows for convenient access this function when needed:
UserSchema.methods.addPost = function(post) {
this.posts.push(post);
this.save();
return true;
};
To call this instance method from our server Controller, we could re-write our Controller as follows:
User.findById(req.session.userID)
.then(function(user) {
// call our instance method above:
user.addPost(newPost);
return res.json(newPost);
});
The post will be pushed and saved by the instance method, which has been built into the instance object itself.

Categories

Resources