Mongoose populate() returns empty array - node.js

I want to query the subdocument array by the property 'token' in clientSchema. But I'm not able to populate the subdocument array. It always returns empty value.
This is what I'm tried
var performAuthAsync = promise.promisify(performAuth);
var response = {};
performAuthAsync(req).then(function (client) {
sendStatus(res, 200, { "success": "true", "value": client });
}).catch(ApiError, function (e) {
response.error = "true";
response.message = e.message;
if (e.message == "Invalid Authorization" || e.message == "Unauthorized access") {
console.log(e.message);
sendStatus(res, 401, response, req.query.type);
}
else {
sendStatus(res, 500, response, req.query.type);
}
});
PerformAuth method
function performAuth(req, callback) {
try {
var authHeader = req.headers.authorization;
console.log(authHeader);
//error in req format
if (!authHeader || !authHeader.startsWith("Basic ")) {
console.log("inside fail authheader");
return callback(new ApiError("Invalid Authorization"));
}
authHeader = authHeader.replace("Basic ", "");
authHeader = Buffer.from(authHeader, 'base64').toString('ascii');
console.log(authHeader);
//temporary populate check
clientApp.findOne({}).populate({
path: 'appClients',
model: 'TClient'
}).exec(function (error, apps) {
console.log("populated apps check " + apps); //object containing empty array
//{ _id: 5987099f2cb916a0de80f067,
// appSecret: 'THisIsSecret',
// appId: 'W5ikGw16dQjgWm8bGjqdAwi1IDR2XibD3XESYokH',
// appClients: [] }
// mongo console output
// { "_id" : ObjectId ("5987099f2cb916a0de80f067"),
// "appSecret" : "THisIsSecret",
// "appId" : "W5ikGw16dQjgWm8bGjqdAwi1IDR2XibD3XESYokH",
// "appClients" : [ ObjectId("59881a64dbab536016e7f970") ], "__v" : 0 }
});
clientApp.findOne({}).populate('appClients').findOne({
'appClients.token': authHeader
}).exec(function (error, client) {
if (error) {
console.log("inside dberror");
console.error(error);
return callback(error, null);
}
if (!client) {
return callback(new ApiError("Unauthorized access"), null);
}
return callback(client);
});
}
catch (exception) {
console.log("inside exception");
console.error(exception);
return callback(exception, null);
}
}
Clientapp and client schemas: ( they are in different files)
var appSchema = new Schema({
appId: {
type: String,
required: true,
unique: true
},
appSecret: {
type: String,
required: true,
unique: true
},
appClients: [{ type: Schema.Types.ObjectId, ref: 'TClient' }],
createdAt: Date,
modifiedAt: Date
});
// model
var clientApp = mongoose.model('ClientApp', appSchema);
var clientSchema = new Schema({
clientId: {
type: String,
required: true,
unique: true
},
info: {
type: String,
required: true,
},
token: {
type: String,
required: true,
unique: true
},
createdAt: Date,
modifiedAt: Date
});
// model
var tclient = mongoose.model('TClient', clientSchema);
What I'm doing wrong? Any help is appreciated

