Problem with getting average rating from mongodb in node.js express - javascript

I have a node.js express back-end with a mongodb database on the cloud(mongodb atlas).
This is the Post object and I want to get the average grade from reviewerComments and userComments.
import mongoose from 'mongoose'
const postSchema = mongoose.Schema({
game: {
},
reviewerComments: [
{
author: String,
authorEmail: String,
avatar: String,
title: String,
text: String,
grade: Number,
date: String
}
],
userComments: [
{
author: String,
authorEmail: String,
text: String,
grade: Number,
date: String
}
]
}, {
timestamps: true
})
const Post = mongoose.model('Post', postSchema)
export default Post
I have a API route that calls this function
export const getAverageGrades = async (req, res) => {
const { id } = req.query
try {
const post = await Post.aggregate([
{
$match:
{
_id: mongoose.Types.ObjectId(`${id}`)
}
},
{
$unwind: "$reviewerComments"
},
{
$group:{
_id:{
"comment_id": "$reviewerComments.id",
"title": "$reviewerComments.title",
"grade": "$reviewerComments.grade",
},
avg_rating: {$avg:"reviewerComments.grade"}
}
},
{
$project:{
"id": "$_id.rating_id",
"title": "$_id.title",
"avg_rating": "$avg_rating" // avg_rating returns null
}
}
])
res.status(200).json(post);
} catch (error) {
console.log(error.message)
res.status(404).json({ message: error.message });
}
}
This function is returning a response where avg_rating is null, what am I doing wrong?

Related

How can I $push an item in two different fields, depending on the condition?

I'm trying to receive the user location and store it in the database. Also, the user can choose if he wants to save all his previous locations or not.
So I have created a boolean variable historicEnable: true/false.
So when the historicEnable is true, I want to push to historicLocation[] array in the UserSchema and if it is false, I want just to update currentLocation[] array in the UserSchema.
conntrollers/auth.js
exports.addLocation = asyncHandler(async (req, res, next) => {
const {phone, location, status, historicEnable} = req.body;
let theLocation;
if (historicEnable== true){
theLocation = await User.findOneAndUpdate(
{ phone },
{ $push:{ locationHistoric: location, statusHistoric: status }},
{ new: true }
)
} else if(historicEnable== false){
theLocation = await User.findOneAndUpdate(
{ phone },
{ location, status },
{ new: true }
)
}
res.status(200).json({
success: true,
msg: "A location as been created",
data: theLocation,
locationHistory: locationHistory
})
})
models/User.js
...
currentLocation: [
{
location: {
latitude: {type:Number},
longitude: {type:Number},
},
status: {
type: String
},
createdAt: {
type: Date,
default: Date.now,
}
}
],
historicLocation: [
{
locationHistoric: {
latitude: {type:Number},
longitude: {type:Number},
},
statusHistoric: {
type: String
},
createdAt: {
type: Date,
default: Date.now,
}
}
]
Also, not sure how to make the request body so the function works.
req.body
{
"phone": "+1234",
"historicEnable": true,
"loications": [
{
"location": {
"latitude": 25,
"longitude": 35
},
"status": "safe"
}
]
}
To sum up, if historicEnable is true, the data will be pushed in historicLocation, and if it false, to update the currentLocation.
How can I solve this?
You can use an update with an aggregation pipeline. If the historicEnable is known only on db level:
db.collection.update(
{phone: "+1234"},
[
{$addFields: {
location: [{location: {latitude: 25, longitude: 35}, status: "safe"}]
}
},
{
$set: {
historicLocation: {
$cond: [
{$eq: ["$historicEnable", true]},
{$concatArrays: ["$historicLocation", "$location"]},
"$historicLocation"
]
},
currentLocation: {
$cond: [
{$eq: ["$currentLocation", false]},
{$concatArrays: ["$currentLocation", "$location"]},
"$currentLocation"
]
}
}
},
{
$unset: "location"
}
])
See how it works on the playground example
If historicEnable is known from the input, you can do something like:
exports.addLocation = asyncHandler(async (req, res, next) => {
const phone = req.body.phone
const historicEnable= req.body.historicEnable
const locObj = req.body.location.locationHistoric[0];
locObj.createdAt = req.body.createdAt
const updateQuery = historicEnable ? { $push:{ locationHistoric: locObj}} : { $push:{ currentLocation: locObj}};
const theLocation = await User.findOneAndUpdate(
{ phone },
updateQuery,
{ new: true }
)
res.status(200).json({
success: true,
msg: "A location as been created",
data: theLocation,
locationHistory: locationHistory
})
})

