How do I include a virtual field in API response using Mongoose? - node.js

How do I include a virtual field in a JSON response
const ItemSchema = mongoose.Schema({
name: String,
time: { type: Date, default: Date.now }
});
ItemSchema.virtual('timeleft').get(function() {
this.timeleft = 24
var currentTime = moment();
var timeStored = moment.utc(this.time).local().format();
this.timeleft -= currentTime.diff(timeStored, 'h');
});
API call
app.get('/getAllItems', function(req, res, next) {
Item.find({}, function(err, items) {
res.json(items);
});
});
So technically the response won't include virtual timeleft field. Am I missing something?
[
{
name: "nike",
time: "21/2/22"
},
{
name: "adidas",
time: "21/2/22"
},
]

// use Schema like this
const ItemSchema = new Schema({
name: String,
time: { type: Date, default: Date.now }
}, {
toObject: { virtuals: true },
toJSON: { virtuals: true }
});
ItemSchema.virtual('timeleft').get(function() {
// this.timeleft = 24
var currentTime = moment();
var timeStored = moment.utc(this.time).local().format();
console.log(" ====== 000 ======== ", currentTime.diff(timeStored, 'h'))
return this.timeleft = currentTime.diff(timeStored, 'h');
});
const Item = mongoose.model('Item', ItemSchema);
new Item({
name: 'Axl'
}).save((err, result) => {
console.log("=== err ", err, "=== result ", result)
});
Item.find({}, function(err, items) {
console.log("=========", items)
});

According to Mongoose docs Mongoose virtuals are not stored in MongoDB, which means you can't query based on Mongoose virtuals.
// Will **not** find any results, because `domain` is not stored in
// MongoDB.
const doc = await User.findOne({ domain: 'gmail.com' });
doc; // undefined
If you want to query by a computed property, you should set the property using a custom setter or pre save middleware.

Modify your schema as shown below:
const ItemSchema = mongoose.Schema({
name: String,
time: { type: Date, default: Date.now },
toObject: { virtuals: true }, // <-- These properties will configure
toJSON: { virtuals: true } // model to include virtuals
});
Modify your API call as follows:
app.get('/getAllItems', function(req, res, next) {
Item.find({}, function(err, items) {
res.json(items.toObject()); // <-- use .toObject() or .toJSON()
});
});

Related

find all in mongoose

