Build query to get chat conversation messages in sails js - node.js

Lets say i have the following model schema described as below to add some chat functionality between 2 users in my sails app. For instance if i have user A sending a message to user B, then user B will reply back to user A which will always create 2 conversations between both users. So my question is how can i query both conversations to get messages from A and B. I tried something like this but maybe theres a simple logic.
// User Model
attributes: {
username: {
type: 'string',
required: true,
unique: true,
},
conversations_sender: {
collection: conversation,
via: 'sender'
},
conversations_recipient: {
collection: conversation,
via: 'recipient'
}
}
// Conversation model
attributes: {
sender: {
model: user
},
recipient: {
model: user
},
messages: {
collection: 'message',
via: 'conversation'
}
}
// Message model
attributes: {
text: {
type: 'string'
},
conversation: {
model: 'conversation'
},
}
// Conversation Controller
get: function(req, res) {
var params = {
or : [
{
sender: req.param('sender'),
recipient: req.param('recipient')
},
{
sender: req.param('recipient'),
recipient: req.param('sender')
}
]
}
Conversation.find(params) ...
}

You should rethink your schema a bit, see this link that has a good database design for your needs:
http://www.9lessons.info/2013/05/message-conversation-database-design.html
You should be able then to fetch all the messages with the 2 user ids like this:
Conversation.findAll({sender: ..., receiver: ...})
Also you will need a timestamp for the messages, in the future you'll want to sort them somehow and also make the nice 'Read yesterday' feature

Related

how to attach or detach record on many to many sequelize association?

I have many to many association like this following model:
const Movie = sequelize.define('Movie', { name: DataTypes.STRING });
const Actor = sequelize.define('Actor', { name: DataTypes.STRING });
const ActorMovies = sequelize.define('ActorMovies', {
MovieId: {
type: DataTypes.INTEGER,
references: {
model: Movie,
key: 'id'
}
},
ActorId: {
type: DataTypes.INTEGER,
references: {
model: Actor,
key: 'id'
}
}
});
Movie.belongsToMany(Actor, { through: ActorMovies });
Actor.belongsToMany(Movie, { through: ActorMovies });
And I succsessfully create Movie when create an Actor record with this following code:
Actor.create({
name: 'Jhony',
movies: [
{ name: 'Movie 1'}, // it will generate Movie with ID 1
{ name: 'Movie 2'} // it will generate Movie with ID 2
]
}, {
include: [ Movie ]
})
but my question how can I attach multiple existing Movie record when creating an Actor?
I already try:
Actor.create({
name: 'Edward',
movieIds: [1, 2]
}, {
include: [ Movie ]
})
and:
Actor.create({
name: 'Edward',
movies: [{id: 1}, {id: 2}]
}, {
include: [ Movie ]
})
But stil didn't work. Anyone can help me, please. Thanks in advance
You can't link existing movies to a new actor while creating it. You need to call setMovies of the new actor model instance:
const actor = await Actor.create({
name: 'Edward',
})
await actor.setMovies([1, 2])
Also, please pay attention that if you execute more than one query that changes something in DB it would be much more reliable to use transactions to turn all this queries into one atomic operation.

When sails default model attributes get their values?