mongodb after updating document, returns old values

router.delete('/deletepost', (req, res) => {
// console.log(req.query.postid)
if (req.query.category === 'forsale') {
ForSalePosts.findById(req.query.postid)
// .then(post => console.log(post))
.deleteOne()
.catch(err => console.log(err))
AllPosts.updateOne({ user: req.query.userid },
{ $pull: { posts: { postid: req.query.postid } } })
.catch(err => console.log(err))
AllPosts.aggregate(
[
{ $match: { user: ObjectId(req.query.userid) } },
{ $unwind: '$posts' },
{ $sort: { 'posts.date': -1 } }
]
)
.then(posts => {
// console.log(posts)
res.json(posts)
})
.catch(err => res.status(404).json({ nopostfound: 'There is no posts' }))
}
})
this is my route. i am trying to delete an item in my document. the item is being deleted however it returns old values. for example :
Allposts has an array with posts:[postid:{type:String}, ...]
I am trying to delete a specific postid by using $pull,
postid is being deleted however when I aggregate the same model, .then(posts=> console.log(posts)) returns old values on first call, doesnt update the component.
EDIT: just realized sometimes it returns the right values but sometimes it returns the old values as well. does anyone know why and what can i do to solve it ?
const mongoose = require('mongoose')
const Schema = mongoose.Schema;
const AllPostsSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
posts: [{
postid: {
type: String
},
title: {
type: String
},
category: {
type: String
},
subcategory: {
type: String
}, category: {
type: String
},
description: {
type: String
},
name: {
type: String
},
price: {
type: Number
},
email: {
type: String
},
phonenumber: {
type: Number
},
language: {
type: String
},
make: {
type: String
},
model: {
type: Number
},
odometer: {
type: Number
},
condition: {
type: String
},
state: {
type: String
},
town: {
type: String
},
city: {
type: String
},
links: [{ type: String }],
date: {
type: Date,
default: Date.now
}
}]
})
module.exports = AllPosts = mongoose.model('allposts', AllPostsSchema)
REACT FUNCTION CALL :
deletePost = (category, postid) => {
const postinfo = {
category: category.toLowerCase(),
postid: postid,
userid: this.props.auth.user.id
}
this.props.deletePost(postinfo)
}
You need to add options parameter to delete like:
AllPosts.updateOne({ user: req.query.userid },
{
$pull: { posts: { postid: req.query.postid } }
},
{ new: true }
);
This will return the new object after performing the operation. Hope this works for you.
All the mongo queries return partial promise. You have to use .then in order to resolve each promises.
Here you are running all the queries in series without using .then or async-await. So whenever you $pull from AllPosts after that immediately you call the AllPosts aggregate query which sometimes get executed and sometimes it doesn't.
So in order to make it run one by one you have to use either .then or async-await.
router.delete("/deletepost", (req, res) => {
if (req.query.category === "forsale") {
ForSalePosts.findById(req.query.postid)
.deleteOne()
.then(() => {
AllPosts.updateOne(
{ "user": req.query.userid },
{ "$pull": { "posts": { "postid": req.query.postid } } }
)
.then(() => {
AllPosts.aggregate([
{ "$match": { "user": ObjectId(req.query.userid) } },
{ "$unwind": "$posts" },
{ "$sort": { "posts.date": -1 } }
]).then(posts => {
// console.log(posts)
res.json(posts);
});
})
.catch(err =>
res.status(404).json({ "nopostfound": "There is no posts" })
);
});
}
})

Send nested object on GET

