Mongoose - can't call model.find() in post findOneAndUpdate hook - javascript

Summary
In Mongoose have a post findOneAndUpdate hook and inside of this hook I need to query the database. I'm trying to do a .find() on another model, however each time I do, I get the following error:
Error
TypeError: Users.find is not a function
at model.Query.<anonymous> (/Users/benjilightstone/Coding/eSports-node-RESTful-API/api/models/fortniteUsers.js:29:32)
I have tried running the exact same .find() in one of my express routes and it works perfectly. I have checked the require() for Users and it's imported correctly. I have no idea why I'm getting this error and would love some help!
Code
fortniteUsers.js (Model with post hook)
const mongoose = require("mongoose");
const Users = require("./users");
const uniqueValidator = require("mongoose-unique-validator");
const statsFieldsSchema = require("./statsFields");
const fnUserPlatformSchema = {
//this is so that have a unique entry for a username &platform combo
fnUser: { type: String, require: true },
platform: { type: String, enum: ["pc", "xb1", "psn"], require: true }
};
//TODO took out updated at because the record shouldn't get updated?
//TODO think about usecase of Fortnite user changing their username
const fortniteUserSchema = mongoose.Schema({
// pass a javascript object that defines the schema
fnUserPlatform: { type: fnUserPlatformSchema, unique: true, require: true },
createdAt: { type: Date, require: true },
hasAccount: { type: Boolean, require: true, default: false },
updatedAt: { type: Date, require: false },
lifeTimeStats: { type: statsFieldsSchema, require: true }
});
fortniteUserSchema.post("findOneAndUpdate", async function(fortniteUser, next) {
try {
console.log("inside fortniteUser post hook", typeof fortniteUser._id);
const fnUserId = String(fortniteUser._id);
console.log("fnUserId", fnUserId);
// const userId = await User.find({ fnUser: fnUserId });
const userId = await Users.find({ fnUser: "5ccb08198f52f40117e950b3" });
console.log("userId", userId);
} catch (err) {
console.log("error in post hook", err);
}
});
fortniteUserSchema.plugin(uniqueValidator);
module.exports = mongoose.model("FortniteUser", fortniteUserSchema);
users.js (User model imported in fortniteUsers.js)
const mongoose = require("mongoose");
const FortniteUsers = require("./fortniteUsers");
require("mongoose-type-email");
const userSchema = mongoose.Schema({
// pass a javascript object that defines the schema
fnUser: {
type: mongoose.Schema.Types.ObjectId,
ref: "FortniteUser",
require: true
},
eSportsUsername: { type: String, require: true },
password: { type: String, require: true, minlength: 5 },
email: { type: mongoose.SchemaTypes.Email, require: true, unique: true },
ageConfirmed: { type: Boolean, require: true },
createdAt: { type: Date, require: true },
updatedAt: { type: Date, require: false }
});
userSchema.pre("save", function() {
console.log("inside pre save users.js");
console.log("docToSave", this); // QUESTION: how would I pass in req.body.fnUser
FortniteUsers.findOne({
fnUserPlatform: { username: req.body.fnUser, platform: "psn" }
})
.then(result => {
console.log("result in pre", result);
})
.catch(err => console.log("error i expect", err));
});
userSchema.post("save", function(user) {
//TODO where is user gettting passed from, I dont really understand this
console.log("we're in post save ... ", user);
FortniteUsers.findOneAndUpdate(
{ _id: user.fnUser },
{ $set: { hasAccount: true } },
function(err, doc) {
if (err) {
console.log("error in mongoose userSchema.post", err);
}
}
);
});
module.exports = mongoose.model("User", userSchema);

Look like the problem is because you require fortniteUsers and users in each other so one of them is not yet exported when call inside the other.
To verify this, you can console.log(FortniteUsers) and console.log(Users) after require them. You can see that FortniteUsers is Model {FortniteUsers} but Users is just {}.
You can solve it by moving this line const FortniteUsers = require("./fortniteUsers"); to be after the exports in users.js (not sure this is the good way but it works).

Related

Mongoose v6.2.7 new Model.save() method not working tried promise, callback and async await in try-catch nothing works

