How do I add expenses for only specific users? - node.js

I have created two models in my app- one for User (_id, email, username, password) and one for Expense (_id, date, detail, amount, category). For the users, I have finished the authentication with jwt.
I want logged-in users to be able to add/remove expenses and not show their expenses to other users but I don't know how I can implement that. I am not asking for code- I would be grateful if you could roughly tell me what I need to do. Thanks!
//expense schema
const expenseSchema = new mongoose.Schema(
{
date: Date,
detail: String,
amount: Number,
category: String
}
)
//controller for adding expenses
const addExpenseController = (req, res) => {
const expense = new Expense({
"date": new Date(),
"amount": req.body.amount,
"detail": req.body.detail,
"category": "expense"
});
expense.save();
res.send('expense added');
};

You should define a ref property in the expense schema pointing at the User model (change the value of the ref attribute to equal the model name given to the users):
const expenseSchema = new mongoose.Schema(
{
...
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}
)
Then, on creation, specify the user by setting the value of its _id.
You can either store it in the session or pass it in the body, depending on your implementation:
const addExpenseController = async (req, res) => {
try {
const expense = new Expense({
date: new Date(),
amount: req.body.amount,
detail: req.body.detail,
category: 'expense',
user: req.session.user_id, // or req.body.user_id
});
await expense.save();
res.send('expense added');
} catch (err) {
res.send('server error');
}
};

Related

Update document in MongoDB via NodeJS

So my knowledge of NodeJS and MongoDD are non-existent (just need to do a small code update for a friend) and I'm stuck.
Need to update a single document inside a collection via a unique id but can't seem to do it.
Here's the Model (I've trimmed it down and cut out all unnecessary data). I'm trying to update the field notes inside a transaction.
In short each entry in the given (an Agent) table will have a collection of multiple Transactions & Documents. I need to update a specific Transaction with the unique _id that is auto generated.
import { Schema, model } from 'mongoose';
interface Transaction {
first_name: string;
last_name: string;
type: string;
notes: string;
}
interface Agent {
org_id: number;
transactions: Array<Transaction>;
documents: Array<string>;
}
const transactionSchema = new Schema<Transaction>({
first_name: { type: String },
last_name: { type: String },
type: { type: String },
notes: String,
});
const transactionsSchema = new Schema<Agent>({
org_id: { type: Number },
transactions: [transactionSchema],
documents: [documentTypesSchema],
});
const AgentTransaction = model<Agent>(
'agent_transaction_table',
transactionsSchema
);
export default AgentTransaction;
Here's what I tried but didn't work (obviously), again I've trimmed out all unnecessary data. Just to clarify, the endpoint itself works, but the DB update does not.
import AgentTransaction from '../models/transaction'; // the above model
transaction.put('/notes', async (req, res) => {
const { org_id, transaction_id, notes } = req.body;
try {
const notesResult = await AgentTransaction.updateOne({
'transactions._id': transaction_id,
}, {
$set: {
'notes': notes
},
});
res
.status(200)
.json({ message: 'Updated', success: true, notesResult });
} catch (error) {
res.status(400).send(error);
}
});
So I figured it out. Maybe it'll help someone else as well.
const notesResult = await AgentTransaction.updateOne({
'transactions._id': { $in: [trunc2] },
}, {
$set: {
'transactions.$.notes': notes
},
});
The main issue was that the payload object needed to target the collection folder + the wildcard + the field, not just only the field.

best schema model in my case (mongoose js)

I need advice on the schema.
For now, it looks as shown below:
const accountSchema = new mongoose.Schema({
nickname:{
index:true,
type: String,
},
levels_scores: [Number],
money: Number,
skins: { circles: [Number], sticks: [Number], targets: [Number] },
current_skins: { circles: Number, sticks: Number, targets: Number },
})
my queries are always filtered by nickname so I created index on it.
I very often update money value :
saveMoney(req, res) {
const nickname,money = req.body
Accounts.findOneAndUpdate({ nickname:nickname},{money:money}),{useFindAndModify:false)
res.sendStatus(200)
}
Callback in the case will return all the document data which I don't need.(levels_scores,skins etc.) Performance waste I think
Am I thinking wrong?
Should I make schema with references,like money schema which stores only parent ID and money value?
I also have queries like:
Accounts.findOne({ nickname: req.body.nickname }, (err, account) => {
const part = req.body.skin[0]
const skin_number = req.body.skin[1]
account.skins[part].push(skin_number)
account.markModified("skins")
account.save()
res.sendStatus(200)
})
If i use .select("skins") method to not return all the document data, will I be able to save it?
How would I do that to be the most performant?

