Why wouldn’t mongoDB update the “Date” field of my user entry? - node.js

I created a User schema in my React App as follows:
const userSchema = new Schema(
{
profileId: String,
expirationDate: { type: Date, default: new Date() },
credits: { type: Number, default: 0 },
},
{ timestamps: { createdAt: "created_at" } }
);
When the user pays me, I want to reset/update two fields: the expirationDate and credits via a post method. Here’s the code I use on my expressjs backend server to update the database entry on MongoDB Atlas:
req.user.expirationDate = new Date(
req.user.expirationDate.setDate(req.user.expirationDate.getDate() + 30)
);
req.user.credits += 1;
const user = await req.user.save();
res.send(user);
Once the operation succeeded, I can see the field of “credits” gets updated (increased by 1). However, the “expirationDate” field remains unchanged. What’s more curious is that when I send the updated user object to my frontend server with “res.send(user)”, I can see the updated expirationDate in the console.
Successfully updated user model as designed/intended: seen from frontend console
But below is what I saw in my mongoDB:
Updated user entry in MongoDB: the Date field "expirationDate" is not updated; but, the "credits" field is.
What is going on here? How to fix it?

I was having a similar issue recently and haven't figured out the actual reason behind this, but as a workaround try telling mongoose explicitly that the expirationDate-field was changed:
req.user.expirationDate = new Date(
req.user.expirationDate.setDate(req.user.expirationDate.getDate() + 30)
);
req.user.markModified('expirationDate');
await req.user.save();
EDIT:
Just debugged it again and I think the reason behind this behaviour is your default value for expirationDate. Try passing it the Date.now function instead of immediately setting it to a new date:
expirationDate: {type: Date, default: Date.now},
This fixed it for me without having to use markModified().

Although we still have some unsolved issues on why the same set of codes works differently. I have decided not to deal with it for the moment. Here's my solution to the original problem: change the datatype from Date to String. Here's the new set of codes:
Creating User schema at the frontend:
const userSchema = new Schema(
{
profileId: String,
expirationDate: { type: String, default: new Date().toDateString() },
credits: { type: Number, default: 0 },
},
{ timestamps: { createdAt: "created_at" } }
);
Updating user at the backend to MongoDB Atlas::
d = new Date(req.user.expirationDate);
req.user.expirationDate = new Date(
d.setDate(d.getDate() + 30)
).toDateString();
req.user.credits += 1;
const user = await req.user.save();
console.log(typeof req.user.expirationDate);//Checking the datatype of "expirationDate"

Related

Mongoose delete records after certain time

I have a mongoose Schema which looks like this:
const USERS_DATA = new Schema({
_id: Number,
name: String,
img: String,
date: Date,
phone: String,
article: String,
createdAt: {
type: Date,
required: true,
default: Date.now,
index: { expires: '3d' }
}
},
{
collection: "users",
_id: false,
}
);
I need to push data to this schema.
const User = mongoose.model("users", USERS_DATA);
function pushToDB() {
const newUser = new User({
name: INPUT.name,
img: INPUT.img,
date: INPUT.date,
phone: INPUT.phone,
article: INPUT.article,
});
newUser.save(function (err) {
mongoose.disconnect();
if (err) return console.log(err);
});
}
This data have to be deleted after 3 days when it was pushed to database. How to implement it in node.js? I found it really confusing and tried lots of code. Any answers are appreciated! Thanks
P.S. I use mongoDb Atlas
You should separate the process to push the data into the database from the process to delete it after 3 days. You have already the first part :).
For the second part, you can write a function deleteOldDocument. This function will query for documents in DB that are created for 3 days or more, and delete them. Then, you can run this function periodically, 1 time per day for example.
The pseudo-code, in case you need it :
async function deleteOldDocument() {
const 3DaysAgo = ...; // here you can subtract 3 days from now to obtain the value
// search for documents that are created from 3 days or more, using $lt operator
const documentToDelete = await User.find({"created_at" : {$lt : 3DaysAgo }});
// delete documents from database
.....
// recall the function after 1 days, you can change the frequence
setTimeOut(async function() {
await deleteOldDocument();
}), 86400);
}
// call deleteOldDocument to start the loop
deleteOldDocument();

set an expiration time for document node.js mongodb

