I'm creating an appliction using MEAN stack in which i've one to one chat feature. I'm using mongoose and have 3 schemas :
Users
{
name: {
type: String,
required: true
},
email: {
type: String
},
phone: {
type: Number,
required: true
},
profile_pic: {
type: String,
default: null
},
password: {
type: String
},
salt: {
type: String
}
}
Messages
{
message : {
type : String,
required : true
},
timestamp : {
type : Number,
required : true
},
from : {
type : String,
required : true
},
conversation_id : {
type : mongoose.Schema.Types.ObjectId,
required : true
}
}
Conversations
{
users : [ {
type: type : mongoose.Schema.Types.ObjectId,
ref : User
}]
}
Now, when user goes to his messenger i want to show him all his past conversations with latest messages so right now i'm exceuting find command on Conversation and populating User
then after getting all conversations i'm executing find command for each conversation to get their latest message, so by this there are so many find operations are done on database in single request. i.e. why i need some solution like populate latest message for each conversation ( like i can populate using _id of conversation )
Edit
Here is the code i'm using right now :
let completeConversations = [];
conversation.find({ "users": req.body.token_data.id }).populate({ path: 'users', select: 'name profile_pic' }).exec((err, conversations) => {
if (err) throw err
if (conversations.length == 0) {
res.status(200);
res.json({ message: "No Conversations Found" })
} else {
conversations.forEach(element => {
messages.find({ conversation_id: element._id }).sort('-timestamp').limit(1).exec((err, message) => {
if (err) {
res.status(200);
res.json({ message: "Latest Message Not Found", conversations })
} else {
element = JSON.parse(JSON.stringify(element));
element.latest = message[0];
completeConversations.push(element);
}
if (completeConversations.length === conversations.length) {
res.json(completeConversations)
}
})
});
}
})
thanks in Advance
Related
I have this record
{
"_id" : ObjectId("5dfdff479ad032cbbc673507"),
"selection" : [
{
"highlights" : "test",
"comment" : "CHANGE THIS",
"el" : "body:nth-child(2)>div:nth-child(2)#root>div.App>p:nth-child(1)"
},
{
"highlights" : "Barrett’s lyrical prose opens with a clever and tender solution",
"comment" : "",
"el" : "body:nth-child(2)>div:nth-child(2)#root>div.App>p:nth-child(2)"
}
],
"category" : [],
"status" : "",
"url" : "http://localhost:3000/theone",
"title" : "React App test",
"__v" : 4
}
And I want to update the comment. I have tried to use update and findOneAndUpdate and nothing is working. Here is my attempt
WebHighlight.findOneAndUpdate(
{
_id: req.params.highlight,
"selection.highlights": "test"
},
{ "selection.$.comment": "yourValue" }
);
That req.params.highlight is the id (I even hardcoded it)
I also tried this
WebHighlight.findById(req.params.highlight, (err, book) => {
var test = [...book.selection];
test[0].comment = "somethibf"
book.save();
res.json(book);
});
And nothing is working.
This is the model
const webhighlightsModel = new Schema({
selection: { type: Array, default: "" },
category: { type: Array, default: [] },
title: { type: String },
url: { type: String },
status: { type: String, default: "" }
});
Actually your code seems to work, but findOneAndUpdate returns the old document if you don't give {new: true} option.
I think for this reason, you think the update wasn't successfull, but if you check your collection, you will see the update.
WebHighlight.findOneAndUpdate(
{
_id: req.params.highlight,
"selection.highlights": "test"
},
{ "selection.$.comment": "yourValue" },
{ new: true }
)
.then(doc => res.send(doc))
.catch(err => res.status(500).send(err));
Also I think it would be better if selection had a sub schema like this:
const mongoose = require("mongoose");
const schema = new mongoose.Schema({
selection: [
new mongoose.Schema({
highlights: String,
comment: String,
el: String
})
],
category: { type: Array, default: [] },
title: { type: String },
url: { type: String },
status: { type: String, default: "" }
});
module.exports = mongoose.model("WebHighlight", schema);
So with this every selection would an _id field, and it would be better to update with this _id.
You should use the $set operator to update existing values:
WebHighlight.findOneAndUpdate(
{
_id: req.params.highlight,
"selection.highlights": "test"
},
{ '$set': { "selection.$.comment": "yourValue" } }
);
I need to save or insert a new record into my mongo database. I hope the field "userid" of it can automatically increase by 1. Is it possible to do it in mongodb?
Schema
generalUserApplication: {
userid: Number, // 1
lora: [
{},
{}
]
}
Here's the way from the mongo tutorial.
You need to create new collection counters in your db.
function getNextSequence(db, name, callback) {
db.collection("counters").findAndModify( { _id: name }, null, { $inc: { seq: 1 } }, function(err, result){
if(err) callback(err, result);
callback(err, result.value.seq);
} );
}
Then, you can use getNextSequence() as following, when you insert a new row.
getNextSequence(db, "user_id", function(err, result){
if(!err){
db.collection('users').insert({
"_id": result,
// ...
});
}
});
I have use another package named 'mongoose-sequence-plugin' to generate sequence which is auto-incremented by 1.
Here I am posting code that I have tried for same problem. Hope it will help.
Schema :
var mongoose = require('mongoose');
var sequenceGenerator = require('mongoose-sequence-plugin');
var Schema = mongoose.Schema;
var ProjectSchema = new Schema({
Name : { type : String, required: true },
Description : { type : String, required: true },
DetailAddress : { type : String, required: true },
ShortAddress : { type : String, required: true },
Latitude : { type : Number, required: true },
Longitude : { type : Number, required: true },
PriceMin : { type : Number, required: true, index : true },
PriceMax : { type : Number, required: true, index: true },
Area : { type : Number, required: true },
City : { type : String, required: true }
});
ProjectSchema.plugin(sequenceGenerator, {
field: 'project_id',
startAt: '10000001',
prefix: 'PROJ',
maxSaveRetries: 2
});
module.exports = mongoose.model('Project', ProjectSchema);
This will create projects collection with parameter project_id starting from PROJ10000001 and on wards. If you delete last inserted record then this package reads the current last entry of field project_id and next id is assigned by incrementing current id.
I have this function. Allow take a service only is not taken:
is taken only if the available param is true.
function takeService(req, res) {
var serviceId = req.params.id;
var driverId = req.body.driverId;
Service.findById(serviceId, (err, service) =>{
if (!err) {
if (!service) {
res.status(404).send({message: 'Not found'});
} else {
if (service.available === false ) {
res.status(409).send({message: 'The service is taken'});
} else {
Service.findByIdAndUpdate(serviceId, {
driverId,
status: 1,
available: false
}, (err, serviceUpdated) =>{
if (!err && serviceUpdated) {
res.status(200).send({message: "tomado"});
}
});
}
}
}
});
}
Schemas:
var ServiceSchema = Schema({
clientId: {
type: String,
ref: 'Client'
},
available: Boolean,
routeId: {
type: String,
ref: 'Route'
},
date: Date,
radius: Number,
driverId: {
type: String,
ref: 'Driver'
},
status: Number,
time: String,
createdTime: Number,
rateId: {
type: String,
ref: 'Rate'
}
});
var DriverSchema = Schema({
name: String,
surname: String,
username: String,
password: String,
status: { type: Number, default: 0 },
oneSignalId: String,
plate: String,
make: String,
year: String,
model: String,
groupId: [{
type: String,
ref: 'DriverGroup'
}],
unit: String,
telephone: String
});
The problem is when two devices call to this function, in some cases both find the document and check if is available and then both update the same document. I am looking a some validation in the schema for autocheck this property.
If I understand the problem correctly, the main issue is that two devices may think that a service is still available.
The ultimate cause of this is that there's a race condition between findById and findByIdAndUpdate: between those two calls, there's a window of time in which another request can change the document in the database.
To fix this, you can use the atomic findAndModify command, which Mongoose exposes as (amongst others) Model#findOneAndUpdate.
Your code would become something like this:
function takeService(req, res) {
var serviceId = req.params.id;
var driverId = req.body.driverId;
Service.findOneAndUpdate({
_id : serviceId,
available : true
}, {
driverId : driverId,
status : 1,
available : false,
}, (err, service) => {
if (err) {
return res.status(500);
} else if (! service) {
return res.status(409).send({message: 'The service is taken'});
} else {
return res.status(200).send({message: "tomado"});
}
});
}
There are a few differences with your original code that you should be aware of:
you can't distinguish between a service not existing (invalid/unknown serviceId) and a service that is not available anymore; in both cases, the update will yield no result and a 409 response is sent back;
findOneAndUpdate will return the old document, before it was updated. If you want to receive the updated document, pass the new option in the query:
Service.findOneAndUpdate({ ... }, { ... }, { new : true }, (err, service) => { ... })
I added an error handler in there, that sends back a 500 ("Internal Server Error") response.
I get the following error when attempting to test the schema:
"CastError: Cast to ObjectId failed for value "{ by: 58803a77479f7e2a2d069d93, at: 2017-01-19T04:03:03.156Z }" at path "_id" for model "Poll"
const PollSchema = Schema({
title: {
type: String,
required: true
},
desc: {
type: String
},
created : {
by: {
type: Schema.Types.ObjectId,
ref: 'User'
},
at: {
type: Date,
default: Date.now
}
}
})
Here is the testing code
describe('Associations', () => {
let _cindy, _poll;
beforeEach((done) => {
_cindy = new User ({
firstName : 'Cindy',
lastName : 'Cronkite'
})
_poll = new Poll ({
title : 'my first blog',
desc : 'yada yada yada'
created : {
by : _cindy._id
}
})
_poll.created = _cindy
_cindy.save()
.then(() => {
_poll.save().then(() => done())
})
})
it('saves a relation between a user and a poll', (done) => {
Poll.findOne({ title : 'my first blog'})
.populate('created')
.then( (_pollQuery) => {
console.log('_pollQuery', _pollQuery)
assert(_pollQuery.created.by.firstName === 'Cindy')
done()
})
})
})
You need to populate with created.by
Poll.findOne({ title : 'my first blog'})
.populate('created.by')
Instead of populate, I would suggest to use $lookup aggregation so that data is fetched within single query.
I have two models Users and Chat with many to many association.
User.js
module.exports = {
attributes: {
name: {
type : 'string'
,required : true
},
email:{
type : 'string'
,email : true
,required: true
,unique : true
},
enpassword : {
type: 'string'
},
online : {
type: 'boolean',
defaultsTo: false
},
socketid: {
type: 'string'
},
chats : {
collection: 'chat',
via: 'users',
dominant: true
}
};
Chat.js
module.exports = {
attributes: {
messages : {
collection: 'message',
via : 'chat',
dominant : true
},
users : {
collection: 'user',
via : 'chats'
},
}
};
When I call sails blueprint /user/1/chats I am getting list of chats but users association of each chat is not populated.
How can I achieve this from Sails queries ?
Great question. Here is a really easy way to do this.
First, require the following module.
var nestedPop = require('nested-pop');
Next, run your query.
getPopulatedUsers = function(req, res) {
User.find()
.populate('chats')
.then(function(users) {
return nestedPop(users, {
chats: [
'messages',
'users' // I actually wouldn't recommend populating this since you already have the users
]
}).then(function(users) {
console.log(users);
res.json(users);
});
});
}
More docs on this can be found at the following link.
https://www.npmjs.com/package/nested-pop