How to create a document with the data from another collection?

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const CompanySchema = new Schema(
{
companyName: {
type: String,
required: true,
unique: true
},
taxOffice: {
type: String
},
taxNumber: {
type: String
},
},
{
timestamps: true
}
);
const Company = mongoose.model('Company', CompanySchema);
module.exports = Company;
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const DateSchema = new Schema({
name: {
type: String,
unique: true,
required: true
},
companies: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Company' }]
});
const Date = mongoose.model('Date', DateSchema, 'dates');
module.exports = Date;
const router = require('express').Router();
const Date = require('../models/date');
router.route('/').get((req, res) => {
Date.find()
.then(dates => res.json(dates))
.catch(err => res.status(400).json('Error: ' + err));
});
router.route('/add').post((req, res) => {
const name = req.body.name;
const newDate = new Date({ name });
newDate
.save()
.then(() => res.json('Date added!'))
.catch(err => res.status(400).json('Error: ' + err));
});
module.exports = router;
I have 2 collections called Company and Date.
I inserted many data to Company Collection.
But I want that Company data(companies) to copied into Date Collection whenever I create a Date document.
I want to store company data as an array for each Date.
By the way don't know that my schema design is correct for the purpose. What should I do?
I want to have a Date document like:
{
name: "DECEMBER-2019",
companies: ['5e2076236664640d22515f7b', '5e2076236664640d22515f7a']
}
It sounds like you want to take an action whenever you create a new document in the Date collection. If you are using Atlas (MongoDB's fully managed database as a service), you can configure a Trigger to fire whenever a new document is inserted into the Date collection. Another option (regardless of whether you are using Atlas or not) is to use Change Streams to monitor changes in the Date collection. For more on how to configure these, see https://www.mongodb.com/blog/post/node-js-change-streams-and-triggers.
Assuming that only companies present at the time of Date Object creation should be added in Date's companies field, Then you can maintain a cache of companies ObjectIds at any point of time which will be updated for every delete/insert in Company Collection.
You can write your own function of creating a date object which will have parameter name which add the current companies ObjectIds.
Date.statics.getNewDateObject = function(name) {
let companyIds = await getCachedCompanyIds();
return new Date({name: name, companies: companyIds});
}
This will have a document like:
{
name: "TODAY'S DATE",
companies: ['5e2076236664640d22515f7b', '5e2076236664640d22515f7a']
}
If you want to populate the Date object with complete Company Information, You can use the populate method present in mongoose which populates the complete company information into the Date Object.
https://mongoosejs.com/docs/populate.html
I solved it:
router.route('/add').post((req, res) => {
const name = req.body.name;
Company.find().then(companies => {
const newDate = new Date({ name, companies });
newDate
.save()
.then(() => res.json('Date added!'))
.catch(err => res.status(400).json('Error: ' + err));
});
});

Saving data to array in mongoose

Users are able to post items which other users can request. So, a user creates one item and many users can request it. So, I thought the best way would be to put an array of users into the product schema for who has requested it. And for now I just want to store that users ID and first name. Here is the schema:
const Schema = mongoose.Schema;
const productSchema = new Schema({
title: {
type: String,
required: true
},
category: {
type: String,
required: true
},
description: {
type: String,
required: true
},
userId: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
requests: [
{
userId: {type: Object},
firstName: {type: String}
}
],
});
module.exports = mongoose.model('Product', productSchema);
In my controller I am first finding the item and then calling save().
exports.postRequest = (req, res, next) => {
const productId = req.body.productId;
const userId = req.body.userId;
const firstName = req.body.firstName;
const data = {userId: userId, firstName: firstName};
Product.findById(productId).then(product => {
product.requests.push(data);
return product
.save()
.then(() => {
res.status(200).json({ message: "success" });
})
.catch(err => {
res.status(500).json({message: 'Something went wrong'});
});
});
};
Firstly, is it okay to do it like this? I found a few posts about this but they don't find and call save, they use findByIdAndUpdate() and $push. Is it 'wrong' to do it how I have done it? This is the second way I tried it and I get the same result in the database:
exports.postRequest = (req, res, next) => {
const productId = req.body.productId;
const userId = req.body.userId;
const firstName = req.body.firstName;
const data = {userId: userId, firstName: firstName};
Product.findByIdAndUpdate(productId, {
$push: {requests: data}
})
.then(() => {
console.log('succes');
})
.catch(err => {
console.log(err);
})
};
And secondly, if you look at the screen shot is the data in the correct format and structure? I don't know why there is _id in there as well instead of just the user ID and first name.
Normally, Developers will save only the reference of other collection(users) in the collection(product). In addition, you had saved username also. Thats fine.
Both of your methods work. But, second method has been added in MongoDB exactly for your specific need. So, no harm in using second method.
There is nothing wrong doing it the way you have done it. using save after querying gives you the chance to validate some things in the data as well for one.
and you can add additional fields as well (if included in the Schema). for an example if your current json return doesn't have a field called last_name then you can add that and save the doc as well so that's a benefit..
When using findById() you don't actually have the power to make a change other than what you program it to do
One thing I noticed.. In your Schema, after you compile it using mongoose.modal()
export the compiled model so that you can use it everywhere it's required using import. like this..
const Product = module.exports = mongoose.model('Product', productSchema);

how to insert multiple JSON objects to one property of a schema?

I have a requirement to store multiple JSON objects in a property of schema.
take this example...
const Schema = require("mongoose").Schema;
const Student= Schema({
student_id: String,
name:String
attendance:[
{
date: Date,
Status: String
}
]
});
I need to insert attendance of individual student which looks like this..
student_id: student_001,
name:'Joe'
attendance:[
{
date: 24-10-2018,
status: 'Present'
},
{
date: 25-10-2018,
status: 'Absent'
},
//list goes on
]
I am using NodeJs as Backend, EJS template as front end and mongodb database. Date and Status comes when user submits data from front end. So I am having hard time writing my post request. Any types of comments / suggestions / change of model structure are welcome. Thank you.
You can create a separate attendance Schema.
const Schema = require("mongoose").Schema;
const AttendanceSchema = new Schema({
date: Date,
status: String
});
const StudentSchema = new Schema({
student_id: String,
name:String
attendance:[AttendanceSchema]
});
const Student = mongoose.model('Student', StudentSchema);
Add a new Student.
let newStudent = new Student({
student_id: student_001,
name:'Joe'
});
newStudent.save();
Update attendance:
let att1 = {
date: 24-10-2018,
status: 'Present'
};
// Here 'id' is id of particular student.
Student.update({ _id: id }, { $push: { attendance: att1 } })
.then(() => console.log("Success"))
.catch(err => console.log(err));
At some point later:
let att2 = {
date: 25-10-2018,
status: 'Absent'
};
// Here 'id' is id of particular student.
Student.update({ _id: id }, { $push: { attendance: att2 } })
.then(() => console.log("Success"))
.catch(err => console.log(err));
I suggest you to change the model structure to be normalized.
This will improve your experience in future statistics querying.
Also, one more suggestion - do not use string indentifiers in mongoDB, this can cause a headache in maintaining their uniqueness. Mongo has automated _id property assigning to each document, you could use it if you need to indentify any object.
Considering my suggestions - the code will look like:
const Schema = require("mongoose").Schema;
const Student = Schema({
name: String
});
const Attendance = Schema({
date: Date,
status: String,
student_id: {
type: Schema.Types.ObjectId,
ref: 'Student'
}
})
Then, you could simply create attendance records assigned to the student :
const attendance = new AttendanceModel({
date: new Date('05/20/2018'),
status: "present",
student_id: "somestudentid"
});

Resources