so i know there is something called TTL in mongo but i dont think it will work for what i want to do, i want to set a document from my schema where the default value is basic but when the customer pays his membership i want to set it to "plus" i did that query in my controller, but i want to know if there is a way to make this document when the value is "plus" to have an expiration time, like 1 week or 1 month, and when the time is out set it again to basic, there is the schema
const UserSchema = new Schema({
username: {type: String, required: true},
nombre_empresa: {type: String},
email: {type: String, required: true, unique: true},
password: {type: String, required: true},
tipo_cuenta: {type: String, required: true},
isNewUser: {type: String, default: 'basic'}
},
{
timestamps: true
},
{
typeKey: '$type'
});
and here the process when i change his status
userCtrl.renderMembershipSucess = async (req, res) => {
const status = 'plus'
const status_basic = 'basico'
const status_user = await User.findByIdAndUpdate(
req.user.id, {
$set: { isNewUser: status }
}
)
console.log(status_user)
res.render('users/sucess')
}
Rather than relying on a trigger that must fire and correctly update the user records, store the expiration and check it when determining if the user is plus or not.
For example, you might add a plusExpires field to the user schema with a default value of null. When you upgrade the user to plus, set plusExpires to the Date that it should no longer be valid.
Add an instance method to the schema to perform that check:
UserSchema.methods.isPlus = function() {
return this.isNewUser == "plus" && (( this.plusExpires == null ) || (this.plusExpires > new Date())
}
Then any time you need to test if a user is plus or not, just call User.isPlus() on the user object.
i dont think there mongodb provide a built-in way to do what you want, however you can run a cronjob every midnight to fetch your "plus" customers, check their membership and update it if necessary.
another way maybe to design your mongodb collections to leverage the TTL, you can separate the User and PlusMembership collections, when a user make a payment, insert a PlusMembership data with TTL

How to update MongoDB createdAt date

I am trying to update the automatically added createdAt date on a record to right now.
Here the code I am using:
Document.findOneAndUpdate({_id: docId}, {createdAt: new Date()}, function(err, updated) {});
It works on my dev environment, but not the server. It does not return an error, but does not update the record, just returns the unchanged record.
I tried formatting it with new Date().toISOnew Date().toISOString() or .toGMTString() but both still only work on the dev environment. Both have node 6.10, but the server has mongo 3.4.4 and the dev has mongo 3.2.10.
If I add another field to be updated (second arugment), that field gets updated fine, but createdAt remains unchanged.
Automatic createdAt and updatedAt fields are populated by mongoose using the timestamps option as
const schema = new Schema({
// Your schema...
}, {
timestamps: { createdAt: true, updatedAt: false }
})
If you take a look at the source code you'll see that createdAt is excluded from updates. It's fairly easy though to modify your schema accordingly.
const schema = mongoose.Schema({
name: String,
createdAt: { type: Date, default: Date.now }
});
const Test = mongoose.model('test', schema);
const john = await Test.create({name: 'John Doe'});
console.log(john);
const updatedJohn = await Test.findOneAndUpdate({name: 'John Doe'}, { createdAt: Date.now() });
console.log(updatedJohn);
I do not know if it works for update, but for create, I first make sure have the createdAt field abled in my DB and then set the createdAt field like this:
{createdAt: moment().toISOString()}
because createdAt and updatedAt are ISO strings.
Edit: Just like Nigel mentioned, I do use moment library but new Date().toISOString() gives you the same result.

mongoose: Insert a subdocument NOT an array

I am struggling to insert a document inside another document. I've looked at all the entries like this but they aren't quite what I am looking for.
Here is the scenario:
I have a common document that has its own schema. Lets call it a related record:
(function(){
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var relatedRecordSchema = new Schema({
params: {
recordId: Schema.Types.ObjectId,
recordType: String,
recordTitle: String
},
metadata: {
dateCreated: {type: Date, default: Date.now}
}
},{ _id : false });
mongoose.model('RelatedRecord', relatedRecordSchema);
})();
I have no trouble inserting this in an ARRAY inside document that require it. I.e its configured this way:
//Embedded
relationships: {
following: [mongoose.model('RelatedRecord').schema],
followers: [mongoose.model('RelatedRecord').schema],
blocked: [mongoose.model('RelatedRecord').schema]
}
This works perfectly.
The scenario that does not work is where there is a single related record, lets say the source of a notification:
var notificationSchema = new Schema({
params: {
title: String,
imageUrl: String,
source: mongoose.model('RelatedRecord').schema
},
metadata: {
dateCreated: { type: Date, default: Date.now },
dateViewed: Date
}
});
So when I am creating the notification I try and assign the previously prepared RelatedRecord
returnObj.params.source = relatedRecord;
The record appears during a debug to be inserted (it is inside a _docs branch but far deeper than I would expect) but when the object is saved (returnObj.save()) the save routine is abandoned without error, meaning it does not enter into the callback at all.
So it looks to me that i'm confusing mongoose as the dot assignment is forcing the subdoc into the wrong location.
So the question is simple:
How do I set that subdocument?
What the question isn't:
No I don't want to populate or advice on how you would solve this problem differently. We have sensible reasons for doing things how we are doing them.
Cheers
b
As Hiren S correctly pointed out:
1) Sub-Docs = array, always. Its in the first line in the docs :|
2) By setting the type to mixed, assignment of the object worked.
I'm a dumdum.

Time to live in mongodb, mongoose dont work. Documents doesnt get deleted

Im using this scheme for a session in my node.js app
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// define the schema for our user session model
var UserSessionSchema = new Schema({
sessionActivity: { type: Date, expires: '15s' }, // Expire after 15 s
user_token: { type: String, required: true }
});
module.exports = mongoose.model('UserSession', UserSessionSchema);
And I create a "session" in my app with:
...
var session = new Session();
session.user_token = profile.token;
session.save(function(save_err) {
if (save_err) {
....
} else {
// store session id in profile
profile.session_key = session._id;
profile.save(function(save_err, profile) {
if (save_err) {
...
} else {
res.json({ status: 'OK', session_id: profile.session_id });
}
});
...
The problem is that the document lives permanetly, its never expires. It should only live for 15 seconds (up to a minute). Whats wrong with my code? I have tried to set the expries: string to a number i.e 15, to a string '15s' and so on.
var UserSessionSchema = new Schema({
sessionActivity: { type: Date, expires: '15s', default: Date.now }, // Expire after 15 s
user_token: { type: String, required: true }
});
A TTL index deletes a document 'x' seconds after its value (which should be a Date or an array of Dates) has passed. The TTL is checked every minute, so it may live a little longer than your given 15 seconds.
To give the date a default value, you can use the default option in Mongoose. It accepts a function. In this case, Date() returns the current timestamp. This will set the date to the current time once.
You could also go this route:
UserSessionSchema.pre("save", function(next) {
this.sessionActivity = new Date();
next();
});
This will update the value every time you call .save() (but not .update()).
To double check the indexes that have been created in the DB you can run this command in your mongo shell db.yourdb.getIndexes(). When changing the indexes you have to manually delete it in the collection before the new one will take effect. Check here for more information Mongoose expires property not working properly

Resources