Mongoose conditional field value - node.js

I am working on a project with NodeJS and MongoDB and I am using Mongoose. In my database I am storing the companies with the opening and closing hours. I also have a field isOpen which is a boolean and I want to know if it's possible to dynamically change the value of isOpen in mongoDB according to current date and the the opening hours of the company.
{
openingHours {
opens: Number,
closes: Number
},
isOpen: Boolean // should depend on the current date and openinghours
}
PS: I saw that it was possible to put a function over the field required.

You could use virtual property to get that functionality.
var storeSchema = new Schema({
openingHours {
opens: Number,
closes: Number
},
});
storeSchema.virtual('isOpen').get(function () {
const currentTime = Date.now();
const currentHour = currentTime.getHours();
return this.openingHours.opens >= currentHour && currentHour < this.openingHours.closes;
});
More information about virtual property you can find in official documentation.

Related

Fetching timestamp from firestore Cloud Functions nodejs

I am an interface where I have declared lastLogin as Date.
LastLogin looks like this:
export interface User {
lastLogin: Date,
}
I am fetching this timestamp in my cloud function and I want it as a timestamp so that I can subtract it from the current Timestamp.
const mentor: User = mentorDoc.data() as User;
const login = mentor['lastLogin'];
const lastOpen = mentor['lastLogin'].valueOf();
const currentTime = new Date().getTime();
const diffMs = (currentTime - lastOpen);
const diffHrs = Math.floor((diffMs % 86400000) / 3600000); // hours
Last open shows like this :
063740174400.000000000
And when I use to like this :
const lastOpen = new Date (mentor['lastLogin'].valueOf());
Output:
undefined
Please change Date to firestore Timestamp both in the interface declaration as well as the collection field that holds the date in the firestore console
export interface User {
lastLogin: firebase.firestore.Timestamp,
}
You can now retrieve the date and also update it, this time simply by passing a new Date object.
// Read currently saved Timestamp
const ref = db.collection<User>('users').doc(`${uid}`).get().toPromise()
const saved_date = ref?.data().lastLogin
const current_date = new Date()
// if you just want the Date difference in ms after retrieving the timestamp from firestore.
const diff = current_date.getMilliseconds() - (saved_date.toDate()).getMilliseconds() // Timestamp -> Date (ms)
// Update Timestamp using a Javascript Date object
db.collection('..').doc('..').set({ lastLogin: current_date }, {merge:true})
// set creates the field while update works only if field already exists

Mongoose custom SchemaType toJSON/Transform

For an application I'm currently working on I need to store dates without a time. I've done this by creating a custom schema type that looks something like so:
var mongoose = require('mongoose');
/**
* Registers a new DateOnly type field. Extends the `Date` Schema Type
*/
function DateOnly(key, options) {
mongoose.SchemaTypes.Date.call(this, key, options, 'DateOnly');
}
DateOnly.prototype = Object.create(mongoose.SchemaTypes.Date.prototype);
DateOnly.prototype.cast = (originalValue) => {
try {
var value = originalValue;
if (typeof value === 'string' && !value.match(/^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))(T00:00:00.000Z)?$/)) {
throw new Error('Date is invalid');
} else if (typeof value === 'number') {
value = new Date(value);
}
if (value instanceof Date) {
value = new Date(value.getFullYear(), value.getMonth(), value.getDate());
}
return mongoose.Schema.Types.Date._cast(value);
} catch (err) {
throw new mongoose.SchemaType.CastError('date', originalValue, this.path);
}
};
mongoose.Schema.Types.DateOnly = DateOnly;
module.exports = DateOnly;
Which allows the model to accept date strings (ex: 2020-01-01) and date objects. Now this will store all dates at midnight UTC time, that way I still get all the advantages of them being stored as dates in mongodb.
Where my issue comes in is that when I'm returning one of these dates to the API it gets returned in full ISO format (ex: 2020-01-01T00:00:00.000Z), which will get converted into the local user's time zone. In my timezone, this date will show up as 1 day earlier than desired.
So my question is, how can I make it so that when document.toJSON is called the date is transformed? I know that what I want to be returning is date.toISOString().substring(0,10).
I've tried inheriting from the Date class, but I discovered it isn't compatible with how mongoose and the mongodb driver work.
I know I could write a method to put in the toJSON.transform options, but then I'd have to do this for every field and model that uses the type.
A solution for this was added in mongoose 5.9.0, and can be done like so:
DateOnly.set('transform', (val) => {
return /* transformed value */;
});
Source: https://github.com/Automattic/mongoose/issues/8403