Initially, the project was set up with promise support, and all queries used promise like method.then().catch() later some were converted to try-catch with async await. All worked fine until a few weeks ago when all of a sudden some methods stopped working, I have tried converting the methods to many different variations from promise to callback and to try-catch. await new Model(object).save() does not save the record. I am using mongoose.createConnection because I need to connect to two databases.
Here is how I init my DB
const mongoose = require("mongoose");
mongoose.Promise = require('bluebird');
function makeNewConnection(uri, id) {
const db = mongoose.createConnection(uri);
db.on("error", function(error) {
console.log(
`MongoDB :: connection ${this.name} :: ${id} ${JSON.stringify(error)}`
);
db.close().catch(() =>
console.log(`MongoDB :: failed to close connection ${this.name}`)
);
});
db.on("connected", async function() {
mongoose.set("debug", function(col, method, query, doc) {
console.log(
`MongoDB :: ${
this.conn.name
} :: ${id} ${col}.${method}(${JSON.stringify(query)},${JSON.stringify(
doc
)})`
);
});
console.log(`MongoDB :: connected ${this.name} :: ${id}`);
require("../models/notification.model");
if (process.env.DATABASE_ENV === "local" && id === "cloud") {
require("../helpers/data.sync.helper");
}
});
db.on("disconnected", function() {
console.log(`MongoDB :: disconnected ${this.name} :: ${id}`);
});
return db;
}
// Use
let local, cloud;
if (process.env?.DATABASE_ENV === "local") {
// Connect to local database
local = makeNewConnection(
`mongodb://${process.env.DATABASE_USER}:${process.env.DATABASE_PASS}#127.0.0.1:27017/Eyemasters?retryWrites=true&authSource=admin&useNewUrlParser=true&useUnifiedTopology=true&w=majority`,
"local"
);
// Connect to cloud database
cloud = makeNewConnection(
`mongodb://${process.env.DATABASE_USER}:${process.env.DATABASE_PASS}#64.227.44.132:27017/Eyemasters?retryWrites=true&w=majority`,
"cloud"
);
// Start Database sync helper
} else {
// Connect to cloud local database
local = makeNewConnection(
`mongodb://${process.env.DATABASE_USER}:${process.env.DATABASE_PASS}#localhost:27017/Eyemasters?retryWrites=true&w=majority`,
"local"
);
}
module.exports = {
local,
cloud
};
And here is one of my models having the issue.
const mongoose = require("mongoose");
mongoose.Promise = require('bluebird');
const { local, cloud } = require("../config/database.config");
const { genId } = require("../helpers/doc.id.generator");
const validator = require("validator");
const UserSchema = mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
email: {
type: String,
required: true,
unique: true,
validate: {
validator: validator.isEmail,
message: "{VALUE} is not a valid email",
isAsync: false
}
},
hash: { type: String, bcrypt: true, rounds: 10 },
firstname: { type: String, required: true },
lastname: { type: String, required: true },
phone: { type: String },
dateOfBirth: { type: Date },
designation: { type: String },
role: { type: mongoose.Schema.Types.ObjectId, ref: "Role" },
passport: { type: String },
accountDetails: {
name: String,
number: Number,
bank: String
},
defaultBranch: {
type: mongoose.Schema.Types.ObjectId,
ref: "Branch"
},
branches: [{ type: mongoose.Schema.Types.ObjectId, ref: "Branch" }],
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
lastModifiedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
webpush: { type: Object },
inactive: { type: Boolean, default: true },
approved: { type: Boolean, default: false },
activationCode: { type: String, unique: true },
activationExpiresIn: { type: Date }
},
{ toJSON: { virtuals: true }, timestamps: true }
);
UserSchema.plugin(require("mongoose-bcrypt"));
genId(UserSchema);
UserSchema.pre("save", function(next) {
if (!this.createdBy) this.createdBy = this._id;
if (!this.lastModifiedBy) this.lastModifiedBy = this._id;
});
exports.User = exports.User || local.model("User", UserSchema);
exports.OnlineUser = exports.OnlineUser || cloud.model("User", UserSchema);
And Lastly my controller setup;
exports.create = async (req, res) => {
// Validating entered data
if (
!req.body.firstname ||
!req.body.lastname ||
req.body.firstname.length < 3 ||
req.body.lastname.length < 3 ||
!req.body.email ||
!req.body.role ||
req.body.email.length < 3
) {
return res.status(400).send({
message: "Please fill in all required fields"
});
}
try {
const user = await User.findOne({
email: req.body.email.toLowerCase()
});
if (user) {
throw new Error("User with email " + req.body.email + " already exist");
}
console.log("Before create");
let newUser = new User({
...req.body,
activationCode: randtoken.uid(16),
activationExpiresIn: moment.utc().add(30, "minutes"),
email: req.body.email.toLowerCase()
});
console.log(newUser.save);
const userData = await newUser.save();
console.log("Saved");
let transaction = new DbTransaction({
transactionType: "insert",
modelName: "User",
data: userData,
clients: [process.env.DATABASE_CLIENT_ID],
isProcessed: false
});
await transaction
.save()
.then(d => console.log("Transaction updated successfully"))
await User.populate(userData, populateQuery, (err, data) => {
if (err) throw new Error(err);
return res
.status(201)
.send({ message: "User created successfully", user: data });
});
} catch (err) {
console.log(err);
console.log(err.kind);
return res.status(500).send({
message: err.message
});
}
};
I have tried different variants of javascript promise based work flow. Like Model.method().then().catch(), async try-await Model.method()-catch and lastly callback Model.method((err, data)=>{ //do something }).
None of the above conbination has worked. My observation is that mongoose just logs "done" into the console for this method but never action is never actually performed.
Your help is greatly appreciated, I have absolutely no idea why this is not working.
Thank you.
To all who made effort to assist, Thank you for the help.
I don't know why I am seeing the problem after posting here.
The issue was coming from not calling next in the middleware inside the model setup;
UserSchema.pre("save", function(next) {
if (!this.createdBy) this.createdBy = this._id;
if (!this.lastModifiedBy) this.lastModifiedBy = this._id;
});
Replace with;
UserSchema.pre("save", function(next) {
if (!this.createdBy) this.createdBy = this._id;
if (!this.lastModifiedBy) this.lastModifiedBy = this._id;
next();
});
Thank you all once again for your support.

Multiple async queries in nodejs (mongoose)

I am a nodejs newbie. I have two simple models, User and Story. Here is what I want to do:
I want to retrieve all stories that have {status:"public"} and store it in an array called retrievedStories.
Then for each story I want to use its "user" field (which contains the object id of the user) to lookup the name of the user from User
Then add a new key in each element of retrievedStories called authorName with the name of the user.
Here are the models:
const UserSchema = new mongoose.Schema({
googleId: {
type: String,
required: true
},
displayName: {
type: String,
required: true
},
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
image: {
type: String,
},
createdAt: {
type:Date,
default: Date.now()
}
})
const StorySchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true
},
body: {
type: String,
required: true
},
status: {
type: String,
default: 'public',
enum: ['public', 'private']
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
createdAt: {
type:Date,
default: Date.now()
}
})
And here is what I tried, but doesn't work. The stories are retrieved but the authorName is not added. Any help (possibly a better way to do this?) will be highly appreciated!
router.get('/',async (req,res)=>{
try {
const retrievedStories = await Story.find(
{status: "public"}
)
await Promise.all(retrievedStories.map(async (story) =>{
const author = await User.findById(story.user)
story.authorName = author.displayName
}))
return res.json(retrievedStories)
} catch (error) {
console.log(error)
}
})
You can simplify your query by using populate to retrieve User's data:
router.get('/', async (req, res) => {
try {
const retrievedStories = await Story.find({ status: 'public' })
.populate('user')
.exec();
return res.json(retrievedStories);
} catch (error) {
console.log(error);
}
});
You can then access User's displayName data on each Story by accessing story.user.displayName.
For more information on query population see the official docs.

