Saving Items In Mongoose For Loop With Schema Methods - node.js

I'm having a problem saving items running through for loop if I attach any extra validation methods. Basically, I'm building an instagram API app that allows editors to remove photos that are unseemly. Photos are pulled from Instagram in batches of 20 and displayed to editors. If an editor clicks a photo, it is first put on a 'blacklist' database by ID, then deleted from the main photo database.
In order to not have blacklisted photos reappear on the feed, before saving an item to the main photo database, it needs to check the Instagram ID of the photo against the blacklist. To do this, I'm using a Schema method.
The problem right now, is that I'm only getting ONE photo saved to the DB. If I take out the method check, then I get all 20.
Here's my main create controller:
exports.create = function(req, res) {
var topic = req.body.topic || 'nyc';
var path = 'https://api.instagram.com/v1/tags/' + topic + '/media/recent?client_id=' + 'XXXXXXXXXX';
request.get({url: path}, function(err, response){
if (err){
console.log('Failed to get data: ', err);
return res.status(400).json({error: 'Not allowed'});
}
else{
// ------------------------------------------------
// Make sure we have JSON
//
var body = JSON.parse(response.body);
// ------------------------------------------------
// Loop through each response
//
for (var i = 0; i < body.data.length; i++ ){
var photoData = body.data[i];
// ------------------------------------------------
// If there is no caption, skip it
//
if (!photoData.caption){
text = '';
}
else{
text = photoData.caption;
}
// ------------------------------------------------
// Create new photo object
//
var photo = new Photo({
link: photoData.link,
username: photoData.user.username,
profilePicture: photoData.user.profile_picture,
imageThumbnail: photoData.images.thumbnail.url,
imageFullsize: photoData.images.standard_resolution.url,
caption: text,
userId: photoData.user.id,
date: photoData.created_time,
_id: photoData.id
});
photo.checkBlacklist(function(err, blacklist){
if (!blacklist){
photo.save(function(err, item){
if (err){
console.log(err);
}
console.log('Saved', item);
})
}
});
// -------------------------------------------------
//
// Save
//
// -------------------------------------------------
} // END FOR LOOP
console.log('Photos saved');
return res.json(201, {msg: 'Photos updated'} );
}
});
};
And here's my Schema for photos:
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var Blacklist = require('../blacklist/blacklist.model');
var PhotoSchema = new Schema({
created: {type: Date, default: Date.now()},
date: String,
link: String,
username: String,
profilePicture: String,
imageThumbnail: {type: String, unique: true},
imageFullsize: String,
caption: String,
userId: String,
_id: {type: String, unique: true}
});
PhotoSchema.methods.checkBlacklist = function(callback){
return Blacklist.findById(this._id, callback);
};
module.exports = mongoose.model('Photo', PhotoSchema);
Strangely, I'm getting console messages for all 20 saves in the create controller:
console.log('Saved', item);
But only ONE photo is actually saved. Any ideas why?
Thanks

When you have to perform the same asynchronous task for items in an array, don't use a regular for loop. Check out async.each, it fits better in your scenario, like (just the else part of your code):
var body = JSON.parse(response.body);
async.each(body.data, function (photoData, callback) {
// ------------------------------------------------
// If there is no caption, skip it
//
if (!photoData.caption){
text = '';
}
else{
text = photoData.caption;
}
// ------------------------------------------------
// Create new photo object
//
var photo = new Photo({
link: photoData.link,
username: photoData.user.username,
profilePicture: photoData.user.profile_picture,
imageThumbnail: photoData.images.thumbnail.url,
imageFullsize: photoData.images.standard_resolution.url,
caption: text,
userId: photoData.user.id,
date: photoData.created_time,
_id: photoData.id
});
photo.checkBlacklist(function(err, blacklist){
if (!blacklist){
photo.save(function(err, item){
if (err){
console.log(err);
}
console.log('Saved', item);
callback();
});
}
});
}, function (error) {
if (error) res.json(500, {error: error});
console.log('Photos saved');
return res.json(201, {msg: 'Photos updated'} );
});
Don't forget to install
npm install async
and require async:
var async = require('async');

