How to limit my collection size to only one document? - node.js

I have a API, which allows a user to add a intervalValue - which will be used in my program to determine how often my program runs. I am using node js, express and mongodb in my project.
This is the API which allows a user to add a value:
router.post('/', function(req, res, next) {
intervalValue.create(req.body, function(err, post) {
if (err) {
console.log(err);
} else {
res.json(post);
}
});
});
And this is the schema for it:
var intervalValueSchema = new mongoose.Schema({
milliseconds: {
type: Number,
min: 15000
}
}, {
capped: {
size: 1024,
max: 1,
autoIndexId: true
}
});
From my understanding, this schema will only allow milliseconds value larger than 15000 milliseconds, and because it is capped, it will only allow one document in the collection.
THE AIM : add a interval value, and then only be able to modify that value - i.e. it will not be allowed to be deleted, and no more will be able to be added. Hence I need it to be limited to one document.
However with the current code, I am able to add multiple documents to this collection (even though when I do isCapped() I get true returned), and when I update the value I can insert a value less than 15000 - which should not be allowed.
This is the update API:
router.put('/updateValue/:id', function(req, res, next) {
intervalPoll.findByIdAndUpdate(req.params.id, req.body, {
new: true
}, function(err, post) {
if (err) {
console.log(err);
} else {
res.json(post);
}
});
});
What am I doing wrong - I am a beginner with mongo so any tips would be appreciated. Also, is there a better way to achieve my aim?
Thanks!