Mongoose MongoDB in Node.js - Saving datetime in local timezone

I am using Mongoose in NodeJS project. I have this schema:
let InventorySchema = new Schema({
name: String,
tradable: {
type: Date,
default: () => {
return new Date().getTime()
}
}
}, {
versionKey: false
});
I live in Prague (GMT+01:00). When I insert "line" into my document, tradable is set automatically (because of default) to datetime without GMT. For example time now is in my city 16:51 but into database its saved as 15:51
How to save correct datetime? NodeJS Date giving me correct datetime when it is called normally.
EDIT: Using Date.now not helping! Same output
Use getTimezoneOffset() It will provide you the offset :
var date = new Date(); //ex 2019-01-18T16:26:44.982Z
var offset = - date.getTimezoneOffset() / 60; //in your case 1
And you add the offset to your date.

Mongoose date comparison

My application will allow user to create coupons.
Coupon will be valid in datefrom and dateto period.
The thing is that every coupon should be valid for selected days, not hours.
For example since Monday(2016-06-12) to Tuesday(2016-06-13), so two days.
How should I store dates on server side and then compare it using $gte clause in Mongoose?
Thank you :-)
{ "_id" : 1, "couponStartDate" : ISODate("2016-06-26T18:57:30.012Z") }
{ "_id" : 2, "couponStartDate" : ISODate("2016-06-26T18:57:35.012Z") }
var startDate = new Date(); // I am assuming this is gonna be provided
var validDate = startDate;
var parametricDayCount = 2;
validDate.setDate(validDate.getDate()+parametricDayCount);
CouponModel.find({couponStartDate: {$gte: startDate, $lte: validDate}}, function (err, docs) { ... });
You can store expiration time as UNIX timestamp. In your Mongoose model you can use expiration : { type: Number, required: true}
If you have user interface for creating coupons then you can configure your date picker to send time in UNIX timestamp.
Or If you are getting Date string then you can use var timestamp = new Date('Your_Date_String');
And for calculation of Days you can use Moment JS. Using this you can calculate start of the date using .startOf(); and end of date using .endOf();
Timestamp return from Moment JS can be used for Mongoose query like $gte : some_timestamp and $lte : some_timestamp
If you want to validate the coupon before it is persisted, you can create a max / min value for the date field:
See this sample from official mongoose documentation on DATE validation:
var s = new Schema({ dateto: { type: Date, max: Date('2014-01-01') })
var M = db.model('M', s)
var m = new M({ dateto: Date('2014-12-08') })
m.save(function (err) {
console.error(err) // validator error
m.dateto = Date('2013-12-31');
m.save() // success
})
Hint: use snake_case or camelCase for field names

How to automatically generate custom id's in Mongoose?

I'd like to manage my own _id's through Mongoose/MongoDB - as the default ones are pretty long and consume bandwidth.
The difficulty is that I need to create them (say, by incrementing a counter), but I need to do this in a concurrent environment (Node.JS). Is there a simple example I could follow that creates a schema, with a custom _id and a static method (or anything better) that automatically generates the next unique _id, whenever a new document is created?
You could use Mongo's findAndModify() to generate sequential values. Below is an example of this:
// (assuming db is a reference to a MongoDB database)
var counters = db.collection('counters');
var query = {'name': 'counterName'};
var order = [['_id','asc']];
var inc = {$inc:{'next':1}};
var options = {new: true, upsert: true};
counters.findAndModify(query, order, inc, options, function(err, doc) {
if(err) {
callback(err);
return;
}
var id = doc.next;
callback(null, id);
});
Although generating sequential IDs looks pretty on applications keep in mind that there are some drawbacks to them (e.g. when you need to split your database geographically) which is why Mongo uses the long pseudo-random keys that it does.
As Chad briefly touched on, Mongo implements a uuid system for you, taking into account the timestamp, network address, and machine name, plus an autoincrementing 2 digit counter (in the event that multiple entries with the same timestamp occur). This schema is used to allow distributed databases (ie, running different database instances on different machines) while ensuring that each entry will still have a unique identifier (because the machine name section will be different).
Trying to role out your own schema would likely greatly limit the scalability that mongo provides.
This should work
import { randomString } from '#/helpers'
const taskSchema = new mongoose.Schema({
_id: {
type: String,
unique: true,
default: randomString
},
title: String,
...
})
Random string function
// helpers
export const randomString = (length?: number) => {
let result = ''
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
const charactersLength = characters.length
const n = length || 15
for (let i = 0; i < n; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
Tested result
{ "_id": "EIa9W2J5mY2lMDY", ... }

Resources