I have Schema With name score, and that have an iduser: req.session.user._id.
and I have more than one items with same iduser. I wanna to find all the items with the same iduser. and I use the
var users = await storescors16.find({ id }) id = res.session.user._id.
but that show me all the items in score !
My code
//schema
const storescor = new mongoose.Schema({
iduser: String,
level: { type: String, default: null },
day: { type: String, default: null },
date: { type: String, default: null },
time: { type: String, default: null },
objectif: { type: String, default: null }
})
var storescors16 = mongoose.model("storescor", storescor);
//post infos to database
router.post('/control/edite-control', (req, res) => {
console.log(req.session.selectuserid);
var { level, day, date, time, ob } = req.body
var scor = new storescors16({
iduser: req.session.selectuserid,
level: level,
day: day,
date: date,
time: time,
objectif: ob,
})
//read infos from databse
router.get('/result', auth, async(req, res) => {
var id = req.session.user._id
console.log(id);
var user = User.findById(id, (err, docs) => {
if (err)
console.log(err);
else
console.log();
})
var ids = req.session.user._id
var notes = await storescors16.find({ ids })
console.log(notes);
let scor = user.quiz
res.render('./options/result', {
notes: notes,
scor: scor,
title: 'سجل درجات النجم',
name: session.user.name,
email: session.user.email,
})
});
I use nodejs as backend
If you want to find all use findAll function with where.
const users = await storescors16.findAll({ id });
That if I understand you currect.

Mongodb and Mongoose error: E11000 duplicate key error index

So I've been working on a project and I finished most of it, but then this error popped up, saying there is something that is undefined, here is the error:
E11000 duplicate key error index: build-a-voting-app.polls.$votedIp_1 dup key: { : undefined }
Here is my code for my create new mongo schema file (polls.model.js)
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const pollSchema = new Schema({
title: { type: String, unique: true, required: true },
choices: [
{
title: { type: String, required: true },
count: { type: Number, default: 0 }
}
],
votedIp: [{ type: String, unique: true }],
createdAt: {type:Date, default:Date.now()},
createdBy: String
});
const Poll = mongoose.model('polls', pollSchema);
module.exports = Poll;
Here is the function where I add the inputs
function submitVote(field, res, ip) {
Poll.findOneAndUpdate(
{ choices: { $elemMatch: { title: field } } },
{ $inc: { 'choices.$.count': 1 }, $addToSet: { 'votedIp': ip } },
{ new: true },
function (err, poll) {
if (err) throw err;
res.json({ updated: poll });
}
);
}
Here is how I first created it
var newPoll = new Poll({
title: req.body.title,
choices: choicesArr,
createdBy: req.session.user.username || req.session.user
}).save(function (err, poll) {
if (err) throw err
res.redirect('/mypolls')
});
If you want to see the full code please go to https://github.com/ElisaLuo/Freecodecamp-Build-A-Voting-App
I'm using the ip addresses for checking if the user has voted or not (I'm building a voting app), but right now, I cannot even create a new schema / poll. Does anyone know why the error happens and how I can solve it?
#Elisa l - you may want to read this - mongoose enforce unique attribute on subdocument property
However, I did manage to test with mongoose-mock and the behavior is as expected - test results below (please do check the two versions of votedIp in the test code snippets)
and as described in the MongoDb document referenced in the above link. Mongoose does not enforce the unique integrity, MongoDb does.
With the mocha test below (inserted as snippets, not to run the code but just for better readability, please ignore the messy look of the comments in the code but the permutation and combination had to be worked out!), I did manage to create the mongoose schema by adding a create method in "Poll". please note the change in the schema - votedIp: { type: String, unique: true }, you can change it to array in the test code.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var pollSchema = new Schema({
title: { type: String, unique: true, required: true },
choices: [
{
title: { type: String, required: true },
count: { type: Number, default: 0 }
}
],
votedIp: { type: String, unique: true },
createdAt: {type:Date, default:Date.now()},
createdBy: String
});
// Below code added by RJv (ie. me :)
var NewPoll = new mongoose.Schema(pollSchema);
NewPoll.statics.create = function (params, callback) {
var newUpdate = new NewPoll(params);
newUpdate.save(function(err, result) {
callback(err, result);
});
return newUpdate;
};
var Poll = mongoose.model('Model', NewPoll);
module.exports = Poll;
var expect = require('chai').expect,mongooseMock = require('mongoose-mock'),proxyquire=require('proxyquire'),
sinon = require('sinon'), chai=require('chai'),sinonChai = require("sinon-chai");chai.use(sinonChai);
var Poll;
before(function(done){
Poll = proxyquire('./Poll', {'mongoose': mongooseMock});
done();
})
describe('model', function() {
/* beforeEach(function (done) {
Poll = proxyquire('./Poll', {'mongoose': mongooseMock});
done();
});*/
it("should be called once",function(done){
setTimeout(done, 15000);
var callback = sinon.spy();
var poll1 = Poll.create({ "title": 'jv', "choices": [{"title":"jv#gmail.com","count":"1"}],
"votedIp":"9.1.2.1","createdAt":"23/07/2017","createdBy":"Lisa"}, callback);
// Below to pass data for votedIp as an array as described in the original schema by Elisa
//"votedIp":[{"add1":"9.","add2":"1.","add3":"2.","add4":"1"}],"createdAt":"23/07/2017","createdBy":"Lisa"}, callback);
//expect(poll1.votedIp[0].add1+poll1.votedIp[0].add2+poll1.votedIp[0].add3+poll1.votedIp[0].add4).equals("9.1.2.1");
expect(poll1.save).calledOnce;
console.log(JSON.stringify(poll1));
expect(poll1.votedIp).equals("9.1.2.1");
done();
});
it('should expect same ip to get added', function(done) {
this.timeout(5000);
setTimeout(done, 15000);
var callback = sinon.spy();//mock(new Poll({ title: 'jv', choices: [{title:"jv#gmail.com","count":"1"}], votedIp:[{ad1:"9.",add2:"1.",add3:"2.",add4:"1"}],createdAt:"25/07/2017",createdBy:"Lisa"}));
var poll = Poll.create({ "title": 'jv', "choices": [{"title":"jv#gmail.com","count":"1"}],
"votedIp":"9.1.2.1","createdAt":"23/07/2017","createdBy":"Lisa"}, callback);
// var poll = Poll.create({ "title": 'jv', "choices": [{"title":"jv#gmail.com","count":"1"}],
// Below to pass data for votedIp as an array as described in the original schema by Elisa
// "votedIp":[{"add1":"9.","add2":"1.","add3":"2.","add4":"1"}],"createdAt":"25/07/2017","createdBy":"Lisa"}, callback);
// expect(poll.votedIp[0].add1+poll.votedIp[0].add2+poll.votedIp[0].add3+poll.votedIp[0].add4).equals("9.1.2.1");
expect(poll.save).calledOnce;
expect(poll.votedIp).equals("9.1.2.1");
//assert(spy.calledOnce);
done();
});
});
Are you calling submitVote multiple times in quick succession? You might be running into https://jira.mongodb.org/browse/SERVER-14322.
The suggested fix for this is to check the error and if one of the calls fails retry it.
https://docs.mongodb.com/manual/reference/method/db.collection.update/#use-unique-indexes

Retrieve Documents containing a subdocument in a REST API

I am trying to retrieve a list of documents that contain a sub doc to be listed on a web application.
I have my models setup as such:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var storeSchema = new mongoose.Schema({
name: String,
address: String,
phone: String,
webUrl: String,
coords: {type: [Number], index: '2dsphere'}
});
var reviewSchema = new mongoose.Schema({
user: {type: String, required: true},
store: { type: Schema.ObjectId, ref: 'Store' },
review: {type: String},
tags: [String]
});
mongoose.model('Review', reviewSchema);
mongoose.model('Store', storeSchema);
And the api controller setup as such:
var mongoose = require('mongoose');
var Game = mongoose.model('Review');
var sendJsonResponse = function(res, status, content) {
res.status(status);
res.json(content);
};
module.exports.gamesListByDistance = function(req, res) {
var lng = parseFloat(req.query.lng);
var lat = parseFloat(req.query.lat);
var maxDistance = parseFloat(req.query.maxDistance);
var point = {
type: "Point",
coordinates: [lng, lat]
};
var geoOptions = {
spherical: true,
maxDistance: theEarth.getRadsFromDistance(maxDistance),
num: 10
};
Review.geoNear(point, geoOptions, function(err, results, stats) {
console.log('Geo Results', results);
console.log('Geo stats', stats)
if (err) {
console.log('geoNear error:', err);
sendJsonResponse(res, 404, err);
} else {
results.populate(results, {path:'store', select:'name coords'}, function(err,reviews) {
if (err) {
sendJsonResponse(res, 400, err);
} else {
games = buildReviewsList(req, res, results, stats);
sendJsonResponse(res, 200, reviews);
}
});
}
});
};
var buildReviewsList = function(req, res, results, stats) {
var reviews = [];
results.forEach(function(doc) {
reviews.push({
distance: theEarth.getDistanceFromRads(doc.dis),
store: doc.obj.store.name,
status: doc.obj.status,
tags: doc.obj.tags,
_id: doc.obj._id
});
});
return reviews;
};
But am getting:
TypeError: undefined is not a function
What is the proper way to populate these subdocs and return the list of reviews to be consumed with a Web Application?
Why do you have :
var Game ?
should be var Review.

How to save userId in mongoose hook?

Given yon schema, how do I save userId to createdBy and updatedBy?
This seems like it should be an easy use case. How do I do it?
I'm not sure how to get userId from req.user.id to the model before being written.
// graph.model.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var schema = new Schema({
title: String,
createdAt: Date,
createdBy: String,
updatedAt: Date,
updatedBy: String,
});
// This could be anything
schema.pre('save', function (next) {
- if (!this.createdAt) {
this.createdAt = this.updatedAt = new Date;
this.createdBy = this.updatedBy = userId;
} else if (this.isModified()) {
this.updatedAt = new Date;
this.updatedBy = userId;
}
next();
});
Here's the controller code if you're interested:
var Graph = require('./graph.model');
// Creates a new Graph in the DB.
exports.create = function(req, res) {
Graph.create(req.body, function(err, thing) {
if(err) { return handleError(res, err); }
return res.status(201).json(thing);
});
};
// Updates an existing thing in the DB.
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Graph.findById(req.params.id, function (err, thing) {
if (err) { return handleError(res, err); }
if(!thing) { return res.send(404); }
var updated = _.merge(thing, req.body);
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(thing);
});
});
};
The following is just another way of saving userId.
Sample model with createdBy, updatedBy, createdAt, updatedAt fields:
import mongoose from 'mongoose';
const SupplierSchema = new mongoose.Schema(
{
name: {
type: String,
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
updatedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
},
{
timestamps: {
createdAt: true,
updatedAt: true,
},
},
);
export default mongoose.model('Supplier', SupplierSchema);
Note that in mongoose starting from version ^4.13.17 you can simply specify timestamps createdAt, updatedAt directly in the schema.
https://mongoosejs.com/docs/4.x/docs/guide.html#timestamps
Then in supplier controller assign req.user._id to the fields createdBy, updatedBy:
import mongoose from 'mongoose';
import { Supplier } from '../models';
exports.create = async (req, res) => {
const supplierToCreate = new Supplier({
_id: new mongoose.Types.ObjectId(),
name: req.body.name,
createdBy: req.user._id,
updatedBy: req.user._id,
});
return supplierToCreate
.save()
.then(() =>
res.status(201).json({
message: 'New supplier is created successfully.',
}),
)
.catch(errSaving => res.status(500).json({ error: errSaving }));
};
You can't access req object inside of mongoose hook.
I think, you should define virtual field with a smart setter instead:
schema.virtual('modifiedBy').set(function (userId) {
if (this.isNew()) {
this.createdAt = this.updatedAt = new Date;
this.createdBy = this.updatedBy = userId;
} else {
this.updatedAt = new Date;
this.updatedBy = userId;
}
});
Now all you have to do is to set modifiedBy field with correct userId value in your controller:
var updated = _.merge(thing, req.body, {
modifiedBy: req.user.id
});

Mongoose .pre('save') does not trigger

I have the following model for mongoose.model('quotes'):
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var quotesSchema = new Schema({
created: { type: String, default: moment().format() },
type: { type: Number, default: 0 },
number: { type: Number, required: true },
title: { type: String, required: true, trim: true},
background: { type: String, required: true },
points: { type: Number, default: 1 },
status: { type: Number, default: 0 },
owner: { type: String, default: "anon" }
});
var settingsSchema = new Schema({
nextQuoteNumber: { type: Number, default: 1 }
});
// Save Setting Model earlier to use it below
mongoose.model('settings', settingsSchema);
var Setting = mongoose.model('settings');
quotesSchema.pre('save', true, function(next) {
Setting.findByIdAndUpdate(currentSettingsId, { $inc: { nextQuoteNumber: 1 } }, function (err, settings) {
if (err) { console.log(err) };
this.number = settings.nextQuoteNumber - 1; // substract 1 because I need the 'current' sequence number, not the next
next();
});
});
mongoose.model('quotes', quotesSchema);
There is an additional Schema for mongoose.model('settings') to store an incrementing number for the incrementing unique index Quote.number im trying to establish. Before each save, quotesSchema.pre('save') is called to read, increase and pass the nextQuoteNumber as this.number to the respectively next() function.
However, this entire .pre('save') function does not seem to trigger when saving a Quote elsewhere. Mongoose aborts the save since number is required but not defined and no console.log() i write into the function ever outputs anything.
Use pre('validate') instead of pre('save') to set the value for the required field. Mongoose validates documents before saving, therefore your save middleware won't be called if there are validation errors. Switching the middleware from save to validate will make your function set the number field before it is validated.
quotesSchema.pre('validate', true, function(next) {
Setting.findByIdAndUpdate(currentSettingsId, { $inc: { nextQuoteNumber: 1 } }, function (err, settings) {
if (err) { console.log(err) };
this.number = settings.nextQuoteNumber - 1; // substract 1 because I need the 'current' sequence number, not the next
next();
});
});
For people who are redirected here by Google, make sure you are calling mongoose.model() AFTER methods and hooks declaration.
In some cases we can use
UserSchema.pre<User>(/^(updateOne|save|findOneAndUpdate)/, function (next) {
But i'm using "this", inside the function to get data, and not works with findOneAndUpdate trigger
I needed to use
async update (id: string, doc: Partial<UserProps>): Promise<User | null> {
const result = await this.userModel.findById(id)
Object.assign(result, doc)
await result?.save()
return result
}
Instead of
async update (id: string, doc: Partial<UserProps>): Promise<User | null> {
const result = await this.userModel.findByIdAndUpdate(id, doc, { new: true, useFindAndModify: false })
return result
}
The short solution is use findOne and save
const user = await User.findOne({ email: email });
user.password = "my new passord";
await user.save();
I ran into a situation where pre('validate') was not helping, hence I used pre('save'). I read that some of the operations are executed directly on the database and hence mongoose middleware will not be called. I changed my route endpoint which will trigger .pre('save'). I took Lodash to parse through the body and update only the field that is passed to the server.
router.post("/", async function(req, res, next){
try{
const body = req.body;
const doc = await MyModel.findById(body._id);
_.forEach(body, function(value, key) {
doc[key] = value;
});
doc.save().then( doc => {
res.status(200);
res.send(doc);
res.end();
});
}catch (err) {
res.status(500);
res.send({error: err.message});
res.end();
}
});

Resources