set an expiration time for document node.js mongodb - node.js

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

Related

Making Mongodb documents inactive after certain time

Let's say I am creating a web application that posts event markers to a map to help flash mobs find out where to meet up. In this hypothetical app and due to the nature of flash mobs, these markers would have to become inactive after a certain time interval so as to not pollute the map with events that are no longer occurring. My question is what is the appropriate way to allow this to happen in mongoDB using node.js without deleting the document?
If I have a mongoose model (Marker) like this:
const mongoose = require('mongoose');
const markerSchema = new mongoose.Schema({
name: {
type: String,
},
description: {
type: String,
trim: true,
},
createdAt: {
type: Date,
default: Date.now(),
select: false,
},
expiresAt: {
type: Date
},
active: Boolean,
location: {
type: {
type: String,
enum: ['Point'],
required: true,
},
coordinates: {
type: [Number],
required: true,
},
},
});
markerSchema.pre('save', function (next) {
this.expiresAt = this.createdAt + 60 * 60 * 1000 * 6;
next();
});
const Marker = mongoose.model('Marker', tourSchema);
module.exports = Marker;
What would be the best way to update the markers active field to false after an arbitrary amount of time, say 6 hours?
I have researched Mongodb's TTL and I have also considered using the createdAt field to query for markers that are within the time frame of active using Marker.find({ expiresAt: {$gt: Date.now() }) but I do not know if this would be the best. Is there a way to run a function or middleware that periodically checks each document and sets the active field to false if the time frame is up? Just curious how you would approach this problem so querying Marker.find({ active: true }) would provide the data I am looking for.
Setting TTL in MongoDB runs a scheduled task every 60 seconds and will remove the document once expired. If you don't want to remove the document and mark it instead.
Run a CRON/Scheduled task every minute
Update the document matching your custom TTL criteria
Use the criteria in all select clause to ignore expired documents.
You can use node-cron to achieve your goal.
For example, rub a job every minute :
cron.schedule('* * * * *', function() {
console.log('running a task every minute');
});
More details and full usage example tutorials :
https://www.digitalocean.com/community/tutorials/nodejs-cron-jobs-by-examples
https://blog.logrocket.com/task-scheduling-or-cron-jobs-in-node-using-node-cron/

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

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"

mongoose default value equal to other value

For my project i've created an userSchema which simplified looks like the following:
var userSchema = new Schema({
_id: String,
screenname: {type: String, required: false, default: "equal _id"},
});
The user has an _id that is a string which also is his username.
Everything works so far until i tried to add an extra field screenname. What i want is when the user creates an account, his screenname equals the value of _id. Later he can adjust it but by default it should equal the value of _id. i've also tried :
screenname: {type: String, required: false, default: _id},
But than ofcourse _id is not defined.
How should i set the default value to equal another value ?
use the pre middleware explained here
userSchema.pre('save', function (next) {
this.screenname = this.get('_id'); // considering _id is input by client
next();
});
You can pass a function to default, following is a schema field excerpt:
username: {
type: String,
required: true,
// fix for missing usernames causing validation fail
default: function() {
const _t = this as any; // tslint:disable-line
return _t.name || _t.subEmail;
}
},

Storing a User Provided Date in Mongoose

So, I've got this schema:
var imageSchema = new Schema( {
caption: {type: String, required: true},
url: {type: String, required: true}
});
var EventSchema = new Schema({
name: {type: String, required: true},
date: {type: Date, required: true},
time: {type: String, required: true},
location: {type: String, required: true},
description: { type: String, required: false },
image: {type: String, required: false},
images: [imageSchema]
});
Requests are handled via locomotive.js, and the controller action for creating new records looks like this:
EventController.create = function() {
if(preScreen.screen.bind(this)("event", "create")) {
this.elements = modelHelper.loadValues.bind(this)();
this.saveMessage = "Save";
this.strings = strings;
if(this.req.method && this.req.method == "POST")
{
this._createEvent();
} else {
this.render();
}
} else {
this.redirect(this.urlFor({controller: "dashboard", action: "error"}));
}
};
This is a fairly standard action controller; mostly invoking an input view or or handling the _create when the received with a POST header.
the _createEvent function looks like this:
EventController._createEvent = function() {
if(!(this.elements)) this.elements = require('../templates/Event/elements')();
if(!(this.event)) this.event = new Event();
modelHelper.populate.bind(this)(this.elements, "event", function() {
modelHelper.save.bind(this)("event", this._confirm.bind(this), "create");
}.bind(this));
};
For my models I encapsulate all of the inputs in a template pattern. Rather than spend a lot of time on the framework around this (which I am working on releasing open source once I have finished doing small tweaks too) I will say that effectively the template contains one element for each path in the schema and provides some client-side details (error messages, labels etc.) these template objects are used by a modelHelper object which is fairly agnostic. Effectively what modelHelper.populate does is inspect the "type" property of each object within elements, and calls a handler for the appropriate input type.
the handler for date types is:
case "date" :
this[record][field.name] = strings.exists(this.param(field)) ?
strings.trim(this.param(field.name)) : null;
break;
although I've also tried strings.trim(Date.parse(this.param(field.name)) to get the UTC Timestamp from the user string.
I've been able to validate that the user entered date string does return a valid UTC stamp by using console.log within the date parser.
When the modelHelper.save() call is made it runs through these template objects, creates an associate array with the values picked up from the parsers and passes it to Record.save().
Most of this has been thoroughly tested and is being used in production however this is my first scenario where I am using dates other than date.now() as a default value.
What is the correct body for the date parser in order for mongodb/ the mongoose driver to push a date into a Date type?
Any string that JavaScript's Date.parse method can parse will work as the string is cast to a Date by Mongoose using this function which calls the Date constructor which uses Date.parse to parse strings.

Mongoose custom schema type date interval

I have many fields in my documents of type date intervals, such as this
{
publishDate:
{
start: {type: Date, required: true},
end: {type: Date, required: true}
}
}
To reduce duplication of the code and make it easier to maintain, how to create custom Mongoose type, for instance DateInterval, containing two fields:
start
end
and containing validator that makes sure both fields are filled out, and start is before end?
You can reuse schemas in mongoose.
var DateIntervalSchema = new Schema({
start: {type: Date, required: true},
end: {type: Date, required: true}
});
var SomeSchema = new Schema({
publishDate: [DateIntervalSchema],
// ... etc
});
You can also reference documents from other collections.
var SomeSchema = new Schema({
publishDate: {type: Schema.ObjectId, ref: 'DateInterval'}
});
//using populate
SomeModel.findOne({ someField: "value" })
.populate('publishDate') // <--
.exec(function (err, doc) {
if (err) ...
})
You'll want to develop a custom schema type. There are a number of plugins that do this already, one of which, for long numbers, can be found here: https://github.com/aheckmann/mongoose-long/blob/master/lib/index.js . This is a good basic example to follow.
For your purposes, then, you can create a DateInterval custom schema, casting it as type Date, and then use a validator to check start and end - http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate.
Since mongoose >=4.4 you can implement your custom schema type.
Documentation is not very clear, but you can follow this example.
You have to:
define your DateInterval custom object with toBSON() / toJSON() and toObject() prototype methods
define the DateIntervalType inherited from mongoose.SchemaType for handle the mongoose integration, and casting to DateInterval.
In this way you can achieve full control on memory (Mongoose model) and mongodb (raw's bson) data representation.

Resources