I have a very basic schema which has another object called Vehicle, inside
let rentSchema = new Schema({
code: {
type: Number
},
vehicle: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Vehicle'
},
ongoing: {
type: Boolean,
default: false
}
}, {collection: 'RentCollection'});
Find all in the controller
exports.getRent = function (req, res) {
// Find in the DB
rentSchema.find({}, function (err, rent) {
if (err) res.status(400).send(err);
res.json(rent);
});
};
The response comes as an array of Rents but Vehicle object is missing from the Object Rent. Why is that?
_id: "5e04c19d0a0a100f58bd64b5"
__v: 0
ongoing: false
Here is step by step explanations to make it work:
1-) First you need to create a model and export it like this:
rent.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
let rentSchema = new Schema(
{
code: {
type: Number
},
vehicle: {
type: mongoose.Schema.Types.ObjectId,
ref: "Vehicle"
},
ongoing: {
type: Boolean,
default: false
}
},
{ collection: "RentCollection" }
);
module.exports = mongoose.model("Rent", rentSchema);
2-) Let's say you have this Vehicle model:
vehicle.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
let vehicleSchema = new Schema(
{
name: String
},
{ collection: "VehicleCollection" }
);
module.exports = mongoose.model("Vehicle", vehicleSchema);
3-) First let's create a vehicle like this in the VehicleCollection like this:
{
"_id": "5e0f465205515667746fd51a",
"name": "Vehicle 1",
"__v": 0
}
4-) Then let's create a rent document in RentCollection using this vehicle id like this:
{
"ongoing": false,
"_id": "5e0f46b805515667746fd51d",
"code": 1,
"vehicle": "5e0f465205515667746fd51a",
"__v": 0
}
5-) Now we can use the following code, to populate the vehicle with the rents.
const Rent = require("../models/rent"); //todo: change to path to the rent.js
exports.getRent = function(req, res) {
Rent.find({})
.populate("vehicle")
.exec(function(err, rent) {
if (err) {
res.status(500).send(err);
} else {
if (!rent) {
res.status(404).send("No rent found");
} else {
res.json(rent);
}
}
});
};
6-) The result will be:
[
{
"ongoing": false,
"_id": "5e0f46b805515667746fd51d",
"code": 1,
"vehicle": {
"_id": "5e0f465205515667746fd51a",
"name": "Vehicle 1",
"__v": 0
},
"__v": 0
}
]
You will have to use the populate method to populate a vehicle object.
From docs:
rentSchema.
findOne({}).
populate('vehicle').
exec(function (err, obj) {
if (err) return handleError(err);
console.log(obj);
});
Also in your current code, you havent setted up model:
RentCollection = mongoose.model('RentCollection', rentSchema);

How to retrieve data from collection using find() method in custom date format

my Schema
Customers.js
import mongoose from 'mongoose';
const Customers = mongoose.Schema({
CustomerID: { type: String, default: "" },
Name: { type: String, default: "" },
Email: { type: String, default: "" },
PhoneNumber: { type: String, default: "" },
Join_Date: { type: Date, default: null }
}, { collection: 'Customers' });
export default mongoose.model('Customers', Customers);
my router controller
import Customers from "./Customers";
router.post('/List_All_Customers', (req, res) => {
Customers.find().lean().exec().then((Data) => {
res.json({Data: Data});
}).catch((err) => {
console.log(err);
});
})
My Current Result
{
Data: [
{
CustomerID: "6ad050d4-04ac-41f2-8c93-49f68f106889",
Name: "Uday Kumar",
Email: "uday#blaabla.com",
PhoneNumber: "+91-991010191",
Join_Date: "2018-04-24T12:00:00.000Z"
},
{
CustomerID: "792b67f9-9026-43bc-9017-46cd2568b4e9",
Name: "Prem Kumar",
Email: "prem#blaabla.com",
PhoneNumber: "+91-881010091",
Join_Date: "2018-04-24T15:00:00.000Z"
}
]
}
Expecting Result
{
Data: [
{
CustomerID: "6ad050d4-04ac-41f2-8c93-49f68f106889",
Name: "Uday Kumar",
Email: "uday#blaabla.com",
PhoneNumber: "+91-991010191",
Join_Date: "Apr-24 2018, 12:00:00"
},
{
CustomerID: "792b67f9-9026-43bc-9017-46cd2568b4e9",
Name: "Prem Kumar",
Email: "prem#blaabla.com",
PhoneNumber: "+91-881010091",
Join_Date: "Apr-24 2018, 15:00:00"
}
]
}
Is there anyway in mongodb for result in custom dates. I am using mongoose mongodb connection in my project.
I can manipulate data using for loop with momentjs but it is taking time.
So i need one solution for my question.
Thanks in advance.
Comments are appreciated.
You Can use javascript map function
like
import Customers from "./Customers";
router.post('/List_All_Customers', (req, res) => {
Customers.find().lean().exec().then((Data) => {
Promise.resolve(arr.map(item=>{
item.Join_Date = fnToConvertDateToYourFormate(item.Join_Date);
return item;
})).then(Data=>{
res.json({Data: Data});
});
}).catch((err) => {
console.log(err);
});
})
OR
You Can Use Mongoose MapReduce
http://mongoosejs.com/docs/api.html#mapreduce_mapReduce
var o = {};
o.map = function () {
this.Join_Date = fnToConvertDateToYourFormate(this.Join_Date);
emit(this.CustomerId,this)
}
o.reduce = function (k, vals) { }
mongoose.model('Customers').mapReduce(o, function (err, results) {
console.log(results)
});
You can use aggregate query. An example for this query is given below:-
db.customers.aggregate([
{$project:
{yearMonthDayUTC:
{$dateToString:
{format: "%Y-%m-%d",
date: "$date"
}
},
}
}
])
For this query, the date should be in ISO. So, while inserting element into the db, you can use new Date() as this returns the current date as a Date object. The mongo shell wraps the Date object with the ISODate helper.
An example for inserting data in db is given below:-
db.sales.insert({ "_id" : 4, "item" : "mansi", "price" : 10, "quantity" : 2, "date" : new Date(Date.now()) })
You can try mongoDB $dateToString aggregation.
Customers.aggregate([
{
$project: {
"other_field": 1, // and so on as many fields you need
Join_Date: {
{ $dateToString: { format: "%Y-%m-%d", date: "$Join_Date" } }
}
}
}
]).then((Data) => {
res.json({Data: Data});
}).catch((err) => {
console.log(err);
});
Read more about it at: https://docs.mongodb.com/manual/reference/operator/aggregation/dateToString/

