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}.
Related
I have created 2 Users(Admin and Users) and also i have created many ToDos for a User but here my Todo array is empty in my User Schema. Unable to understand why todo task are not assigned to the User Schema.
UserSchema
var userSchema = new Schema({
name: {
type: String,
required: true,
maxlength: 30,
trim: true
},
role: {
type: Number,
default: 0
},
todos: [{
type: Schema.Types.ObjectId,
ref:"Todo"
}]
});
module.exports = mongoose.model("User", userSchema)
Todo Schema
let Todo = new Schema({
todo_heading: {
type: String
},
todo_desc: {
type: String
},
todo_priority: {
type: String
},
todo_completed: {
type: Boolean
},
user: {
type: Schema.Types.ObjectId,
ref:"User"
}
})
module.exports = mongoose.model('Todo', Todo);
here are my routes
User Route
router.get("/user/:userId/todos", isSignedIn, isAuthenticated, getToDos)
Todo Route
router.get("/", getTodos)
router.get("/:id", getUsertodos);
router.post("/user/:userId/add", addUsertodos);
User Controllers
exports.getToDos = (req, res) => {
User.find({ _id: req.params._id })
.populate("todos")
.exec((err, toDo) => {
if (err) {
res.json(err)
}
res.json(toDo)
})
}
ToDo Controllers
exports.addUsertodos = (req, res) => {
let todo = new Todo(req.body)
todo.save((err, todo) => {
if (err) {
return res.status(400).json({
error: "not saved"
})
}
else {
return res.json(todo)
}
})
}
it should work as expected if you add the objectId of newly created todo to the todos property when you create a user.
//routers/todo.js
var express = require('express');
var router = express.Router();
const Todo = require('../models/Todo');
const User = require('../models/User');
/* GET home page. */
router.get('/', async function (req, res) {
let todos = await Todo.find();
res.json({
todos
});
});
router.post('/todos', async function (req, res) {
//add todos
let {
todo_desc,
todo_heading,
todo_priority,
todo_completed
} = req.body;
try {
//NOTE: for simplicity assigning first user but you can grab it from the params
let user = await User.findOne();
let todo = await Todo.create({
todo_desc,
todo_completed,
todo_priority,
todo_heading,
user: user._id
})
res.json({
message: 'todo created successfully',
todo
});
} catch (err) {
return res.status(500).json({
message: 'Unable to create a todo',
err: JSON.stringify(err)
})
}
});
module.exports = router;
Here is the user route where post route get the string id of created ID and converts it to ObjectId(), assign it to the todos.
var express = require('express');
var router = express.Router();
let _ = require('lodash');
var mongoose = require('mongoose');
const User = require('../models/User');
/* GET users listing. */
router.post("/", async function (req, res) {
let {
name,
todos
} = req.body;
try {
let user = new User();
user.name = name;
let objectIds = todos.split(',').map(id => mongoose.Types.ObjectId(id));
user.todos.push(...objectIds)
await user.save()
console.log("user: ", JSON.stringify(user));
if (_.isEmpty(user)) {
res.status(500).json({
message: 'unable to create user'
})
}
res.json(user);
} catch (err) {
res.status(500).json({
message: 'unable to create user',
err: JSON.stringify(err)
})
}
});
router.get("/", async function (req, res) {
try {
let user = await User.find().populate('todos');
console.log("user: ", JSON.stringify(user));
if (_.isEmpty(user)) {
res.status(500).json({
message: 'unable to find user'
})
}
res.json(user);
} catch (err) {
res.status(500).json({
message: 'unable to find user',
err: JSON.stringify(err)
})
}
});
module.exports = router;
Check out the attached screenshot, the user record now contains the todos assigned to it.
If you want checkout the working code, please visit this repo that i created!!.
Hope this help.Cheers!!
When user logs into the application, I am using express-sessions to verify them, I attach a user_id to the sessions object , this I will use when I am creating a post, to refrence the user who created that post, eventually I want to have all those post a user created , inside a collection, such that I can easily show it on users dashboard.
You can recreate the bug by my github: https://github.com/fullstackaccount/auth_cookies-Session,
use the postman information to : create user,login, then try to submit post.
postman Docs: https://documenter.getpostman.com/view/8427997/SWEB1amP?version=latest
I am attempting to use this solution to create this refrence to posts:
https://teamtreehouse.com/community/how-to-assign-a-user-a-post-with-mongoose-and-express
THANK YOU FOR YOUR TIME!
This is the login:
console.log(req.session);
if (!req.session.user) {
/// If the user does not exist , check if they are authenticated by sessions (express sessions makes authorization in headers)
var authHeader = req.headers.authorization;
if (!authHeader) {
var err = new Error('You are not authenticated ...');
res.setHeader('WWW-Authenticate', 'Basic');
err.status = 401;
return next(err);
}
var auth = new Buffer.from(authHeader.split(' ')[1], 'base64').toString().split(':');
var username = auth[0];
var password = auth[1];
User.findOne({ username: username })
.then((user) => {
if (user === null || user.password !== password) {
// Client tried to login and username/password could not be found
var err = new Error('Username or Password could not be found');
err.status = 403; // 403 = forbidden access
next(err);
} else if (user.username === username && user.password === password) {
// double check everything is there, though it should be!
req.session.user = 'authenticated';
req.session.user_id = user._id; // the user.id is being stored in the sessions object alongside with the cookie
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('You are authenticated!');
console.log(`req.session.information ==== ${req.session.information}`);
}
})
.catch((err) => next(err));
} else {
// we passed the block of user not existing (!req.session.user), so they are auth, nothing to see here.. move along!
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('you are already authenticated my love');
}
});
This is the Post route, here the express-sessions is availiable, I attempt to save the user with their ID but I get an error :
throw er; // Unhandled 'error' event
^
MongooseError [CastError]: Cast to ObjectId failed for value "{
title: 'Myposting',
content: 'hi',
author: [ 5df8a29be1e23f2d442e8530 ]
}" at path "posts"
POST ROUTE:
router.post('/', (req, res, next) => {
console.log(`Checking to see if session is passed =================== ${req.session}`); // session object
console.log(`Checking to see if req.session.information is passed =================== ${req.session.user_id}`); // mongoDB id of user
postModel.create(req.body, (error, returnedDocuments) => {
userModel.findById(req.session.user_id, (error, user) => {
if (error) throw new Error(error);
console.log(returnedDocuments);
let myUser = mongoose.Types.ObjectId(req.session.user_id);
// We create an object containing the data from our post request
const newPost = {
title: req.body.title,
content: req.body.content,
// in the author field we add our current user id as a reference
author: [ myUser ] //ObjectID(req.session.user_id)
};
// we create our new post in our database
postModel.create(newPost, (err, post) => {
if (err) {
res.redirect('/');
throw new Error(err);
}
// we insert our newpost in our posts field corresponding to the user we found in our database call
user.posts.push(newPost);
// we save our user with our new data (our new post).
user.save((err) => {
return res.redirect(`/posts/${post.id}`);
});
});
});
});
});
As per request, post and user model :
POST MODEL :
{
title: {
type: String,
default: 'BreakfastQueenROCKS'
},
content: {
type: String,
default: 'Booyeah!'
},
author: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
]
},
{
timestamps: true
}
);
USER MODEL:
{
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
admin: {
type: Boolean,
default: false
},
// we refrence the postModel,
posts: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}
]
},
{
timestamps: true
}
);
I believe the issue has to do with pushing the post onto the User model. What you have is user.posts.push(newPost);, which is the whole post object, but the user model defines posts as:
posts: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}
]
So it seems that you only want to store the post ID on the user, so you can simply change the above mentioned line to user.posts.push(newPost._id);
With the update it should should look like so:
userModel.findById(req.session.user_id, (error, user) => {
if (error) throw new Error(error);
let myUser = mongoose.Types.ObjectId(req.session.user_id);
const newPost = {
title: req.body.title,
content: req.body.content,
author: [ myUser ]
};
postModel.create(newPost, (err, post) => {
if (err) {
res.redirect('/');
throw new Error(err);
}
user.posts.push(newPost._id);
user.save((err) => {
return res.redirect(`/posts/${post._id}`);
});
});
});
Solution fixed in case anyone needs it :
post model
router.post('/', (req, res, next) => {
// We need to find the logged in user, it is passed in the sessions object from userModel when /login is hit
userModel.findById(req.session.user_id, (error, user) => {
// We create an object containing the data from our post request
const newPost = {
title: req.body.title,
content: req.body.content,
// in the author field we add our current user id as a reference
author_id: mongoose.Types.ObjectId(req.session.user_id)
};
// we create our new post in our database
postModel.create(newPost, (error, returnedDocuments) => {
if (error){
res.redirect('/');
throw new Error(error);
}
// we insert our newpost in our posts field corresponding to the user we found in our database call
user.posts.push(returnedDocuments._id);
// we save our user with our new data (our new post).
user.save((err) => {
return res.redirect(`${returnedDocuments._id}`);
});
});
// this will populate the posts field in our userSchema (which contain the id references to our posts)
userModel.findById(req.session.user_id).populate('posts').exec((err, user) => {
console.log('user.posts======',user.posts);
});
});
});
login code:
// User can Login for their account
router.post('/login', (req, res, next) => {
console.log(req.session);
if (!req.session.user) {
/// If the user does not exist , check if they are authenticated by sessions (express sessions makes authorization in headers)
var authHeader = req.headers.authorization;
if (!authHeader) {
var err = new Error('You are not authenticated ...');
res.setHeader('WWW-Authenticate', 'Basic');
err.status = 401;
return next(err);
}
var auth = new Buffer.from(authHeader.split(' ')[1], 'base64').toString().split(':');
var username = auth[0];
var password = auth[1];
User.findOne({ username: username })
.then((user) => {
if (user === null || user.password !== password) {
// Client tried to login and username/password could not be found
var err = new Error('Username or Password could not be found');
err.status = 403; // 403 = forbidden access
next(err);
} else if (user.username === username && user.password === password) {
// double check everything is there, though it should be!
req.session.user = 'authenticated';
req.session.user_id = user._id; // the user.id is being stored in the sessions object alongside with the cookie
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('You are authenticated!');
console.log(`req.session.information ==== ${req.session.information}`);
}
})
.catch((err) => next(err));
} else {
// we passed the block of user not existing (!req.session.user), so they are auth, nothing to see here.. move along!
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('you are already authenticated my love');
}
});
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.
While studying node I am trying to create a little commentthread application. Since the book I am using is a little outdated, I had to adjust the model provided in the book to get the application running. However, I think something is still wrong with my model, because a part of the data is stored as object Object. Does anyone see the problem in the model?
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ReplySchema = new Schema();
ReplySchema.add({
username: String,
subject: String,
timestamp: { type: Date, default: Date.now },
body: String,
replies:[ReplySchema]
}, { _id: true });
var CommentThreadSchema = new Schema({
title: String,
replies:[ReplySchema]
});
mongoose.model('Reply', ReplySchema);
mongoose.model('CommentThread', CommentThreadSchema);
The result in Mongo:
{ _id: 56c8c91b011c7db2608159d6,
'[object Object]timestamp': Sat Feb 20 2016 21:14:19 GMT+0100 (CET), '[object Object]replies': [] }
The controller
var mongoose = require('mongoose'),
CommentThread = mongoose.model('CommentThread'),
Reply = mongoose.model('Reply');
exports.getComment = function(req, res) {
CommentThread.findOne({ _id: req.query.commentId })
.exec(function(err, comment) {
if (!comment){
res.json(404, {msg: 'CommentThread Not Found.'});
} else {
res.json(comment);
}
});
};
exports.addComment = function(req, res) {
CommentThread.findOne({ _id: req.body.rootCommentId })
.exec(function(err, commentThread) {
if (!commentThread){
res.json(404, {msg: 'CommentThread Not Found.'});
} else {
var newComment = Reply(req.body.newComment);
newComment.username = generateRandomUsername();
addComment(req, res, commentThread, commentThread,
req.body.parentCommentId, newComment);
}
});
};
function addComment(req, res, commentThread, currentComment,
parentId, newComment){
if (commentThread.id == parentId){
console.log(newComment);
commentThread.replies.push(newComment);
updateCommentThread(req, res, commentThread);
} else {
for(var i=0; i< currentComment.replies.length; i++){
var c = currentComment.replies[i];
if (c._id == parentId){
c.replies.push(newComment);
var replyThread = commentThread.replies.toObject();
updateCommentThread(req, res, commentThread);
break;
} else {
addComment(req, res, commentThread, c,
parentId, newComment);
}
}
}
};
function updateCommentThread(req, res, commentThread){
CommentThread.update({ _id: commentThread.id },
{$set:{replies:commentThread.replies}})
.exec(function(err, savedComment){
if (err){
res.json(404, {msg: 'Failed to update CommentThread.'});
} else {
res.json({msg: "success"});
}
});
}
function generateRandomUsername(){
//typically the username would come from an authenticated session
var users=['Mojos', 'Milo', 'Mihaal', 'Haly', 'MilodeBaesz', 'Mihaly'];
return users[Math.floor((Math.random()*5))];
}
I received an error when I manually deleted an index from elasticsearch. This happen after manually deleted and I use User.search function in the route. This is the error:
Error: [search_phase_execution_exception] all shards failed
The reason why I manually deleted the index is because mongoosastic has a known issue where, whenever I delete documents from mongodb, elasticsearch still has the documents with it.
Here's the code
models/user.js
var mongoose = require('mongoose');
var mongoosastic = require('mongoosastic');
var Schema = mongoose.Schema;
var UserSchema = new Schema({
private: false,
twitter: String,
tokens: Array,
username: String,
displayName: String,
picture: String,
});
UserSchema.plugin(mongoosastic, {
hosts: [
'localhost:9200'
]});
module.exports = mongoose.model('User', UserSchema);
router.js
User.createMapping(function(err, mapping) {
if (err) {
console.log('error creating mapping (you can safely ignore this)');
console.log(err);
} else {
console.log('mapping created!');
console.log(mapping);
}
});
var stream = User.synchronize();
var count = 0;
stream.on('data', function(err, doc){
count++;
});
stream.on('close', function(){
console.log('indexed ' + count + ' documents!');
});
stream.on('error', function(err){
console.log(err);
});
/* The result for searching for User's */
router.get('/search', function(req, res, next) {
console.log(req.query.q);
if (req.query.q) {
User.search({
query_string:
{ query: req.query.q }
}, function(err, results) {
if (err) return next(err);
console.log(results);
var data = results.hits.hits.map(function(hit) {
return hit;
});
console.log(data);
return res.render('main/search_results', { data: data });
});
}
});