In Meteor JS I want to find users whose birthday is today.I have this piece of code that runs fine on my computer (locally) but it fails in production :
let today = new Date()
let users = Meteor.users.find({
"status.lastLogin": { $not: { $eq: null } },
$expr: {
$and: [
{
$eq: [
{
$dayOfMonth: {
date: "$profile.birthdate",
timezone: "Europe/Paris",
},
},
today.getDate(),
],
},
{
$eq: [
{
$month: {
date: "$profile.birthdate",
timezone: "Europe/Paris",
},
},
today.getMonth() + 1,
],
},
],
},
})
My server is hosted on Galaxy and the DB on mongodb.com I checked the profile.birthdate type and it is Date on mongodb.com The error is :
MongoError: can’t convert from BSON type string to Date\n at Connection. (/app/bundle/programs/server/npm/node_modules/meteor/npm-mongo/node_modules/mongodb/lib/core/connection/pool.js:450:61)\n at Connection.emit (events.js:311:20)\n at Connection.EventEmitter.emit (domain.js:482:12)\n at processMessage (/app/bundle/programs/server/npm/node_modules/meteor/npm-mongo/node_modules/mongodb/lib/core/connection/connection.js:384:10)\n at TLSSocket.
Does anyone know why this is happening and how can I fix it?
Edit: By following @Jankapunkt advice to use aggregate and by reading this post, I was able to write a better (I think...) query and now it is working. This is the new code:
const today = new Date()
let users = Meteor.users.aggregate(
{
$project: {
status: "$status",
roles: "$roles",
month: {
$month: {
date: "$profile.birthdate",
timezone: "Europe/Paris",
},
},
day: {
$dayOfMonth: {
date: "$profile.birthdate",
timezone: "Europe/Paris",
},
},
},
},
{
$match: {
"status.lastLogin": { $ne: null },
roles: "candidate",
month: today.getMonth() + 1,
day: today.getDate(),
},
}
)
There are several issues here, I'd like to address:
$expr
is usually only required in rare cases or when matching against a regular expression.
$dayOfMonth
is an aggregate operator and not available in basic queries, but there are packages available
Meteor has builtin Date support through EJSON, which extends BSON by custom types (it abstracts the type conversion away for you):
Meteor.publish('allbirthdays', function () {
const today = new Date()
// correct timezone here
return Meteor.users.find({ '$profile.birthdate': today })
}
No need to convert Date to some mongo operators etc.
$and
is a contradiction if differnt values are both required for the same field (birthdate
can never be today and today in a month), did you intend to use $or
?
{ $not: { $eq: null } }
can be written as { $ne: null }
Always disable the services
field if you publish users! Services contains the (hashed) password and other oauth providers, including resume token, which could lead to serious security issues:
The above methods allow only an exact Date matches, because MongoDB provides Date-specific query only through aggregate
.
Therefore, your options are:
A) Use the aggregate package to build $expr
for $month
and $dayOfMonth
as in your example code
B) Create the birthday only as locale field (which makes it a String
type):
export const getDate = () => {
// todo apply locale, timezone etc.
return new Date().toLocaleString(undefined, {
day: 'numeric', month: 'long'
})
}
and save it in the user's collection as a separate field (e.g. birthDay
):
Meteor.users.update(userId, {
$set: {
'$profile.birthDay': getDate() // "June 3"
}
})
ans query only for this day:
Meteor.publish('allbirthdays', function () {
const today = getDate()
return Meteor.users.find({ '$profile.birthDay': today })
}
Number
types in separate fields:const today = new Date()
Meteor.users.update(userId, {
$set: {
'$profile.birthDay': today.getDate() // 3
'$profile.birthMon': today.getMonth() // 6
}
})
ans query only for this day:
Meteor.publish('allbirthdays', function () {
const today = new Date()
return Meteor.users.find({
'$profile.birthDay': today.getDate()
'$profile.birthMon': today.getMonth()
})
})