Suppose i have a Schema as follows:
const UserSchema = new mongoose.Schema({
name: String,
createdAt: { type: Date, default: Date.now, expires: 3600 }
});
const User= mongoose.model('User', UserSchema);
const user = new User({name: 'Me'});
console.log(user);
setTimeout(function () {
User.update({name: 'Me'}, {name: 'You'}).exec((err, user) => console.log(user));
//update statement is incorrect, but i think you got my point
}, 2000);
So i wanted to know if updating the document will reset the document's expire time if other if change the name attribute, or the time will restart (reset) only if the createdAt value is change.
The Mongoose documentation does not have any point on this.
No, modifying a document's fields other than createdAt has no effect on when the document expires.
The document will expire when: current time >= createdAt + 3600
Related
I currently have data saving and expiring to/from a database via a mongoose schema like so:
var UserSchema = new Schema({
createdAt: { type: Date, expires: '1m' },
name: String,
email: String
});
The only problem is that the document that's saved to the database is completely removed from the database. How would I refactor the above so that the name/email address stay in the database but if the user attempts to login after their expiry date then they're greeted with a message saying 'session has expired, renew session'. (or something similar)
I'm wanting to do it this way because then if the user logs in with an expired email address the server is still able to lookup the email address and spit out a "expired session" message rather than a "not found" error which is what happens when data is deleted.
So to reiterate, how do I keep expired data in a mongo/mongoose database so the app is able to find the email address the user is attempting to login with but if their session has expired they need to renew the session?
You should use concept of Schema Reference for this. Save your expired field in another table and join your main user_table and expire_table(wxample name)
var UserSchema = new Schema({
name: String,
email: String
});
//save date by-default
//expire in 1 min as in your example
var expireSchema = new Schema({
createdAt: { type: Date, default: Date.now, expires: '1m' },
user_pk: { type: Schema.Types.ObjectId, ref: 'user_expire'}
});
var userTable = mongoose.model('user_expire', UserSchema);
var expireTable = mongoose.model('expireMe', expireSchema);
//Save new user
var newUser = new userTable({
name: 'my_name',
email: 'my_email'
});
newUser.save(function(err, result) {
console.log(result, 'saved')
var newExpire = new expireTable({
user_pk:result._id
});
//use _id of new user and save it to expire table
newExpire.save(function(err, result) {
console.log('saved relation')
})
})
Now to detect whether session has expired or not
1. on executing this code before data gets expired
expireTable.findOne()
.populate('user_pk')
.exec(function (err, result) {
if (err) throw err;
console.log(result)
if(result == null) {
console.log('session has expired, renew session')
} else {
console.log('session is active')
}
});
//output - session is active
2. on executing this code after data gets expired
expireTable.findOne()
.populate('user_pk')
.exec(function (err, result) {
if (err) throw err;
console.log(result)
if(result == null) {
console.log('session has expired, renew session')
} else {
console.log('session is active')
}
});
//output - session has expired, renew session
The accepted answer is good, but with Mongo 3.0 and above, the
createdAt: { type: Date, default: Date.now, expires: '1m' }
does not work for me.
Instead I used
var expireSchema = new Schema({
expireAt: {
type: Date,
required: true,
default: function() {
// 60 seconds from now
return new Date(Date.now() + 60000);
}
},
user_pk: { type: Schema.Types.ObjectId, ref: 'user_expire'}
});
More info is here: Custom expiry times for mongoose documents in node js
EDIT
My comment above would also require invoking a Mongo function directly rather than via Mongoose syntax. This would be something like:
db.[collection-name].createIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } )
Additionally this is for the Expire Documents at a Specific Clock Time way of doing a ttl field.
And I still can't seem to get it to work, but might be because of the erratic way that the ttl reaper runs (once every 60 secs, but could be longer...)
EDIT
My issues were due to having an earlier incorrectly configured ttl index persisting on the expireAt field, this prevented my later (correctly defined) index from working. I fixed this just by deleting any non-_id earlier indexes and then re-adding my ttl index on the expireAt field.
Use db.[collection-name].getIndexes()
and
db.[collection-name].dropIndex({ "expireAt":1 }) to clear out before re-applying.
Also one other caveat - setting a Date snapshot in the default property of the expiredAt field means that the default value will always be a fixed date - instead set this Date value dynamically each time you create an instance of expireSchema:
var expireSchema = new Schema({
expireAt: Date,
user_pk: { type: Schema.Types.ObjectId, ref: 'user_expire' }
});
expireSchema
.virtual('expiryTime')
.get(function() {
//allow for reaper running at 60 second intervals to cause expireAt.fromNow message to be 'in the past'
var expiryTime = moment(this.expireAt).fromNow();
if(String(expiryTime).indexOf("ago")){
expiryTime = "in a few seconds";
}
return expiryTime;
});
var expireModel = mongoose.model('UserExpire', expireSchema);
expireModel.collection.ensureIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } );
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"
I currently have data saving and expiring to/from a database via a mongoose schema like so:
var UserSchema = new Schema({
createdAt: { type: Date, expires: '1m' },
name: String,
email: String
});
The only problem is that the document that's saved to the database is completely removed from the database. How would I refactor the above so that the name/email address stay in the database but if the user attempts to login after their expiry date then they're greeted with a message saying 'session has expired, renew session'. (or something similar)
I'm wanting to do it this way because then if the user logs in with an expired email address the server is still able to lookup the email address and spit out a "expired session" message rather than a "not found" error which is what happens when data is deleted.
So to reiterate, how do I keep expired data in a mongo/mongoose database so the app is able to find the email address the user is attempting to login with but if their session has expired they need to renew the session?
You should use concept of Schema Reference for this. Save your expired field in another table and join your main user_table and expire_table(wxample name)
var UserSchema = new Schema({
name: String,
email: String
});
//save date by-default
//expire in 1 min as in your example
var expireSchema = new Schema({
createdAt: { type: Date, default: Date.now, expires: '1m' },
user_pk: { type: Schema.Types.ObjectId, ref: 'user_expire'}
});
var userTable = mongoose.model('user_expire', UserSchema);
var expireTable = mongoose.model('expireMe', expireSchema);
//Save new user
var newUser = new userTable({
name: 'my_name',
email: 'my_email'
});
newUser.save(function(err, result) {
console.log(result, 'saved')
var newExpire = new expireTable({
user_pk:result._id
});
//use _id of new user and save it to expire table
newExpire.save(function(err, result) {
console.log('saved relation')
})
})
Now to detect whether session has expired or not
1. on executing this code before data gets expired
expireTable.findOne()
.populate('user_pk')
.exec(function (err, result) {
if (err) throw err;
console.log(result)
if(result == null) {
console.log('session has expired, renew session')
} else {
console.log('session is active')
}
});
//output - session is active
2. on executing this code after data gets expired
expireTable.findOne()
.populate('user_pk')
.exec(function (err, result) {
if (err) throw err;
console.log(result)
if(result == null) {
console.log('session has expired, renew session')
} else {
console.log('session is active')
}
});
//output - session has expired, renew session
The accepted answer is good, but with Mongo 3.0 and above, the
createdAt: { type: Date, default: Date.now, expires: '1m' }
does not work for me.
Instead I used
var expireSchema = new Schema({
expireAt: {
type: Date,
required: true,
default: function() {
// 60 seconds from now
return new Date(Date.now() + 60000);
}
},
user_pk: { type: Schema.Types.ObjectId, ref: 'user_expire'}
});
More info is here: Custom expiry times for mongoose documents in node js
EDIT
My comment above would also require invoking a Mongo function directly rather than via Mongoose syntax. This would be something like:
db.[collection-name].createIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } )
Additionally this is for the Expire Documents at a Specific Clock Time way of doing a ttl field.
And I still can't seem to get it to work, but might be because of the erratic way that the ttl reaper runs (once every 60 secs, but could be longer...)
EDIT
My issues were due to having an earlier incorrectly configured ttl index persisting on the expireAt field, this prevented my later (correctly defined) index from working. I fixed this just by deleting any non-_id earlier indexes and then re-adding my ttl index on the expireAt field.
Use db.[collection-name].getIndexes()
and
db.[collection-name].dropIndex({ "expireAt":1 }) to clear out before re-applying.
Also one other caveat - setting a Date snapshot in the default property of the expiredAt field means that the default value will always be a fixed date - instead set this Date value dynamically each time you create an instance of expireSchema:
var expireSchema = new Schema({
expireAt: Date,
user_pk: { type: Schema.Types.ObjectId, ref: 'user_expire' }
});
expireSchema
.virtual('expiryTime')
.get(function() {
//allow for reaper running at 60 second intervals to cause expireAt.fromNow message to be 'in the past'
var expiryTime = moment(this.expireAt).fromNow();
if(String(expiryTime).indexOf("ago")){
expiryTime = "in a few seconds";
}
return expiryTime;
});
var expireModel = mongoose.model('UserExpire', expireSchema);
expireModel.collection.ensureIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } );
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
In my database collections, I want to update a 'lastChanged' field every time the record is updated with the current datetime. I want it to be in the same format as mongoose's default date like:
ISODate("2011-10-06T14: 01: 31.106Z")
Any words of wisdom?
If you just want an ISO String use:
new Date().toISOString()
One way of accomplishing this is to use Mongoose Middleware and update the field pre-save.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
//schema
var SomethingSchema = new Schema({
text: {type: String},
createdAt: {type: Date, default: Date.now},
updatedAt: {type: Date, default: Date.now}
});
//middle ware in serial
SomethingSchema.pre('save', function preSave(next){
var something = this;
something.updatedAt(Date.now());
next();
});
It seems, however, that the middleware is not always invoked:
Notes on findAndUpdate()
pre and post are not called for update operations executed directly on the database, including Model.update,.findByIdAndUpdate,.findOneAndUpdate, .findOneAndRemove,and .findByIdAndRemove.order to utilize pre or post middleware, you should find() the document, and call the init, validate, save, or remove functions on the document. See explanation.
Update: See this question "add created_at and updated_at fields to mongoose schemas"
In a few days Mongo is going to announce new 2.6 version (currently you can download experimental 2.5.x version). Among many other features you can use $currentDate which is going to do exactly the thing you want:
db.users.update(
<criteria>,
{
$currentDate: { yourField: true},
}
)
The middleware function is a good approach, however, it should be
SomethingSchema.pre('save', function preSave(next){
var something = this;
something.updatedAt = Date.now();
next();
});
Since something.updateAt is not a function.
I added updated: new Date to fix a similar problem. Here is how I used it.
update: (req, res) => {
let userId = req.params.id;
let userParams = {
name: {
first: req.body.first,
last: req.body.last
},
email: req.body.email,
password: req.body.password,
updated: new Date
};
db.User.findOneAndUpdate(userId, { $set: userParams })
.then(upUser => res.json(`Profile Updated for: ${upUser.fullName}`))
.catch(err => res.status(422).json(err));
}