mongoose mongodb query find

i'm new in mongo and mongoose.
i want to do a simple query in relational database but i have strong problems to do in mongo
here is me schema:
const GroupSchema = new Schema({
name: { type: String , required:true},
image: { type: String , required:true},
location : {type:String , required: true},
locationCode: {type:String , required:true },
created: {type:Date, default:Date.now() , required:true },
createdBy: {type: Schema.Types.ObjectId , ref:'User' , required:true},
//category: [{type:String,enum:[ config.TYPES ]}],
pendingUsers: [
{ text:String,
user:{type: Schema.Types.ObjectId , ref: 'User'}
}
],
rejectedUsers: [ {type: Schema.Types.ObjectId , ref: 'User'} ],
users: [ {type: Schema.Types.ObjectId , ref: 'User' , required:true } ],
adminUsers:[{type:Schema.Types.ObjectId , ref:'User', required:true}],
events :[Event],
activity:[Activity]
})
and i my controller file i want to do the following query:
let groupId = '123123hgvhgj
let userId = 'asdfsadf3434
Group.find()
.where('_id').equals(groupId)
.where('pendingUsers.user')
.in(userId)
.where('users')
.in(userId)
.where('adminUsers')
.in(userId)
.where('rejectedUsers')
.in(userId)
.exec(function (err, records) {
//make magic happen
console.log(records)
});
i have to get the record WHERE _id match with a group id AND (userId exists in pendingUsers OR userid exists in rejectedUsers OR userid exists in users OR userid exists in adminUsers )
i know that seems to be a simple query but returns empty when should be returned the record i have something wrong in the query?
thanks
Even if mongoose seems to support some simple and + or operations (docs)
it seems to me as if you would still need to mix some pure mongodb query into it.
Considering that, i would go with a pure mongodb style query. This one should fit your needs(untested) or will at least point you in the right direction:
Group.find({
$and: [
{_id: groupId},
{
$or: [
{ pendingUsers.user: { $elemMatch: {userId} } },
{ rejectedUsers: { $elemMatch: {userId} } },
{ users: { $elemMatch: {userId} } },
{ adminUsers: { $elemMatch: {userId} } },
]
}
]
});
Group.find({
$and: [
{ _id: groupId},
{
$or: [
{ "pendingUsers": { $elemMatch: { "user": userId } } },
//{ 'pendingUsers.user' : { $elemMatch: {userId} } },
{ rejectedUsers: { $elemMatch: {userId} } },
{ users: { $elemMatch: {userId} } },
{ adminUsers: { $elemMatch: {userId} } },
]
}
]
})

Categories

Resources