CastError: Cast to [undefined] failed for value "[]" (type string) at path "comments.undefined"

I'm quite new to node and mongoose. I'm trying to do a project using them, but i'm running into an error while trying to populate. The comment is saved to the Comment schema perfectly, but throws an error when i reference it Organization Schema.Please advise me on what i'm doing wrong. Any form of assistance will be appreciated.
// Post route for comment(on the Organization's profile page)
router.post('/comment/:id', ensureAuthenticated,(req, res) =>{
let id = req.params.id;
console.log(mongoose.Types.ObjectId.isValid(id))
const commentObject = new Comment({
sender: 'Fred kimani',
commentBody: req.body.commentBody
})
console.log(commentObject);
commentObject.save((err, result) =>{
if(err){console.log(err)}
else{
Organization.findByIdAndUpdate(id, {$push: {comments: result}}, {upsert: true}, (err, organization) =>{
if(err){console.log(err)}
else{
console.log('======Comments====')
}
})
res.redirect('/users/organizationprofilepage/:id')
}
})
});
//Organization Schema
const mongoose = require('mongoose');
const OrganizationSchema = new mongoose.Schema({
organization_name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
},
category: {
type: String,
required: true
},
isApproved: {
type: Boolean,
default: false
},
image:{
type:String,
required:true
},
description: {
type: String,
required: true,
},
comments: [{
type: mongoose.Types.ObjectId,
ref: 'Comment'
}],
},
//{ typeKey: '$type' }
);
OrganizationSchema.statics.getOrganizations = async function () {
try {
const organizations = await this.find();
return organizations;
} catch (error) {
throw error;
}
}
//defines the layout of the db schema
const Organization = mongoose.model('0rganization', OrganizationSchema);
module.exports = Organization;
//Comment schema
const mongoose = require('mongoose');
const CommentSchema = mongoose.Schema({
sender: {
type: String,
},
commentBody: {
type: String,
required: false,
},
date: {
type: Date,
default: Date.now
},
})
CommentSchema.statics.getComments= async function () {
try {
const comments = await this.find();
return comments ;
} catch (error) {
throw error;
}
}
const Comment= mongoose.model('Comment', CommentSchema);
module.exports = Comment;
Try to change the comments type to mongoose.Schema.Types.ObjectId:
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment',
},
],
Try to push the new commend _id into the Organization object after its creation, not the whole object:
commentObject.save((err, result) => {
if (err) {
console.log(err);
} else {
Organization.findByIdAndUpdate(
id,
{ $push: { comments: result._id } }, // <- Change this line
{ upsert: true },
(err, organization) => { }
);
...
}
});
If you just updated the schema you will need to make sure all of the comments are following the new form you created, when you save it will attempt to validate them, that is why an updateOne will work but not await save()