When sails fill default global attributes which we added on config/models.js ,
default settings looks like :
attributes: {
id: { type: 'number', autoIncrement: true },
createdAt: { type: 'number', autoCreatedAt: true },
updatedAt: { type: 'number', autoUpdatedAt: true },
}
Now if we add sth like creatorId to this default attributes , how we should fill it once for all our models ?
attributes: {
id: { type: 'number', autoIncrement: true },
createdAt: { type: 'number', autoCreatedAt: true },
updatedAt: { type: 'number', autoUpdatedAt: true },
creatorId: { type: 'number'}
}
After this change , all models have creatorId with 0 value , how I can set userId to all of my models creatorId before save without repeating my self?
In the controller you are creating the entry in the database this should be quite straight forward. Let's assume that you have two models, User, which comes with Sails built-in authentication, and a Thing, something that someone can own.
In the Thing model, I'd change the ownerId to be owner and associate it with the User model like so:
attributes: {
id: { ... },
createdAt: { ... },
updatedAt: { ... },
owner: {
model: 'User',
required: yes // Enable this when all the stuff in the db has this set
},
}
This creates an association or one-to-many relationship if you know database terminology.
Now in the controller where you create your object to be inserted:
Thing.create({
someAttribute: inputs.someValue,
someOtherAttribute: inputs.someOtherValue,
owner: this.req.me.id
});
If you want to use the created object right away, append .fetch() to the chain after .create({...}) like so:
var thing = await Thing.create({ ... }).fetch();
Let me know if something is unclear.
I'd actually recommend you invest the $9 in buying the SailsJS course. It's an official course, taught by the creator of SailsJS, Mike McNeil. It takes you from npm i sails -g to pushing to production on the Heroku cloud platform. It teaches basic Vue (parasails flavour), using MailGun, Stripe payments, and more. They link to the course on the site here
Update
Did some further digging, and was inspired by a couple of similar cases.
What you can do is expand your model with a custom method that wraps the .create() method. This method can receive the request object from your controllers, but doing this, rather than the previous suggestion, will probably be more work than just adding ownerId: this.req.me.id, to existing calls. I1ll demonstrate anyway.
// Your model
module.exports = {
attributes: { ... },
proxyCreate(req, callback) {
if(!req.body.ownerId){
req.body.ownerId = req.me.id // or req.user.id, cant remember
// which works here
}
Thing.create(request.body, callback);
}
}
And in your controller:
...
// Change from:
Thing.create(req.body);
// To:
Thing.proxyCreate(req);
...
Update #2
Another idea I had was adding the middleware on a per-route basis. I don't know the complexity of your routes, but you can create a custom middleware for only those routes.
In router.js you edit your routes (I'll show one for brevity):
....
'POST /api/v1/things/upload-thing': [
{ action: 'helpers/add-userid-to-ownerid' },
{ action: 'new-thing' }
],
....
In helpers/add-userid-to-ownerid:
module.exports: {
fn: function(req, res) {
if(!req.body.ownerId){
req.body.ownerId = req.me.id;
}
}
}

Sequelize/Postgres - Finding a chat with ONLY these two users

I have these two models: User and Conversation
User = sequelize.define('user', {
name: Sequelize.STRING
});
Conversation = sequelize.define('conversation', {
name: Sequelize.STRING
});
User.belongsToMany(Conversation, { through: 'user_conversation' });
Conversation.belongsToMany(User, { through: 'user_conversation' });
User and Conversation have a many to many relationship.
What I am trying to do is get all conversations that have userA and userB, and NO one else.
I have tried these two methods:
Conversation.findAll({
include: [{
model: User,
where: { id: { Op.and: [ userA.id, userB.id ] }
}]
});
This doesn't return anything, which makes sense. Id can't be A and B at the same time.
Conversation.findAll({
include: [{
model: User,
where: { id: { Op.or: [ userA.id, userB.id ] }
}]
});
This returns any conversation that users A and B are participating in.
How can I find any conversation where ONLY users A and B are involved? Can I somehow count the includes?

MongoDB Query Returns Empty Nested Object

I've got a 'conversations' collection in MongoDB which I'm querying from NodeJS to use the returned data to render the conversation's page.
The data has been stored in the database correctly as far as I can see, when I query it everything comes back as I'd expect, apart from a couple of nested objects - the two users that the conversation belongs to.
Here's what I get when I console.log a conversation (note the 'participants' field:
[ { _id: 57f96549cc4b1211abadf28e,
__v: 1,
messages: [ 57f96549cc4b1211abadf28d ],
participants: { user2: [Object], user1: [Object] } } ]
In Mongo shell the participants has the correct info - the id and username for both participants.
Here's the Schema:
var ConversationSchema = new mongoose.Schema({
participants: {
user1:
{
id: String,
username: String
},
user2:
{
id: String,
username: String
},
},
started: Number,
messages: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Message"
}
]
});
Here's the creation of the conversation document:
var conv = {
participants : {
"user1" : {
"id" : req.body.senderId,
"username" : req.body.senderName
},
"user2" : {
"id" : req.body.recipientId,
"username" : req.body.recipientName
}
},
created : Date.now(),
messages : [] // The message _id is pushed in later.
}
Conversation.create(conv, function(err, newConvo){
if(err){
console.log(err);
} else {
newConvo.messages.push(newMessage);
newConvo.save();
}
})
And lastly, in case it's useful, here's the query to Mongo:
// view all conversations a user belongs to
app.get('/messages', function(req, res){
Conversation.find({
$or : [
{"participants.user1.id" : req.user._id},
{"participants.user2.id" : req.user._id}
]
}, function(err, convos){
if(err){
console.log('Error getting Convos ' + err)
} else {
res.render('messages', {convos: convos, currentUser: req.user});
}
});
});
Thanks a lot for any help that!
It seems that everything is alright, the console.log just doesn't print nested objects by default. Try using:
console.log(JSON.stringify(conversation))
When logging a conversation in order to see the participants objects.
Fixed it!
Andresk's answer above was a big shove in the right direction. As he said, everything was OK, but I wasn't accessing the returned object in the correct way. It's obvious now, but I wasn't providing the index number for the 'convos' object.
I simply needed to do this, even though I was only getting one 'conversation' document back from MongoDB:
console.log(convos[0].participants.user1.username);

How define conditional "Field Selection" in mongodb 'find()' query?

Is it possible to conditionally specify the fields returned by the query. Here is my use case: I have an object with nested user conversations as follows:
{
"_id" : "someId",
user_id: 'user1',
conversations:
[
{
user_id: 'user2',
comments:
[
{
user_id: 'user2',
text: 'Hi user1'
},
{
user_id: 'user1',
text: 'Hi user2'
},
]
},
{
user_id: 'user3',
comments:
[
{
user_id: 'user3',
text: 'Hi user1'
}
]
},
]
}
I would like to allow all users to search for and view all objects but not conversations they don't own. Something as follows:
findObj = function(criteria, user, callback) {
Object.find({criteria}, {conversation:
{
if (user_id == user.id || conversations[].user_id = user.id) {1} else {0}
} }
);
}
Thanks in advance for your help,
-Eric
You can get all users that have conversations that involve user X like so :
db.users.find({$or:[{user_id: X}, {'conversations.user_id':x}]})
However this will not do what you want. You're running into a problem with your schema. You have to remove conversations from the user objects and store them in a dedicated collection that allows for queries on specific conversations seperately.

Resources