I am building a social media and one of the features I have is the ability to follow other users and have their posts show up on a home page, much like an Instagram feed. I have an endpoint that loops through all the users you are following and gets the post that are rated for your age, sorts them and puts them in an array.
router.get("/following", auth, async (req, res) => {
try {
const user = await User.findOne({ firebaseUID: req.authId });
const dob = dayjs(user.dob.toISOString().split("T")[0]);
const currentDate = dayjs(new Date().toISOString().split("T")[0]);
const age1 = currentDate.diff(dob, "year");
let posts = [];
for (let i = 0; i < user.following.length; i++) {
posts.push(
await Post.find({
userID: user.following[i].user,
age: { $lte: age1 },
})
);
}
posts = [].concat.apply([], posts);
posts = _.sortBy(posts, "date").reverse();
res.json(posts);
} catch (err) {
console.error(err.message);
res.status(500).json({ msg: "Server Error" });
}
});
The user schema looks like this
const UserSchema = new Schema({
firebaseUID: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
username: {
type: String,
required: true,
unique: true,
},
avatar: {
type: String,
},
dob: {
type: Date,
default: Date.now,
required: true,
},
slug: {
type: String,
},
following: [
//array of user IDs
{
user: {
type: Schema.Types.ObjectId,
ref: "users", //here so we know which lieks came from which users
},
},
],
followers: [
//array of user IDs
{
user: {
type: Schema.Types.ObjectId,
ref: "user", //here so we know which lieks came from which users
},
},
],
});
The problem is that I am trying to add infinite scroll pagination to the front end that will start by getting the first 15 posts and then get the next 15 and so on from endpoints that provide this.
What would the best way to do this be? For other routes, I have used .skip() and .limit(). I could split the array of posts up right before I send it as a response, but is there a way to do without getting all the posts as this would be taxing if you're following 100s of people with 1000s of posts.
I would need to loop through all the users you are following and get all their posts in line with your age rating but only get the first 15 sorted in date order from newest to oldest. Is there a mongoose function that I could use or would the best way be to split the array of posts?
Thank you.
Related
const categorySchema = mongoose.Schema({
name: {
required: true,
type: String
}
});
const productSchema = mongoose.Schema({
name: {
type: String,
required: true
},
category: {
type: mongoose.Schema.Types.Mixed,
ref: "Category",
required: true
}
});
As you see above I have two models where I am using Category inside Product.
Now I want to fetch all the products by passing a particular Category or its id as _id which gets generated by default by mongodb.
Although I am achieving the desired result do this below:
const id = req.query.categoryId;//getting this from GET REQUEST
let tempProducts = [];
let products = await Product.find({});
products.forEach(product => {
if (product.category._id===id){
tempProducts.push(product);
}
});
It gets me what I want to achieve but still I want to know how to get it using "find" function. or this what I am doing is the only way.
Ok thank you all for your time. I found out the solution which is:
First of all below is my schema as Product which includes another schema named Category:
const productSchema = mongoose.Schema({
name: { type: String, required: true },
category: { type: mongoose.Schema.Types.Mixed, ref: "Category", required: true } });
And to get all the products related to a particular category,
We need to add our query inside quotes like this:
let tempProducts = [];
tempProducts = await Product.find({ "category._id": id});
This is how I achieved as I wanted and working as expected.
I'm new to Aggregation in MongoDB and I'm trying to understand the concepts of it by making examples.
I'm trying to paginate my subdocuments using aggregation but the returned document is always the overall values of all document's specific field.
I want to paginate my following field which contains an array of Object IDs.
I have this User Schema:
const UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true
},
firstname: String,
lastname: String,
following: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
...
}, { timestamps: true, toJSON: { virtuals: true }, toObject: { getters: true, virtuals: true } });
Without aggregation, I am able to paginate following,
I have this route which gets the user's post by their username
router.get(
'/v1/:username/following',
isAuthenticated,
async (req, res, next) => {
try {
const { username } = req.params;
const { offset: off } = req.query;
let offset = 0;
if (typeof off !== undefined && !isNaN(off)) offset = parseInt(off);
const limit = 2;
const skip = offset * limit;
const user = await User
.findOne({ username })
.populate({
path: 'following',
select: 'profilePicture username fullname',
options: {
skip,
limit,
}
})
res.status(200).send(user.following);
} catch (e) {
console.log(e);
res.status(500).send(e)
}
}
);
And my pagination version using aggregate:
const following = await User.aggregate([
{
$match: { username }
},
{
$lookup: {
'from': User.collection.name,
'let': { 'following': '$following' },
'pipeline': [
{
$project: {
'fullname': 1,
'username': 1,
'profilePicture': 1
}
}
],
'as': 'following'
},
}, {
$project: {
'_id': 0,
'following': {
$slice: ['$following', skip, limit]
}
}
}
]);
Suppose I have this documents:
[
{
_id: '5fdgffdgfdgdsfsdfsf',
username: 'gagi',
following: []
},
{
_id: '5fgjhkljvlkdsjfsldkf',
username: 'kuku',
following: []
},
{
_id: '76jghkdfhasjhfsdkf',
username: 'john',
following: ['5fdgffdgfdgdsfsdfsf', '5fgjhkljvlkdsjfsldkf']
},
]
And when I test my route for user john: /john/following, everything is fine but when I test for different user which doesn't have any following: /gagi/following, the returned result is the same as john's following which aggregate doesn't seem to match user by username.
/john/following | following: 2
/kuku/following | following: 0
Aggregate result:
[
{
_id: '5fdgffdgfdgdsfsdfsf',
username: 'kuku',
...
},
{
_id: '5fgjhkljvlkdsjfsldkf',
username: 'gagi',
...
}
]
I expect /kuku/following to return an empty array [] but the result is same as john's. Actually, all username I test return the same result.
I'm thinking that there must be wrong with my implementation since I've only started exploring aggregation.
Mongoose uses a DBRef to be able to populate the field after it has been retrieved.
DBRefs are only handled on the client side, MongoDB aggregation does not have any operators for handling those.
The reason that aggregation pipeline is returning all of the users is the lookup's pipeline does not have a match stage, so all of the documents in the collection are selected and included in the lookup.
The sample document there is showing an array of strings instead of DBRefs, which wouldn't work with populate.
Essentially, you must decide whether you want to use aggregation or populate to handle the join.
For populate, use the ref as shown in that sample schema.
For aggregate, store an array of ObjectId so you can use lookup to link with the _id field.
So I have collections of Teacher, Classes. to understand my problem its necessary to understand my database. the structure is
const TeacherSchema = new Schema(
{
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
role: {
type: String,
default: "teacher"
},
userid: {
type: String,
required: true
},
password: {
type: String,
required: true
},
profileImage: {
type: String,
required: false
},
classes: [{
type:Schema.Types.ObjectId,
ref:'class'
}]//1 teacher will have multiple classes
},
{ timestamps: true }
);
and the class:
const ClassSchema = new Schema(
{
subject: {
type: String,
required: true
},
teacher:[{
type:Schema.Types.ObjectId,
ref: 'teacher'
}],//once class will have only one teacher
},
{ timestamps: true }
);
What i want to do is, I want to show teacher how many classes he/she has. here is my controller.
exports.getAllClass= async (req, res, next) => {
let teacherClasses
try{
teacherClasses= await Teacher.findById(req.params.id)//teacher database
arrayOfClass= teacherClasses.classes //getting the class it has array of objectID
arrayOfClass.forEach(async (classes)=>{
let classDB =await Class.findById(classes)
console.log(classDB.subject)// in the console it shows all the classes.
return res.status(200).json({
'name':classDB.subject //but here it only shows one class,the first class name
})
});
}catch(err){
console.log(err)
}
}
I have tried many thing, but cant reach to a proper solution. Can you tell me what did i do wrong here or is there any better approach? I want to show all the classes in the return. I am new in programming hence assigned in complex task, please help me.
You are using forEach on the arrayOfClass and within that forEach you return and send the response. So as soon as the first promise is resolved, the response is sent.
One simple way to do this is to use a for .. of loop and await each class-promise inside and keep track of each result. Once that's done you can send the response:
...
const classes = [];
for(const classId of arrayofClass) {
const classDB = await Class.findById(classId);
classes.push({ name: classDB.subject });
}
return res.json(classes);
Another way to do this is to use Promise.all() which might be better if you're dealing with a bigger dataset as is processes in parallel whereas the first solution processes in sequence:
...
const classPromises = await Promise.all(arrayofClass.map(classId => Class.findById(classId)));
const result = classPromises.map(classDB => ({
name: classDB.subject
}));
return res.json(result);
I'm learning to use Node.js + Express to build a REST API. In this API
I have the following method:
apiRouter.route('/training/session/byId/:id_session')
// ===== GET =======
.get(function(req, res) {
//Get the session
Session.findById(req.params.id_session, function(err, session) {
//Get an array of exercise associated with the session
Exercise.find({session: session._id}, function(err, exercise) {
let movements = [];
let sets = [];
let i = exercise.length-1;
//For every exercise get the movements and the sets
exercise.forEach(function (ex,index) {
Movement.findById(ex.movement,function(err,movement){
if(movement)
movements.push(movement);
//***** Here?
Set.find({exercise: ex}, function (err, set) {
if(set.length)
sets.push(set);
if(index == i){
res.json({ message: 'ok' ,session,exercise,movements,sets});
}
})
})
})
});
});
})
The idea is obtain all the session related information from the database.
First:
I think that is not the correct way of make multiple querys and return an object with the info of all the querys, but I'm novice with the async working of Node... So what is the correct way to make multiple querys where the data of one query depends of other query?
Second: In the Front-End (React + Redux) I'm making Ajax request with axios and for the same Ajax request sometimes not all 'sets' are fetched (//***** Here?). The problem is in the API?
Thanks in advance.
EDIT: DB models
Session:
var SessionSchema = new Schema({
date: {type: Date, default: Date.now },
time: Number, //Time in seconds
user: {required: true, type: mongoose.Schema.Types.ObjectId, ref: 'User'},
});
Exercise:
var ExerciseSchema = new Schema({
session: {type: mongoose.Schema.Types.ObjectId, ref: 'Session'},
movement: {type: mongoose.Schema.Types.ObjectId, ref: 'Movement'},
timestamp: {
type: Date,
default: Date.now
}
});
Set:
var SetSchema = new Schema({
repetitions: Number,
weight: Number,
rest: Number,
timestamp: {
type: Date,
default: Date.now
},
exercise : {type: mongoose.Schema.Types.ObjectId, ref: 'Exercise'}
});
Movement:
var MovementSchema = new Schema({
name: { type: String, required: true, index: true, unique: true },
material:{ type: String, required: true},
muscles : [{
name : { type: String, required: true},
percentage : { type: Number, required: true}
}]
});
Set.find({exercise: ex}, function (err, set) {
if(set.length)
sets.push(set);
}).then(function(set){
if(index == i){
res.json({ message: 'ok' ,session,exercise,movements,sets});
}
})
Of course my prev answer wouldn't work. The set query callback would execute after the if(index == i). Actually I'm not sure this will produce different results from your code. I've never actually used Mongoose but as far as I know, you can't do joins so nested queries is the way to do it.
You might want to consider using promises. Not necessary, but they make your code easier to read and think about: http://eddywashere.com/blog/switching-out-callbacks-with-promises-in-mongoose/
It might make more sense as well to create a single result object that you build up as the queries return so you end up sending a single JSON object representing your session that looks like:
{
exercises: [
{
sets: [],
movements: [],
},
{
sets: [],
movements: [],
},
...
]
}
I got 3 database models in mongoose that looks like this:
//profile.js
var ProfileSchema = new Schema({
username: { type: String, required: true },
password: { type: String, required: true },
matches: [{ type: Schema.Types.ObjectId, ref: 'Match' }]
});
//match.js
var MatchSchema = new Schema({
scores: [{ type: Schema.Types.ObjectId, ref: 'Score', required: true }],
});
//score.js
var ScoreSchema = new Schema({
score: {type: Number, required: true},
achivement: [{type: String, required: true}],
});
And I try to populate a profile with
Profile.findOne({ _id: mongoose.Types.ObjectId(profile_id) })
.populate('matches')
.populate('matches.scores')
.exec(function(err, profile) {
if (err) {...}
if (profile) {
console.log(profile);
}
});
The matches get populated but I dont get the scores in matches to populate. Is this not supported in mongoose or do I do something wrong? Populate gives me this:
{
user_token: "539b07397c045fc00efc8b84"
username: "username002"
sex: 0
country: "SE"
friends: []
-matches: [
-{
__v: 1
_id: "539eddf9eac17bb8185b950c"
-scores: [
"539ee1c876f274701e17c068"
"539ee1c876f274701e17c069"
"539ee1c876f274701e17c06a"
]
}
]
}
But I want to populate the score array in the match array. Can I do this?
Yes, you are right. I tried using Chaining of populate I got same output.
For your query please use async.js and then populate by the method mentioned below.
For more details please have a look at this code snippet. It is a working, tested, sample code according to your query. Please go through the commented code for better understanding in the code below and the link of the snippet provided.
//Find the profile and populate all matches related to that profile
Profile.findOne({
_id: mongoose.Types.ObjectId(profile_id)
})
.populate('matches')
.exec(function(err, profile) {
if (err) throw err;
//We got the profile and populated matches in Array Form
if (profile) {
// Now there are multiple Matches
// We want to fetch score of each Match
// for that particular profile
// For each match related to that profile
async.forEach(profile.matches, function(match) {
console.log(match, 'match')
// For each match related to that profile
// Populate score achieved by that person
Match.find({
_id:match.id
})
.populate('scores', 'score')
.exec(function (err, score) {
if (err) throw err;
// here is score of all the matches
// played by the person whose profile id
// is passed
console.log(score);
})
})
}
});
Profile.findOne({ _id: mongoose.Types.ObjectId(profile_id) })
.populate('matches.scores')
.exec(function(err, profile) {
if (err) {...}
if (profile) {
console.log(profile);
}
});