The findOne() function return only one document. After that the populate method populates the sub document value. But the next findOne() doesn't work, so you get a null response.
You could try this instead:
async function performAuth(){
let authHeader = authHeader.replace("Basic ", "");
authHeader = Buffer.from(authHeader, 'base64').toString('ascii');
const clientApp = await clientApp.find({})
.populate('appClients')
.map(data => data.find(item=>item.appClients.token === authHeader).exec();
}
Operations Performed
Get all the clientApps
Populate the appClients
Find the clientApp whose token matches the authHeader

Related

Mongoose auto-increment fails because of a cast error

I am trying to increment a simple number field, but it is telling me it is failing to to a casting error.
CastError: Cast to Number failed for value "{ '$inc': 1 }" (type Object) at path "times_dealt"
Says it's an object?
This is my schema for Answer
const answerSchema = new mongoose.Schema({
body: {
type: String,
trim: true,
required: true,
},
times_dealt: {
type: Number,
required: true,
},
times_picked: {
type: Number,
required: true,
},
times_won: {
type: Number,
required: true,
},
}, {
timestamps: true,
});
module.exports = { answerSchema };
This is my route for me the admin to add new answers (it's a game so only I can add them, that why the auth. Figured I'll include the complete code.)
router.post("/answers", async(req, res) => {
try {
const isMatch = await bcrypt.compare(
req.body.password,
process.env.ADMIN_PASSWORD
);
if (isMatch) {
const answer = new Answer({
body: req.body.answer.trim(),
times_dealt: 0,
times_picked: 0,
times_won: 0,
});
await answer.save();
res.status(201).send(answer);
}
res.status(401).send();
} catch (e) {
console.log("failed to save", e);
res.status(400).send(e);
}
});
Then whenever a card is dealt, I want to increase the count for times_dealt, and this is when I get the error. This is how I do it:
async function getOneAnswerCard(room) {
if (room.unused_answer_cards.length !== 0) {
// We pick a random answer's ID from our array of unused answers
const randomAnswerID = getRandomElement(room.unused_answer_cards);
// We get that answer's full object from our DB
const newAnswer = await Answer.findById(randomAnswerID);
// const newAnswer = await Answer.findByIdAndUpdate(randomAnswerID, {
// times_dealt: { $inc: 1 },
// });
await Answer.findByIdAndUpdate(randomAnswerID, {
times_dealt: { $inc: 1 },
});
// We remove it from the unused cards array
room.unused_answer_cards = room.unused_answer_cards.filter(
(answerID) => answerID !== randomAnswerID
);
// We add it to the dealt cards array
room.dealt_answer_cards.push(randomAnswerID);
// We serialize the answer (we don't want the user to get info on our answer stats)
const serializedAnswer = { _id: newAnswer._id, body: newAnswer.body };
return serializedAnswer;
}
}
Just getting the answer by itself is no issue. Getting a random ID and fetching an answer object works just fine. It's only when I've added the increment functionality that it started crashing.
I think you're using $inc with a wrong syntax. Try this:
await Answer.findByIdAndUpdate(randomAnswerID, {
{ $inc: { times_dealt: 1 } },
});

Mongoose: Create() doesn't create document

Hello this is the first time I post a question.
So basically i'm build an API to manage car(Im doing this as a training for better building API). My connection to the D is made using a singleton which is called once by server and the mongo promises are global(and it works fine for now)
I have my model:
import mongoose, {Schema} from "mongoose";
import mongooseUniqueValidator from "mongoose-unique-validator";
class Voiture{
initSchema(){
const schema = new Schema({
Marque:{
type: String,
require:true,
},
Modele:{
type: String,
require:true,
},
Année:{
type: Number,
require:true,
},
Energie:{
type: String,
require:true,
},
Boite_De_Vitesse:{
type: String,
require:true,
},
couleur_exterieure:{
type: String,
require:true,
},
couleur_intérieur:{
type: String,
},
nombre_De_Portes:{
type: Number,
},
nombre_De_Places:{
type: Number,
},
Prix:{
type: Number,
},
Etat:{
type: String,
require: true,
},
Kilométrage:{
type: Number,
},
prix_location:{
type: Number,
require:true,
}
},{timestamp: true});
schema.plugin(mongooseUniqueValidator);
mongoose.model("voitures", schema);
}
getInstance() {
this.initSchema();
return mongoose.model("voitures");
}
}
export default Voiture;
and I also have services and controllers attached to them
I can get all the documents in the mongoDB Database but I can't create Documents
Here is my service
import voiture from "../models/Voiture"
import mongoose from "mongoose"
class VoitureService{
constructor(){
this.model = new voiture().getInstance();
this.getAll = this.getAll.bind(this);
this.insert = this.insert.bind(this);
}
/**
* GET All voiture
*/
async getAll(query){
let {skip, limit} = query;
skip = skip ? Number : 0;
limit = limit ? Number : 10;
delete query.skip;
delete query.limit;
if(query._id){
try {
query._id = new mongoose.mongo.ObjetId(uery._id);
} catch (error) {
console.log("not able to generate mongoose id with content", query._id);
}
}
try {
let items = await this.model
.find(query)
.skip(skip)
.limit(limit)
let total = await this.model.countDocuments();
return {
error: false,
statusCode: 200,
data: items,
total
};
} catch (errors) {
return {
error: true,
statusCode: 500,
errors
};
}
}
/**
* Insert a car in the Database
*/
async insert(data){
console.log("line 60");
try {
console.log("line 62");
let item = await this.model.create(data, function (err) {
console.log("line 64");
if (err) return handleError(err);
// saved!
console.log("line 67");
});
if (item){
console.log("line 70");
return {
error: false,
item
};
}
} catch (error) {
console.log("error", error);
return {
error: true,
statusCode: 500,
message: error.message || "Not able to create item",
errors: error.errors
};
}
}
}
And this my controller
async addCar(req, res){
let response = await this.service.insert(req.body)
if (res.error) return res.status(res.statusCode).send(response);
return res.status(201).send(response);
}
I tried to log the Item but it gives me undefined
Thanks to all of you !
Salayna
You can Follow this code
// At Frist, You import **Voiture** Model Then use the model
async insert(data){
console.log("line 60");
try {
console.log("line 62");
let item = await Voiture.create(data, function (err) {
console.log("line 64");
if (err) return handleError(err);
// saved!
console.log("line 67");
});
if (item){
console.log("line 70");
return {
error: false,
item
};
}
} catch (error) {
console.log("error", error);
return {
error: true,
statusCode: 500,
message: error.message || "Not able to create item",
errors: error.errors
};
}
}

Updating DB Shema in Express JS with Mongoose library

I have created a Mongo DB schema with Mongoose in Express.js and I am building the REST API. However when I try to update existing records the values that I do not update from the schema automatically become null. I understand why this happens just not sure exactly how it should be coded.
This is the route:
router.patch("/:projectId", async (req, res) => {
try {
const updatedProject = await Project.updateOne(
{ _id: req.params.projectId },
{
$set: {
title: req.body.title,
project_alias: req.body.project_alias,
description: req.body.description
}
}
);
res.json(updatedProject);
} catch (err) {
res.json({ message: err });
}
});
also here is the schema:
const ProjectsSchema = mongoose.Schema({
title: {
type: String,
required: true,
unique: true
},
project_alias: {
type: String,
unique: true,
required: true
},
description: String,
allowed_hours: Number,
hours_recorded: {
type: Number,
default: 0
},
date_added: {
type: Date,
default: Date.now
}
});
My problem is that when I want to update just the title:
{
"title" : "Title Updated33"
}
description and alias become null. Should I implement a check?
Just use req.body for the update object like this:
router.patch("/:projectId", async (req, res) => {
try {
const updatedProject = await Project.updateOne(
{ _id: req.params.projectId },
req.body
);
res.json(updatedProject);
} catch (err) {
res.json({ message: err });
}
});
Or even better, create a helper function like this so that we can exclude the fields in the body that doesn't exist in the model:
const filterObj = (obj, ...allowedFields) => {
const newObj = {};
Object.keys(obj).forEach(el => {
if (allowedFields.includes(el)) newObj[el] = obj[el];
});
return newObj;
};
router.patch("/:projectId", async (req, res) => {
const filteredBody = filterObj(
req.body,
"title",
"project_alias",
"description",
"allowed_hours",
"hours_recorded"
);
try {
const updatedProject = await Project.updateOne(
{ _id: req.params.projectId },
filteredBody
);
res.json(updatedProject);
} catch (err) {
res.json({ message: err });
}
});

Generate hashed password in findOneAndUpdate

Here is my query for findOneAndUpdate
const { email, password, id } = req.body
Artist.findOneAndUpdate({ _id: id }, { $set: req.body }).then((artist) => {
return res.json({
success: true,
message: "Invitation sent."
});
})
And here is my schema
var artistSchema = new mongoose.Schema({
name: { type: String, default: '' },
password: { type: String, default: '' }
})
artistSchema.pre('findOneAndUpdate', function (next) {
console.log('------------->>>>>> findOneAndUpdate: ');
console.log(this.password) // why undefined?
next();
});
I want to create a hashed password when user update details
const { email, password, id } = req.body;
Artist.findByIdAndUpdate(id, { $set: req.body }).then(artist => {
return res.json({
success: true,
message: "Invitation sent."
});
});
Example with bcrypt
var artistSchema = new mongoose.Schema({
name: { type: String, default: "" },
password: { type: String, default: "" }
});
artistSchema.pre("update", function(next) {
bcrypt.hash(this.password, 10, function(err, hash) {
if (err) return next(err);
this.password = hash;
next();
});
});
let crypto = require('crypto');
let mongoose = require('../mongoose'),
Schema = mongoose.Schema;
Then Schema
let schema = new Schema({
name: {
type: String,
default: ''
},
hashedPassword: {
type: String,
required: true
},
salt: {
type: String,
required: true
}
});
Then methods and virtuals
schema.methods.encryptPassword = function(password){
return crypto.createHmac('sha1', this.salt).update(password).digest('hex');
};
schema.virtual('password').set(function(password){
this._plainPassword = password;
this.salt = Math.random() + '';
this.hashedPassword = this.encryptPassword(password);
}).get(function(){ return this._plainPassword; });
You can check password like that
schema.methods.checkPassword = function(password){
return this.encryptPassword(password) === this.hashedPassword;
};
Export module
module.exports.Artist = mongoose.model('Artist', schema);
Then just save like before
const { email, password, id } = req.body;
Artist.findOneAndUpdate({ _id: id }, { $set: req.body }).then((artist) => {
return res.json({
success: true,
message: "Invitation sent."
});
});
But I sugest you also to use statics. For example:
schema.statics.updateUser = function (data){
// your code
}
And then you can use
Artist.updateUser(req.body).then((res) => {
// response
})
The answer: Writeconsole.log(JSON.stringify(this._update));
My solution for check blank password.
userSchema.pre('findOneAndUpdate', function() {
console.log(JSON.stringify(this._update));
if (this._update.password.length == 0) {
this._update = {
"fullname": this._update.fullname
};
}
else {
this._update = {
"fullname": this._update.fullname,
"password": bcrypt.hashSync(this._update.password, bcrypt.genSaltSync(8), null)
};
}
});

NodeJS, ExpressJS, Mongoose 4.4, Nonce for concurrency and Document.update

I have the following combination, NodeJS, Express, MongoDB, and Mongoose. I have implemented a nonce with mongoose promises to allow for concurrent edits. See the following.:
//schema
var item_schema = {
//_id: {type: Schema.ObjectId, required: true},
name: {type: String, required: true, index: { unique: true }},
code: {type: String, required: true, index: { unique: true }},
date_time_created: {type: Date, required: true},
date_time_updated: {type: Date, required: true},
nonce: {type: Schema.ObjectId}
};
//model
var item_model = mongoose.model('item', schema);
//update
var concurrency_update = function(req, res, retries) {
var promise = model.findById(req.params.id).exec();
var updated_nonce = mongoose.Types.ObjectId();
promise.then(function(document){ //find document response
if(!document) {
res.status = 404;
return Promise.reject( { "message" : req.params.id + ' document does not exist' } );
}
var now = new Date();
if(req.body.code) {
document.code = req.body.code;
document.date_time_updated = now;
}
if(req.body.name) {
document.name = req.body.name;
document.date_time_updated = now;
}
if(!document.nonce) {
document.nonce = updated_nonce;
var old_nonce = document.nonce;
}
else {
var old_nonce = document.nonce;
document.nonce = updated_nonce;
}
return document.update({ "_id" : req.params.id, "nonce" : old_nonce }).exec();
}).then(function(raw){ //update response
var number_affected = raw.n;
console.log(raw);
if(!number_affected && retries < 10){
//we weren't able to update the doc because someone else modified it first, retry
console.log("Unable to update, retrying ", retries);
//retry with a little delay
setTimeout(function(){
concurrency_update(req, res, (retries + 1));
}, 20);
} else if (retries >= 10){
//there is probably something wrong, just return an error
return Promise.reject({ "message" : "Couldn't update document after 10 retries in update"});
} else {
res.json({"message": req.params.id + ' document was update'});
}
}).catch(function(err){
res.send(err.message);
});
The concurrency update is based of of this:
http://www.mattpalmerlee.com/2014/03/22/a-pattern-for-handling-concurrent/
and reading the mongoose docs the update is based off of this.
http://mongoosejs.com/docs/api.html#document_Document-update
However, when the code enters the final .then (//update response) I see the raw.n (numberAffected) = 1 but the database never gets updated?
The answers probably close but I am missing it.
What am I missing on this?
After the comment by #blakes_seven, I was able to remove the use of nonce and apply updates using atomic modifiers. Here is the updated tested code.
//schema
var item_schema = {
//_id: {type: Schema.ObjectId, required: true},
name: {type: String, required: true, index: { unique: true }},
code: {type: String, required: true, index: { unique: true }},
date_time_created: {type: Date, required: true},
date_time_updated: {type: Date, required: true},
nonce: {type: Schema.ObjectId}
};
//model
var item_model = mongoose.model('item', schema);
//update
var concurrency_update = function(req, res, retries) {
var updated_data = {};
if(req.body.code) {
updated_data.code = req.body.code;
}
if(req.body.name) {
updated_data.name = req.body.name;
}
if(!req.body.name.nonce) {
updated_data.nonce = mongoose.Types.ObjectId();
}
if(updated_data !== {}) {
var update = {
$currentDate: {
date_time_updated: true
},
$set: updated_data
};
var promise = model.update({"_id": req.params.id}, update).exec();
promise.then(function (raw) {
var number_affected = raw.nModified;
console.log(raw);
if (!number_affected && retries < 10) {
//we weren't able to update the doc because someone else modified it first, retry
console.log("Unable to update, retrying ", retries);
//retry with a little delay
setTimeout(function () {
concurrency_update(req, res, (retries + 1));
}, 20);
} else if (retries >= 10) {
//there is probably something wrong, just return an error
return Promise.reject({"message": "Couldn't update document after 10 retries in update"});
} else {
res.json({"message": req.params.id + ' document was update'});
}
}).catch(function (err) {
res.send(err.message);
});
}
else {
res.status = 400;
res.send({"message": 'There is nothing specified in the payload to update!'})
}
};

Resources