findOneandReplace keeps giving error: "Error: The replacement document must not contain atomic operators."?

I am currently developing a Pokemon Team Builder app with a React frontend and an Express backend with MongoDB for the database.
As far as I can tell my TeamSchema has no such atomic operators? Here is my TeamSchema:
const mongoose = require('mongoose');
const TeamSchema = new mongoose.Schema({
name: {
type: 'String',
required: true,
unique: true,
},
team: [
{
name: { type: String },
types: [{ type: String }],
sprite: { type: String },
},
],
username: {
type: String,
required: true,
},
userId: {
type: String,
required: true,
},
});
const TeamModel = mongoose.model('Team', TeamSchema);
module.exports = TeamModel;
And the error gets thrown in this method when I attempt to call the findOneAndReplace method by finding a team that has a name and userId match.
const replaceTeam = async (req, res) => {
const { teamName: name, filteredTeam: team } = req.body;
const { username, _id: userId } = req.user;
const newTeam = new Team({ name, team, username, userId });
try {
const replacedTeam = await Team.findOneAndReplace({ name, userId }, newTeam);
console.log(replacedTeam);
res.status(200).json({ message: 'Team was successfully overwritten!' });
} catch (err) {
console.log(err);
res.status(500).json({ message: 'An error occurred while updating the team.' });
}
};
This has been a real headscratcher here and I am not sure what is going wrong here. I have only started using mongoose a couple of weeks ago, so I wonder if it's something fundamental I am misunderstanding here.
The Mongoose function findOneAndReplace expects a document object passed in. See the below code.
details.findOneAndReplace(
{ location: "New York" },
{ name: "Sunny", age: 292, location: "Detroit" },
function(err, result) {
if (err) {
res.send(err);
} else {
res.send(result);
}
}
);
Change
const newTeam = new Team({ name, team, username, userId })
to
const newTeam = {name, team, username, userId}
Also as in the other poster's code, add the new: true option to the call as follows by changing
const replacedTeam = await Team.findOneAndReplace({ name, userId }, newTeam);
to
const replacedTeam = await Team.findOneAndReplace({ name, userId }, newTeam, { new: true });
otherwise the original document will be returned into replacedTeam
You can just use findOneAndUpdate and update all the fields with new data. You can do it like this:
const replaceTeam = async (req, res) => {
const { teamName: name, filteredTeam: team } = req.body;
const { username, _id: userId } = req.user;
try {
const replacedTeam = await Team.findOneAndUpdate({ name, userId }, { name, team, username, userId }, {new: true});
console.log(replacedTeam);
res.status(200).json({ message: 'Team was successfully overwritten!' });
} catch (err) {
console.log(err);
res.status(500).json({ message: 'An error occurred while updating the team.' });
}
};

Mongoose findById callback returns null despite existence of record

I'm confused with this. The err is valid if the record exists. Oppositely, the err is invalid if the record doesn't exist.
However, the article is always null, although the record is existent in database.
router.get("/:id", function (req, res) {
Article.findById(req.params.id, function(err, article) {
if (err) {
res.send(`Error: ${err}`);
} else {
//Print out "null"
console.log(article);
//Error occur
Article.updateOne({_id: req.params.id}, {$set:{views: article.views + 1}});
res.render("showArticle", { article: article});
}
});
});
Article model:
var mongoose = require("mongoose");
const articleSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: false
},
content: {
type: String,
required: true
},
createdDate: {
type: Date,
default: Date.now
},
views: {
type: Number,
default: 0
}
});
module.exports = mongoose.model("Article", articleSchema);
After searching for hours, OK... it's silly of me.
I haven't given the third parameter in mongoose.model.
Change:
module.exports = mongoose.model("Article", articleSchema);
To
module.exports = mongoose.model("Article", articleSchema, "Article");
I found that mongoose.prototype.model will automatically add a "s" at the end of the model name if there isn't collection name, which means, the Article collection will be named to Articles

Categories