How about solving it recursively
saveGames = (depth) => {
if (depth > 0) {
var newGame = new Game({ user: user._id });
newGame.save((err, game) => {
if (err) {
console.log(err);
return res.status(203).json({
title: 'error',
error: { message: 'Error Saving Games' }
});
}
user.games.push(game);
user.save((err, user) => {
if (err) {
console.log(err);
return res.status(203).json({
title: 'error',
error: { message: 'Error Saving Games' }
});
}
saveGames(depth - 1);
});
});
}
}
saveGames(5);

Related

MongoDB - Handle error event

I have a custom validation when I upload an image to mongoDb. The original name should be unique. If it passes the validation, the code runs properly. But if it fails, it produces error. It says that custom validators that take 2 arguments) are deprecated in mongoose >= 4.9.0. Is there another way to validate the uniqueness of the originalname? Or a way to catch the error? Please help.
router.post('/upload',function(req,res){
Item.schema.path('originalname').validate(function(value, done) {
Item.findOne({originalname: value}, function(err, name) {
if (err) return done(false);
if (name) return done(false);
done(true);
});
});
upload(req,res,function(err, file) {
if(err){
throw err;
}
else{
var path = req.file.path;
var originalname = req.file.originalname;
var username = req.body.username;
var newItem = new Item({
username: username,
path: path,
originalname: originalname
});
Item.createItem(newItem, function(err, item){
if(err) throw err;
console.log(item);
});
console.error('saved img to mongo');
req.flash('success_msg', 'File uploaded');
res.redirect('/users/welcome');
}
});
});
model
var ItemSchema = mongoose.Schema({
username: {
type: String,
index: true
},
path: {
type: String
},
originalname: {
type: String
}
});
var Item = module.exports = mongoose.model('Item',ItemSchema);
module.exports.createItem = function(newItem, callback){
newItem.save(callback);
}
you can provide uniqueness to that field like :-
var ItemSchema = mongoose.Schema({
username: {
type: String,
index: true
},
path: {
type: String
},
originalname: {
type: String,
unique:true // this string will be unique all over the database
}
});
var Item = module.exports = mongoose.model('Item',ItemSchema);
module.exports.createItem = function(newItem, callback){
newItem.save(callback);
}
To validate uniqueness before saving to db, you can try to findOne with tour filename:
router.post('/upload',function(req,res){
Item.findOne({originalname: req.file.originalname}, function(err, name) {
if (err) return done(false); // errors
if (name) return done(false); // check for existence of item here
done(true);
});
});
If findOne function did not respond with any data, it means that, there is no document in collection with the same original name, and you can proceed with adding document to collection

Mongoose find not working with ObjectId

I have one schema defined in userref.js
module.exports = (function userref() {
var Schema = mongoose.Schema;
var newSchema= new Schema([{
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
index: true
},
value: { type: Number }
}]);
var results = mongoose.model('UserRef', newSchema);
return results;
})();
I have inserted some data and when I try to fetch some data I am getting proper values from mongodb console
db.getCollection('userrefs').find({'userId':ObjectId('57a48fa57429b91000e224a6')})
It returns properly some data
Now issue is that when I try to fetch some data in code by giving objectId I am getting empty array. In below function userrefs is returned as empty array
//req.params.userId=57a48fa57429b91000e224a6
var UserRef = require('../userref.js');
this.getuserref = function (req, res, next) {
try {
var o_userId =mongoose.Types.ObjectId(req.params.userId);
var query = { userId: o_userId };
var projection = '_id userId value';
UserRef.find(query, projection, function (err, usrrefs) {
if (err) return next(err);
res.send(usrrefs);
console.log("userref fetched Properly");
});
} catch (err) {
console.log('Error While Fetching ' + err);
return next(err);
}
};
Also when I debug code I can see o_userId as objectId with id value as some junk character
o_userId: ObjectID
_bsontype: "ObjectID"
id: "W¤¥t)¹â$¦"
Try this:
try {
var o_userId =mongoose.Types.ObjectId(req.params.userId);
var query = { userId: o_userId };
var projection = '_id $.userId $.value';
UserRef.find(query, projection, function (err, usrrefs) {
if (err) return next(err);
res.send(usrrefs);
console.log("userref fetched Properly");
});
} catch (err) {
console.log('Error While Fetching ' + err);
return next(err);
}
Add the export like this
module.exports.modelname= mongoose.model('userrefs', nameofschema, 'userrefs');
var z = require('../userref.js');
var UserRef = z.modelname;
Now call using UserRef.
Just simply try this man.
Model.find({ 'userId': objectidvariable}, '_id userid etc', function (err, docs) {
// docs is an array
});
Reference sample copied from their official doc.

