I've been on this problem for quite a while. I have 2 schemas:
**The User Schema**
const mongoose = require('mongoose');
const PaymentSchema = require('../models/payments').schema;
const UserSchema = new mongoose.Schema({
firstname :{
type : String,
required : true
} ,
lastname :{
type : String,
required : true
} ,
email :{
type : String,
required : true
} ,
country :{
type : String,
required : true
} ,
state :{
type : String,
required : true
} ,
city :{
type : String,
required : true
} ,
postal :{
type : String,
required : true
} ,
phone :{
type : String,
required : true
} ,
plan :{
type : String,
} ,
amount :{
type : String,
} ,
profit:{
type: String,
},
amountDue:{
type: String,
},
password :{
type : String,
required : true
} ,
withdrawals :[PaymentSchema],
payDate :{
type : Date,
default : Date.now
},
date :{
type : Date,
default : Date.now
}
});
const User= mongoose.model('User', UserSchema);
module.exports = User;
The payments Schema
const mongoose = require('mongoose');
const PaymentSchema = new mongoose.Schema({
firstname: {
type: String
},
lastname:{
type: String
},
balance:{
type: String
},
address: {
type: String,
required: true
},
amount: {
type: Number,
required: true
},
paid: {
type: Boolean,
required: true
},
userid:{
type: String
},
date: {
type: Date,
default: Date.now
}
});
const Payment = mongoose.model('Payment', PaymentSchema);
module.exports = Payment;
The logic is this: When a user makes payment, a POST request is sent and the information from the payment request is displayed on an admin dashboard. The POST request looks like this:
router.post('/users/:id/payments', function (req, res) {
User.findById(req.params.id, function (err, fuser) {
if (err) {
req.flash('error_msg', 'Something went wrong. Please login and try again')
res.redirect('/logout')
} else {
var pay = new Payment({
firstname: fuser.firstname,
lastname: fuser.lastname,
balance: fuser.amount,
amount: req.body.usd,
address: req.body.to,
paid: false,
userid: fuser.id
})
pay.save(function (err, pays) {
if (err) {
console.log(err)
} else {
User.findById(req.params.id, function (err, userr) {
userr.withdrawals.push(pays)
userr.save(function (err, user) {
if (err) {
req.flash('error_msg', 'Something went wrong. Please login and try again')
res.redirect('/logout')
} else {
res.redirect('/dashboard')
}
})
})
}
})
}
})
The information sent to the admin dashboard is then approved and the admin makes a PUT request to that particular payment, updating the "paid" property to "true". Like so:
router.put('/admin/withdrawals/:id', function (req, res) {
var update = {
paid: true
}
Payment.findByIdAndUpdate(req.params.id, update, function (err, user) {
if (err) {
res.send("error!")
} else {
User.findById(user.userid, function (err, uza) {
if (err) {
res.send("error!")
} else {
var amount = uza.amount
var deduct = req.body.aamount
var balance = parseInt(amount) - parseInt(deduct)
var updated = {
amount: balance,
}
User.findByIdAndUpdate(user.userid, updated, function (err, pays) {
if (err) {
res.send(err)
} else {
req.flash('success_msg', 'Withdrawal has been confirmed!')
res.redirect('/admin/dashboard')
}
})
}
})
}
})
})
The payment document gets updated to "true" but the embedded payment document on the User Schema still remains "false".
Please what do I need to do to update the paid property from "false" to "true" on both the main payment document and on the embedded payment document?
You need to link between the schemas like so:
withdrawals: [{ type: Schema.Types.ObjectId, ref: 'Payment' }],
Otherwise you have to update the data in both schemas manually!
See the docs here: https://mongoosejs.com/docs/api.html#schematype_SchemaType-ref
Related
I have a model that looks like this
const employeeSchema = new mongoose.Schema({
empFirstName:{
type: String,
reuired: true
},
empLastName:{
type: String,
reuired: true
},
empEmail:{
type: String,
reuired: true
},
empPassword:{
type: String,
reuired: true
},
empConfirmPass:{
type: String,
reuired: true
},
empContactNum:{
type: Number,
reuired: true
},
empPosition:{
type: String,
reuired: true
},
empTeam:{
type: String,
reuired: true
}
});
const Employee = mongoose.model('EMPLOYEE', employeeSchema);
module.exports = Employee;
I'm trying to have another field named "mentor" in the employee model, who will again be an employee (a user from employee model) , so how do I make a model refer itself?
i want something like this
mentor :[{
type: mongoose.Schema.Types.ObjectId, ref: 'Employee'
}]
Does this work?
what is the correct way to do this?
also I wanted a teamMembers field which will be an array of 0 or more employees from employee model
how do i do this?
This will work. You had it correct in your question:
const employeeSchema = new Schema ({
mentor: [{ type: Schema.Types.ObjectId, ref: 'Employee' }],
teamMembers: [{ type: Schema.Types.ObjectId, ref: 'Employee' }],
})
I've added some routes as well just to give you an idea on how to use it.
// add team member to employee
index.post('/addTeamMember', (req, res) => {
Employee.findById(req.body.employeeId, (err, employee) => {
if (err) {
console.log(err);
} else {
employee.teamMembers.push(req.body.teamMemberId);
employee.save((err) => {
if (err) {
console.log(err);
} else {
res.redirect('/');
}
});
}
});
});
// add mentor to employee
index.post('/addMentor', (req, res) => {
Employee.findById(req.body.employeeId, (err, employee) => {
if (err) {
console.log(err);
} else {
employee.mentor.push(req.body.mentorId);
employee.save((err) => {
if (err) {
console.log(err);
} else {
res.redirect('/');
}
});
}
});
});
You can also populate those fields:
index.get('/populate', (req, res) => {
Employee.findById(req.body.employeeId)
.populate('mentor')
.populate('teamMembers')
.exec((err, employee) => {
if (err) {
console.log(err);
} else {
res.send(employee);
}
});
});
and just some suggestions:
Don't name your fields emFirstName, emLastName, etc. This is redundant as you already have your model named Employee. It also makes your code less legible. Instead just use firstName, lastName, etc.
I wouldn't recommend using all caps for a model name like 'EMPLOYEE' as this is not the recommended case for mongoose model names. They suggest upercase first letter, so Employee would be more correct.
Basically I'm trying to get the time and the entity changed in a particular model when ever the update method is called.
This is my model I want to keep track of:
const mongoose = require("mongoose");
const modelSchema = mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
name: {
type: String,
required: true,
},
note1: String,
note2: String,
note3: String,
images: {
type: Array,
required: true
},
status: {
enum: ['draft', 'pending_quote', 'pendong_payment', 'in_production', 'in_repair', 'pemding_my_review', 'fulfilled'],
type: String,
default: "draft"
},
price: {
type: mongoose.Schema.Types.ObjectId,
ref: "Price",
}
}, {
timestamps: true,
})
module.exports = mongoose.model("Model", modelSchema)
And this is the method I call to update the status:
exports.updateModel = async (req, res) => {
try {
let id = req.params.id;
let response = await Model.findByIdAndUpdate(id, req.body, {
new: true
})
res.status(200).json({
status: "Success",
data: response
})
} catch (err) {
res.status(500).json({
error: err,
msg: "Something Went Wrong"
})
}
}
you can add a new field in your schema like:
logs:[{
entity: String,
timeStamp: Date
}]
Then updating it basing on your current code:
let id = req.params.id;
// I don't know whats in the req.body but assuming that it
// has the correct structure when passed from the front end
let response = await Model.findByIdAndUpdate(id,
{
$set:req.body,
$push:{logs:{entity:'your entity name here',timeStamp:new Date()}}
}, {
new: true
})
I'm trying to create a route where it takes in a parameter for a username and then displays that users information. Only thing is, the username is in the user schema from when the user signs up. The profile schema references the user schema. How do I use the username parameter in the findOne call to display the users profile data?
User schema:
const UserSchema = new Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
module.exports = User = mongoose.model("users", UserSchema);
Profile schema:
const ProfileSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "users"
},
name: {
type: String
},
image: {
type: String
},
bio: {
type: String
},
location: {
type: String
},
website: {
type: String
},
social: {
youtube: {
type: String
},
facebook: {
type: String
},
instagram: {
type: String
},
twitter: {
type: String
}
}
});
module.exports = User = mongoose.model("profile", ProfileSchema);
Route:
router.get("/user/:username", (req, res) => {
const errors = {};
Profile.findOne({ user: req.params.user.username })
.populate("user", "username")
.then(profile => {
if (!profile) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile);
})
.catch(err => res.status(404).json(err));
});
Please try this :
router.get("/user/:username", async (req, res) => {
const errors = {};
try {
const profile = await User.aggregate([
{ $match: { username: req.params.username } },
{ $lookup: { from: "profile", localField: "_id", foreignField: "user", as: "userProfile" } },
{ $project: { userProfile: { $arrayElemAt: ["$userProfile", 0] }, username: 1, _id:0 } }
]).exec();
if (!profile.length) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile[0]);
} catch (error) {
console.log('Error in retrieving user from DB ::', error);
return res.status(404);
}
})
Try using aggregate, firstly you check-in user table for getting details of a specific username then fetch the profile details as below using lookup, if no profile found after unwind the document will not be fetched and you can check on aggregate result's length as aggregate always return an array in result :
User.aggregate([
{$match:{ username: req.params.user.username }},
{$lookup:{from:"profile",localField:"_id",foreignField:"userId",as:"profileData"}},
{$unwind:"$profileData"},
{$project:{profileData:1,username:1}}
{$limit:1}
])
.then(profile => {
if (!profile.length) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile[0]);
})
You can do it in 2 steps.
Look for users containing username in userSchema, get it's id.
Then in promise, use that id to, look for profileSchema contains.
router.get("/user/:username", (req, res) => {
users.findOne({ username: req.params.username }).then(_user=>{
profile.findOne({ user: _user._id }).populate('user').then(_profile => {
res.json(_profile);
})
})
});
This code will look for username in userSchema and look for userSchema's id in profileSchema then returns profileSchema populated with user.
I am quite new to node.js and I am writing a GET request to retrieve documents from my database. Here is the definition of my request (in users.js) -
router.get('/', function (req, res, next) {
Booking.find({} ,(err,prevBookings)=>{
if(err) {
res.status(400).send('No previous bookings found.');
}
if(prevBookings[0]) {
res.status(200).send(prevBookings);
}
else{
console.log("no records")
res.status(200).send("No previous bookings found.")
}
});
});
And I have defined these in the index.js file as follows:
router.use('/users', require('./users'))
router.use('/prev', require('./users'))
//and there are other routers here too but totally unrelated to users.js
But, I am getting the following error on Postman:
NotFoundError: Not Found
I know that this error indicates that I am not routing it correctly. But I am not able to identify where I am going wrong. Other routes that I have defined, are working. Could anyone please help me out with this? Thank you :)
I think you're specifying '/prev' twice. If you change your route in users.js to "/", see what happens. I think at the moment the route "/prev/prev" will return a result.
I'm guessing your users.js should look like so:
const express = require("express");
const router = express.Router();
router.get('/', function (req, res, next) {
Booking.find({} ,(err,prevBookings)=>{
if(err) {
res.status(400).send('No previous bookingd found.');
}
if(prevBookings[0]) {
res.status(200).send(prevBookings);
}
else{
console.log("no records")
res.status(200).send("No previous bookings found.")
}
});
});
module.exports = router;
And then at the top level (index.js) or whatever:
app.use("/", router);
router.use('/prev', require('./users'))
This should return the correct result on "/prev"
For the booking model, could you try modifying the mongoose schema like so (adding the { collection: } entry):
var bookingSchema = mongoose.Schema({
_id : {
type: mongoose.Schema.Types.ObjectId,
required: true
},
user_id : {
type: mongoose.Schema.Types.Mixed,
ref: 'User',
required: true
},
places_id : {
type: mongoose.Schema.Types.ObjectId,
ref: 'Place',
required: true
},
fromDate : {
type: Date,
required: true,
default: Date.now()
},
toDate : {
type: Date,
required: true,
default: Date.now()
},
people_count : {
type: String,
required: true
},
package_details : {
type: mongoose.Schema.Types.Mixed
},
total_cost : {
type : Number,
required : true
},
cost : {
type: Number,
required: true
},
// confirmation: {
// type : String,
// required: true
// },
transaction_no : {
type : String,
required : true
}
}, {
collection: 'booking'
}
)
let's say there was a User model and a Post model. In this situation User's would have many posts; User would be the parent and Post would be the child. Is it possible to query for posts directly?
For instance if I wanted to do something like
app.get('/post/search/:query', (req,res) => {
Posts.find({title: req.params.query }, (err,post) => {
res.send(JSON.stringify(post))
})
})
or would one have to do:
app.get('/post/search/:query',(req,res) => {
let resultsFromQuery = [];
User.find({'post.title':req.params.query'}, (err,user) => {
user.posts.forEach((post) => {
if(post.title === req.params.query){
resultsFromQuery.push(post);
}
})
})
res.send(JSON.stringify(resultsFromQuery))
})
EDIT: Here is my schema's.
User Schema (Parent)
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
PostSchema = require('./post.js');
let UserSchema = new Schema({
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
posts: [PostSchema]
})
module.exports = mongoose.model('User',UserSchema);
Post Schema (Child)
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
let PostSchema = new Schema({
title: {
type: String
},
description: {
type: String
},
image: {
type: String
},
original_poster: {
id: {
type: String,
required: true
},
username: {
type: String,
required: true
}
},
tags: {
type: [String],
required: true
}
})
module.exports = PostSchema;
EDIT:
Here is a sample document
the result of db.users.find({username: 'john'})
{
"_id" : ObjectId("5a163317bf92864245250cf4"),
"username" : "john",
"password" : "$2a$10$mvE.UNgvBZgOURAv28xyA.UdlJi4Zj9IX.OIiOCdp/HC.Cpkuq.ru",
"posts" : [
{
"_id" : ObjectId("5a17c32d54d6ef4987ea275b"),
"title" : "Dogs are cool",
"description" : "I like huskies",
"image" : "https://media1.giphy.com/media/EvRj5lfd8ctUY/giphy.gif",
"original_poster" : {
"id" : "5a163317bf92864245250cf4",
"username" : "john"
},
"tags" : [
"puppies",
"dogs"
]
}
],
"__v" : 1
}
Yes you can find directly the post title from the user model. like bellow
User.find({"posts.title": "Cats are cool"}, (err, users) => {
if(err) {
// return error
}
return res.send(users)
})
That will return user with all post not only the matching post title. So to return only matching post title can use $ positional operator. like this query
User.find({"posts.title": "Cats are cool"},
{username: 1, "posts.$": 1}, // add that you need to project
(err, users) => {
if(err) {
// return error
}
return res.send(users)
})
that only return matching post
Since you are saving OP data, why not do:
// you'll need to adapt how your are getting the user-id here
const { user } = req
Post.find({ title: 'the title', 'original_poster.id': user.id }, (err, posts) => {
console.log(posts); })
Though I would advise you to adjust your Post-schema:
original_poster: {
type: Schema.Types.ObjectId,
ref: 'User'
}
},
Then you can do Post.find({}).populate('original_poster') to include it in your results.!