I wouldn't expose the ID of that value and I would not implement the PUT handler with :id (which is not convenient anyway, because user has to know the ID which once set will never change and be only one).
I would implement two endpoints:
router.get('/interval', ...
and
router.post('/interval', ...
# or:
router.put('/interval', ...
The get handler would search the database for any document and if present return its value. If there is no such document it would return some default value.
The post handler would first verify the value and then modify a document if it exists or insert it if it doesn't.
I think that in this case it would be much easier to check for that number in your handler then to fight with Mongoose to do what you need. This is a specific use case.
Udate
Here are some examples:
Schema can be:
mongoose.Schema({
milliseconds: Number
});
And the handlers would be something like this - let's say that your model is called Interval:
var defaultInterval = {milliseconds: 15000};
router.get('/interval', function(req, res, next) {
Interval.find().exec(function (err, intervals) {
if (err || intervals.length === 0) {
res.json(defaultInterval);
} else {
res.json({milliseconds: intervals[0].milliseconds});
}
});
});
router.put('/interval', function(req, res, next) {
var milliseconds = // get it from request
if (!milliseconds || milliseconds < 15000) {
// respond with error
} else {
Interval.findOneAndUpdate(
{}, {milliseconds: milliseconds}, {upsert: true},
function(err, interval){
if (err) {
// respond with error
} else {
// respond with success
}
}
);
}
});
This is not tested and you need to fill in the blanks but you get the idea.

Related

Update data in MongoDB using Mongoose and Node.js

I am trying to update certain info in a user collection, when the user is visiting a page.
But my method doesn't work. Can anyone help to get it fixed.
app.get('/add-your-accommodation/apartment-type', (req, res, next) => {
if (req.isAuthenticated()) {
res.render('apartment-type.ejs')
} else {
res.render('login.ejs')
}
var id = req.params.id
if(mongoose.Types.ObjectId.isValid(id)) {
User.findByIdAndUpdate(id, {$set: {accomtype: 'house'}},{new: true})
}
});
Your req.params.id is undefined since there is no mention of it in the route path. You can do this,
app.get('/add-your-accommodation/apartment-type', (req, res) => {
if (!req.isAuthenticated()) {
return res.render('login.ejs')
}
res.render('apartment-type.ejs')
var id = req.user._id //since you're using passport (LocalStrategy)
if(mongoose.Types.ObjectId.isValid(id)) {
User.findByIdAndUpdate(id, {$set: {accomtype: 'house'}})
}
})
Now when you call your API, do it like this,
GET /add-your-accommodation/apartment-type
I agree with #kedar-sedai, when you update/change something in your DB, you should not use a GET request. A good practise would be to use the PUT method, even if you have nothing to pass in the body. It makes it easier for you and other developers to understand what your code does at a glance.
Here are 4 HTTP requests that will work in most of the use cases :
GET
You want to retrieve information from your DB (ex: get users, get all the apartment types...)
POST
You want to add information (ex: register user, add an apartment, ...), or send information using the body of the POST request (ex: login, ...)
PUT
You want to update a value (ex: change username, change an apartment type, ...)
DELETE
You simply want to delete something in your DB (ex: delete a user...)
Try findOneAndUpdate. Also, use callback in your query function for getting the error or result.
app.get('/add-your-accommodation/apartment-type/:id', (req, res, next) => {
if (req.isAuthenticated()) {
res.render('apartment-type.ejs')
} else {
res.render('login.ejs')
}
var id = req.params.id
if(mongoose.Types.ObjectId.isValid(id)) {
User.findOneAndUpdate({_id: mongoose.Types.ObjectId(id)}, { $set: { accomtype:'house' } },(err, result)=>{
if (err) throw new Error(err);
console.log(result)
return
})
}
});

How can I retrieve documents' properties from a pre hook?

I posted this question yesterday because I didn't know how to solve my problem.
Change variable value in document after some time passes?
I was told I need to use a pre hook. I tried to do it, but "this" would refer to the query, not to the document. So I couldn't retrieve the documents to check if the 4 weeks passed. (check the question, you will get it)
Because I don't know how to make this .pre('find') to use variables from each of my document (so it checks if the 4 weeks passed) I was thinking about looping through all of them and checking if 4 weeks passed.
router.get('/judet/:id([0-9]{2})', middleware.access2, function(req, res)
{
var title = "Dashboard";
Somer.find({}, function(err, someri)
{
if(err)
{
console.log(err);
}
else
{
res.render("dashboard", {title: title, id:req.params.id, someri:someri});
}
});
}); ///get route
var someriSchema = new mongoose.Schema({
nume: {type: String, required: true},
dateOfIntroduction: {type:Date, default: Date.now, get: formatareData},
});
someriSchema.pre('find', function(next) {
console.log(this.dateOfIntroduction); <- this will return undefined, because this refers to the query, actually
next();
});///schema and the pre hook. I thought I could use it like this, and inside the body of the pre hook I can check for the date
Here's what I am talking about:
router.get('/judet/:id([0-9]{2})', middleware.access2, function(req, res)
{
var title = "Dashboard | Best DAVNIC73";
Somer.find({}, function(err, someri)
{
if(err)
{
console.log(err);
}
else
{
someri.forEach(function(somer)
{
///check if 4 weeks passed and then update the deactivate variable
})
res.render("dashboard", {title: title, id:req.params.id, someri:someri});
}
});
});
but I think this will be very bad performance-wise if I will get many entries in my DBs and I don't think this is the best way to do this.
So, if I was told correctly and I should use a pre hook for obtaining what I've said, how can I make it refer to the document?
Ok, I think I understood your requirements. this is what you could do:
/*
this will always set a documents `statusFlag` to false, if the
`dateOfIntroduction` was before Date.now()
*/
const mongoose = require('mongoose')
someriSchema.pre('find', function(next) {
mongoose.models.Somer.update(
{ datofIntroduction: { $lte: new Date() }},
{ statusFlag : false})
.exec()
.then((err, result) => {
// handle err and result
next();
});
});
The only problem I see, is that you are firing this request on every find.
in query middleware, mongoose doesn't necessarily have a reference to
the document being updated, so this refers to the query object rather
than the document being updated.
Taken straight from the documentation of mongoose
I pointed you yesterday to their documentation; but here is a more concrete answer.
someriSchema.post('find', function(res) {
// res will have all documents that were found
if (res.length > 0) {
res.forEach(function(someri){
// Do your logic of checking if 4 weeks have passed then do the following
someri.deactivated = true
someri.save()
})
}
})
What this basically do is for every found schema you would update their properties accordingly, your res can have only 1 object if you only queried 1 object. your second solution would be to do the cron
EDIT: This is what you would do to solve the async issue
const async = require('async')
someriSchema.post('find', function(res) {
async.forEach(res, function(someri, callback) {
// Do your logic of checking if 4 weeks have passed
// then do the following - or even better check if Date.now()
// is equal to expiryDate if created in the model as suggested
// by `BenSow`
// Then ONLY if the expiry is true do the following
someri.deactivated = true
someri.save(function (err) {
err ? callback(err) : callback(null)
})
}, function(err){
err ? console.log(err) : console.log('Loop Completed')
})
})

Sails.js - Models & Controllers not working or throwing unexpected errors

I'm quite new to Sails.js, but not new to MVC. I feel like I'm really close, but just missing something somewhere.
I generated a controller with a single action "overview", and if I keep it "hello world"- easy, it works. The below code runs just fine:
// My controller
module.exports = {
overview : function(req, res) {
return res.send("<h1>Tossel<h1>");
},
}
However, when I try to get some data from the model and pass it to the controller, I have issues. I've tried it in two ways. I first defined a custom method on the Model and called it from my controller, like below:
// My model, e.g. Orders
module.exports = {
tableName: 'Orders',
attributes: {
id : {
type: 'integer',
primaryKey: true,
unique: true
},
client_id : {
type: 'integer'
}
},
getAllOrders: function(opts, cb) {
Orders.find().exec(function(err, data) {
cb(err, data);
});
}
};
// My controller
module.exports = {
overview : function(req, res) {
Orders. getAllOrders(null, function(err, data) {
return res.send(data);
});
},
}
From what I can see in the docs, that's pretty much how I should do it. I get the below error though. I get this when executing the command in the console, and by calling it from the controller:
TypeError: query.exec is not a function
I then tried doing everything in the controller:
// My controller
module.exports = {
overview : function(req, res) {
Orders.find().exec(function(err, data) {
return res.send(data);
});
},
}
This method didn't actually throw any errors, but it returned nothing. I'm pretty sure there is data in the database though. Since I wasn't 100% sure, I created an "add" action to the controller as below, to add data to the table. This results in no errors, but the page just hangs.
module.exports = {
overview : function(req, res) {
Orders.find().exec(function (err, data) {
return res.json(data);
});
},
add : function(req, res) {
var data = {
id: 1,
client_id: 1,
};
Orders.create(data).exec(function (err, data) {
return res.send(data);
});
}
};
Can someone please point me in the right direction here. I find the docs to be too simplistic. It's good at showing you the easy stuff, but that's all it shows.
Also, as a bonus, can someone please tell me what I need to do to be able to make changes and refresh the page to see them? At the moment I have to kill the server and bring it back up again to see the changes. This doesn't seem right, I'm must be missing something somewhere.
Edit
As per the below comment:
Node v8.10.0
Sails v0.12.14
I specify the table name because it is indeed different from the model name. I changed the actual name of the table and model in the code I posted.

Retrieving the latest entry and then deleting it right away with MongoDB and Mongoose?

Forgive me for my ignorance but I'm more of a C# developer. This is my first NodeJS program. I'm trying to write a program where my program calls a GET to my node.js server. When the server receives that request I want it to send the latest entry from MongoDB and delete it immediately so it cannot be reused. I want this to be done server-side.
Here's what I have below,
router.get('/', function(req, res, next) {
Todo.findOne({}, {}, { sort: { '_id' : -1 } }, function(err, post) {
res.json(post);
Todo.remove({_id: -1});
});
});
Consider using the findOneAndRemove() API which is an atomic update that issues a mongodb findAndModify remove command, hence always returns the deleted document. Use the sort option to choose which document to delete since multiple docs will be returned by the query:
router.get('/', function(req, res, next) {
var query = {},
options = { "sort": { "_id": -1 } }; // or { "sort": "-_id" }
Todo.findOneAndRemove(query, options, function(err, post) {
if (err) console.error(err);
res.json(post);
});
});

Memory leak, using Sequelize ORM for NodeJS

I'm trying to use this Sequelize ORM stuff for my project. I've integrated it as on example https://github.com/sequelize/express-example. So, cool - for now it's working with all relations and other goods. The problem is, that pm2 show's that my memory usage grows and never coming back.
This is my test script, that eats 100 Mb of RAM per launch. Have I missed something?
router.get('/test', hutils.authChecker, function(req, res, next) {
Project.findById(1,{ include : [Player]}).then(function(project) {
return Promise.denodeify(async.map)(project.Players, function(player, callback) {
Player.create({
project_id : 1,
name : 'iter_'+Math.floor(Math.random() * 1000000)+Math.floor(Math.random() * 1000000)
}).then(function(gamer) {
callback(null, gamer)
});
});
}).then(function(plrs) {
return Promise.denodeify(async.map)(plrs, function(guy, callback) {
guy.update({name : sqlRequest+'zzzzz'+Math.random()}).then(function(number) {
callback(null, number);
});
});
}).then(function(numbers) {
return Player.findAll({where : {name : {$like : '%zzzzz%'}}});
}).then(function(zets) {
return Promise.denodeify(async.map)(zets, function(zet, callback) {
zet.destroy().then(function(number) {
callback(null, number);
});
});
}).catch(function(err) {
next(err);
});
});
P.S. It`s make no sense, just to look how the ORM works. If it's matter, i have 1k players, for this project.
In queries that have results with a lot of rows, all of them will get loaded into memory before the callback.
So in this example, the query Project.findById(1,{ include : [Player]}) deserializes project 1 and all of its 1,000 players into JavaScript objects before returning them in the .then(function(project). Furthermore, the array of plrs, numbers and zets are all similarly stored in memory before they get returned thus increasing memory usage.
A way around this would be to get the database to do the heavy lifting. For example don't return each gamer that gets created and instead perform a update query on the db.
Player.update({
name : sqlRequest + 'zzzzz' + Math.random(),
}, {
where: {
createdAt: {
$gte: new Date() // or some other filter condition that identifies the records you need.
}
}
});
And then instead of destroying each zet, perform a delete query on the db.
Player.destroy({
where: {
name : {
$like : '%zzzzz%'
}
}
});

Resources