mongoose save if new refs

If you want to know what's the problem in this thread in two words: I'm trying to accomplish a mongoose function findOrAdd(): if the Id I'm searching for isn't present, I should add a new document. After that (that's why I need some sync functions) I need to do another query based on the new ObjectIds.
This is my Post Schema
var com_post_schema = new Schema({
content: { type: String, required: true },
postedBy: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'com_user'
}]
});
and my User Schema
var com_user_schema = new Schema({
name: { type: String, required: true },
age: {type:Number}
});
So a post can have more than one author.
My problem: an author can be an existent user (chosen in bootstrap-tokenfield) or a new user, see this json example:
{
"content":"post content",
"postedBy":[
{
"_id":"56a60a972b70225014753d1a",
"name":"Paul",
"age":20,
"__v":0,
"value":"Paul",
"label":"Paul"
},
{
"value":"John",
"label":"John"
}
]
}
The user Paul is already present in 'com_user' collection, I have to save the user John in 'com_user' and then save the post with both user ObjectIds refs (the fields 'value' and 'label' are sent by bootstrap-tokenfield).
I'm not clear how can I do that.
EDIT this is my current code I still have sync problems. I made some tests and I see randomly in console "New post added" and then "User not found.."
Try with 3 users and see..
app.post('/api/community/posts', function(req,res){
var arr=[],i=0;
req.body.postedBy.forEach(function(el){
com_user.findById(el._id, function (err, user) {
if(user) {
console.log("User found!");
console.log(user);
arr.push(mongoose.Types.ObjectId(user._id ));
i++;
if(i==req.body.postedBy.length-1) {
console.log('UFi'+i)
console.log(arr)
var com_post1= new com_post({
content:req.body.content,
postedBy:arr,
});
com_post1.save(function(err){
if(!err){
console.log("New post added!");
res.json({"New post added! ":req.body.content});
}
else {
res.json({"Error adding post":'error'});
error(err)
}
});
}
}
else {
var com_user1= new com_user({
name:el.label,
age: 20
});
com_user1.save(function(err,newuser){
if(err)
console.log(err)
else {
console.log('User not found and just added!');
console.log(newuser)
arr.push(mongoose.Types.ObjectId(newuser._id));
console.log(arr)
i++;
if(i==req.body.postedBy.length-1) {
console.log('NUFi'+i)
console.log(arr)
var com_post1= new com_post({
content:req.body.content,
postedBy:arr,
});
com_post1.save(function(err){
if(!err){
console.log("New post added!");
res.json({"New post added! ":req.body.content});
}
else {
res.json({"Error adding post":'error'});
error(err)
}
});
}
}
});
}
});
});
});
its because the code after forEach gets executed before the forEach is completed. Try it like this
app.post('/api/community/posts', function(req,res){
var arr=[],i=0;
req.body.postedBy.forEach(function(el){
com_user.findById(el._id, function (err, user) {
if(user) {
console.log("User found!");
console.log(user);
arr.push(mongoose.Types.ObjectId(user._id ));
i++;
if(i==req.body.postedBy.length-1) { //to ensure forEach is complete
console.log(arr)
var com_post1= new com_post({
content:req.body.content,
postedBy:arr,
});
com_post1.save(function(err){
if(!err)
res.json({"New post added! ":req.body.content});
else {
res.json({"Error adding post":'error'});
error(err)
}
});
}
}
else {
var com_user1= new com_user({
name:el.label,
age: 20
});
com_user1.save(function(err,newuser){
if(err)
console.log(err)
else {
console.log('User not found and just added!');
console.log(newuser)
arr.push(mongoose.Types.ObjectId(newuser._id));
i++;
if(i==req.body.postedBy.length-1) {
console.log(arr)
var com_post1= new com_post({
content:req.body.content,
postedBy:arr,
});
com_post1.save(function(err){
if(!err)
res.json({"New post added! ":req.body.content});
else {
res.json({"Error adding post":'error'});
error(err)
}
});
}
}
});
}
});
});

CRUD on nested schemas using Mongoose

I am trying to set up my nodejs app with a CRUD for mongodb sub-docs using Mongoose but can't figure out how to access the nested object's _id. I can only get the parent ObjectId. I can perform a .push on a new child object but can't perform a simple get, put or delete on an existing child object.
Here is my schema:
//new user model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
// Task schema
var taskSchema = mongoose.Schema({
clientEasyTask : { type: String },
clientHardTask : { type: String },
clientStupidTask : { type: String }
});
var userSchema = new mongoose.Schema({
email: { type: String, unique: true, lowercase: true },
password: String,
task : [taskSchema]
});
module.exports = mongoose.model('Task', taskSchema);
module.exports = mongoose.model('User', userSchema);
Here is my routes:
'use strict';
var isAuthenticated = require('./middleware/auth').isAuthenticated,
isUnauthenticated = require('./middleware/auth').isUnauthenticated;
var User = require('./models/user');
var Task = require('./models/user');
// Create user.task
module.exports = function (app, passport) {
app.post('/api/tasks', isAuthenticated, function (req, res) {
var userEmail = req.body.email;
var easyTask = req.body.easyTask;
User.findOne({ 'email' : userEmail }, function(err, user) {
console.log('found user and defining status and data');
var status;
var data;
if (err) {
status = 'error';
data = 'unknown error occurred';
}
if (user === null) {
status = 'error';
data = 'user not found';
} else {
status = 'ok';
data = user;
}
user.task.push({
clientEasyTask: easyTask
});
user.save();
res.json({
response: {
'status': status
}
});
});
});
// Get one user.task
app.get('/api/tasks/:id', function (req, res) {
return Task.findById(req.params.id, function(err, task) {
if(!task) {
res.statusCode = 404;
return res.send({ error: 'Not found' });
}
if(!err) {
return res.send({ status: 'OK', task:task });
} else {
res.statusCode = 500;
console.log('Internal error(%d): %s', res.statusCode, err.message);
return res.send({ error: 'Server error' });
}
});
});
};
I am using Postman to test everything so there is no fronted code. When I pass the _id of the task (nested in the user) I receive null when I call Get on '/api/tasks/:id'. How can I can get only the specific task?
The mongoose documentation states that you can use parent.children.id(id); but I couldn't get it to work.
The task field of User contains the tasks as embedded subdocs, not references to another collection, so you can't query tasks independent of users (like you're trying to do).
To query for the embedded task subdoc, you can use a query like this:
User.findOne({'task._id': req.params.id})
.select('task.$') // Just include the matching task element
.exec(function(err, user) {
if(!user) {
res.statusCode = 404;
return res.send({ error: 'Not found' });
}
if(!err) {
// The matching task will always be in the first element of the task array
return res.send({ status: 'OK', task: user.task[0] });
} else {
res.statusCode = 500;
console.log('Internal error(%d): %s', res.statusCode, err.message);
return res.send({ error: 'Server error' });
}
}
);
To make this efficient, you'd want to add an index on {'task._id': 1}.

In mongoose how do I push a subdocument onto an array of a saved objects and then return what I just pushed?

I am trying to push a subdocument onto my object whenever. Below is the handler for POST. First it checks whether the account was invited to take the survey. If everything checks out, then we want to push the response onto the survey responses array.
handler: function(req, res, next) {
var survey = req.params.survey;
var account = req.params.account;
Survey.findById(survey).exec(function(err, survey) {
if(err) { return handleError(res, err); }
if(!survey) { return handleError(res, 'Invalid Survey', true); }
if(!account) { return handleError(res, 'Invalid Account', true); }
var invite = _(survey.invites).find(function(i){
return i.account == account;
});
if(!invite) {
return handleError(res, 'No invite exists for user');
}
if(!survey.responses) {
survey.responses = [];
}
var response = survey.responses.push(req.params);
console.log(response); // returns an integer want it to return the subdocument
survey.save(function(err){
if(err) { return handleError(res, err); }
return res.send(response);
});
});
}
My schemas:
var SurveyResponseSchema = new Schema({
account: {type: Schema.Types.ObjectId, ref: 'Account', required: true},
answers: [SurveyAnswerSchema],
complete: Boolean
});
var SurveySchema = new Schema({
start_date: Date,
end_date: Date,
title: String,
survey_questions: [SurveyQuestionSchema],
invites: [SurveyInviteSchema],
responses: [SurveyResponseSchema]
});
push returns the new length of the responses array, so you'd want to do something like this:
var responseIx = survey.responses.push(req.params) - 1;
survey.save(function(err, survey){
if(err) { return handleError(res, err); }
return res.send(survey.responses[responseIx]);
});

Resources