javascript - Mongoose $gte 日期搜索返回该日期之前的文档
问题描述
我有一个 Mongoose 模式/模型,其属性completedSetup
是一种Date
类型。
项目回购:https ://github.com/rmgreenstreet/custom-forms
const mongoose = require('mongoose');
const passportLocalMongoose = require('passport-local-mongoose');
const Schema = mongoose.Schema;
const Location = require('./location');
const Response = require('./response');
const sgMail = require('@sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const crypto = require('crypto');
const forbiddenWords = ['realtor', 'realty', 'realestate', 'agent', 'broker', 'admin'];
const userSchema = new Schema({
firstname: {
type: String,
required: true
},
lastname: {
type: String,
required: true
},
username: String,
personalEmail:{
type:String,
required:true,
unique:true
},
role:{
type:String,
default:'User'
},
isCompanyAdmin: {
type:Boolean,
default: false
},
company: {
type:Schema.Types.ObjectId,
ref: 'Company'
},
location: {
type:Schema.Types.ObjectId,
ref: 'Location'
},
image: {
url: {
type:String,
default:'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png'
},
public_id: String
},
isExpedited: {
type:Boolean,
default:false
},
isHidden: {
type:Boolean,
default:false
},
formAccessToken: {
type: String,
default: crypto.randomBytes(16).toString('hex')
},
completedSetup: Date,
responses: [
{
type:Schema.Types.ObjectId,
ref:'Response'
}
],
createAccountToken : {
type: String,
default: crypto.randomBytes(16).toString('hex')
},
resetPasswordToken : String,
resetPasswordExpires: Date,
created:{
type:Date,
default:Date.now()
}
});
userSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model('User',userSchema);
查询人createdDate
:
let beginDate = new Date();
beginDate.setMonth(beginDate.getMonth() - 6);
if (req.body.beginDate) {
beginDate = new Date(req.body.beginDate);
}
let endDate = new Date();
if (req.body.endDate) {
endDate = new Date(req.body.endDate);
}
const recentSetups = await User.find({completedSetup: {$gt: beginDate, $lte: endDate}});
这将返回所有User
s,而不仅仅是带有completedSetup
betweenbeginDate
和的那些endDate
。
奇怪的是,相同的查询在其他模式/模型上正确返回,但它们的日期设置不同。
在某些模型上,我有一个created
默认设置为 的属性,该属性Date.now()
在创建时设置,并且查询会返回这些属性。
然而,种子数据completedSetup
使用pickaADate
我定义的函数来选择前一年某个时间的日期,或者从当年到当月的日期(这是一个仍在开发中的投资组合项目):
const fs = require('fs');
const faker = require('faker');
const crypto = require('crypto');
const Company = require('./models/company');
const Location = require('./models/location');
const User = require('./models/user');
const Form = require('./models/form');
const Question = require('./models/question');
const Response = require('./models/response');
const sampleImages = fs.readdirSync('./public/images/seeds');
function flipACoin() {
const yesOrNo = Math.floor(Math.random() *2);
// console.log(yesOrNo);
return yesOrNo;
}
async function pickADate() {
return new Promise((resolve, reject) => {
try {
const today = new Date();
const day = Math.ceil(Math.random() * 27);
// const thisOrLastYear = flipACoin();
const month = Math.ceil(Math.random() * today.getMonth())
const returnDate = new Date(today.getFullYear() - flipACoin(),month,day);
resolve(returnDate);
return;
} catch (err) {
console.log(`Error creating random date: ${err.message}`);
reject(Date.now());
return;
}
});
};
async function seedDefaultQuestions() {
try {
console.log('clearing all default questions from database')
await Question.deleteMany({});
} catch (err) {
console.log(err.message);
}
try {
console.log('adding default questions to database')
const defaultQuestionsJSON = await JSON.parse(await fs.readFileSync('./private/defaultQuestions.json'));
for (let question of defaultQuestionsJSON) {
// console.log(question);
await Question.create(question);
}
console.log(`${defaultQuestionsJSON.length} default questions added to database`);
} catch (err) {
console.log(err.message);
}
};
async function clearDatabase() {
console.log('Clearing database \n Clearing Companies');
await Company.deleteMany({});
console.log('All Companies deleted \n Clearing Locations');
await Location.deleteMany({});
console.log('All Locations deleted \n Clearing Users');
await User.deleteMany({role: {$ne:'owner'}});
console.log('All Users deleted \n Clearing Forms');
await Form.deleteMany({});
console.log('All forms deleted \n Clearing responses');
await Response.deleteMany({});
console.log('Database cleared');
};
async function seedDatabase() {
// const companyCount = Math.ceil(Math.random() * 200);
const companyCount = 10;
const defaultQuestions = await Question.find({isDefault:true});
async function createLocations(companyId) {
const locationCount = Math.ceil(Math.random() * 5);
let locationsArr = [];
for (let i = 0; i < locationCount; i++) {
let isPrimary = false;
if (i=== 0) {
isPrimary = true;
}
const randomImageIndex = Math.ceil(Math.random() * sampleImages.length);
const newLocation = await Location.create({
primary: isPrimary,
officeNumber: Math.ceil(Math.random() * 1000).toString(),
name: faker.company.companyName(),
phone: faker.phone.phoneNumber(),
fax: faker.phone.phoneNumber(),
address: {
streetNumber: Math.random(Math.ceil() * 1000),
streetName: faker.address.streetName(),
secondary: `Ste ${faker.random.alphaNumeric()}`,
city: faker.address.city(),
state: faker.address.stateAbbr(),
postal: faker.address.zipCode(),
country: 'USA'
},
website: faker.internet.url(),
images: [
{
secure_url:`/images/seeds/${sampleImages[randomImageIndex]}`
}
],
company: companyId,
created: await pickADate()
});
await newLocation.save();
newLocation.contacts = await createUsers(newLocation._id, companyId, 'Admin', (Math.ceil(Math.random() * 5)));
await newLocation.save();
console.log(`Location ${newLocation.name} created with ${newLocation.contacts.length} contacts`)
await createUsers(newLocation._id, companyId, 'User', (Math.ceil(Math.random() * 30)));
locationsArr.push(newLocation._id);
await newLocation.addDefaultForm();
}
return locationsArr;
};
async function createUsers(locationId, companyId, role, count) {
let contactsArr = [];
for (let i = 0; i < count; i++) {
const newFirstName = await faker.name.firstName();
const newLastName = await faker.name.lastName();
let newUser;
try {
newUser = await User.register({
firstname: newFirstName,
lastname: newLastName,
username: newFirstName+newLastName,
personalEmail: newFirstName+newLastName+'@test.com',
role: role,
company: companyId,
location: locationId,
formAccessToken: crypto.randomBytes(16).toString('hex'),
createAccountToken: crypto.randomBytes(16).toString('hex'),
created: await pickADate()
},'password');
} catch (err) {
if (err.message.includes('UserExistsError')) {
continue;
}
}
if(role === 'User');{
if(flipACoin()) {
newUser.responses.push(await createResponse(newUser));
await newUser.save();
} else {
continue;
}
if(flipACoin()) {
newUser.completedSetup = await pickADate();
await newUser.save();
} else {
continue;
}
};
contactsArr.push(newUser._id);
console.log(`${role} ${newUser.firstname} ${newUser.lastname} created`);
};
return contactsArr;
};
async function createResponse(user) {
return new Promise(async (resolve, reject) => {
console.log(`Creating a response for ${user.firstname}`)
const makeString = (charLimit) => {
let str = faker.lorem.paragraph()
if (str.length > charLimit) {
str = str.slice(0, charLimit - 1)
}
return str
}
let response = await Response.create({owner:user._id, created:await pickADate()});
try {
for (let question of defaultQuestions) {
const answer = {
questionId: question._id
}
if(question.inputType == 'Checkbox') {
answer.value = true;
}
if(question.inputType == 'Email') {
answer.value = faker.internet.email();
}
if(question.inputType == 'File') {
continue;
}
if(question.inputType == 'Image') {
continue;
}
if(question.inputType == 'Number') {
answer.value = Math.ceil(Math.random() * 99);
}
if(question.inputType == 'Password') {
answer.value = 'Pa55w0rd123';
}
if(question.inputType == 'Radio') {
question.value = 'No';
}
if(question.inputType == 'Select') {
question.value = "At&t";
}
if(question.inputType == 'Tel') {
answer.value = faker.phone.phoneNumber();
}
if (question.inputType == 'Text') {
if(question.maxLength) {
answer.value = makeString(question.maxLength);
} else {
answer.value = faker.lorem.words()
}
}
if(question.inputType == 'Textarea') {
answer.value = faker.lorem.paragraph();
}
if(question.inputType == 'Url') {
answer.value = faker.internet.url();
}
response.answers.push(answer);
}
await response.save();
resolve(response._id);
return;
} catch (err) {
console.log(`Error creating random response: ${err.message}.`);
reject(response);
return;
}
});
}
console.log(`Creating ${companyCount} companies`)
for(let i = 0; i < companyCount; i++) {
const newCompany = await Company.create({
name: faker.company.companyName(),
created: await pickADate()
});
newCompany.locations = await createLocations(newCompany._id);
await newCompany.save();
console.log(`${newCompany.name} created with ${newCompany.locations.length} locations`)
}
console.log('database seeded')
};
module.exports = {seedDatabase, clearDatabase, seedDefaultQuestions};
根据MDN,我认为问题在于该函数生成从去年年初到当前日期的随机日期,但老实说,我看不出new Date(yyyy
(整数),monthIndex,day)
与创建的日期有何不同Date.now()
:https ://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date在“个人日期和时间组件值”下
这是用户数据在 MongoDB 中的外观,completedSetup
属性是最后一个,并且看起来格式正确:
最后,这里是一些查询返回的日志(全部达到字符限制):
[
{
image: {
url: 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png'
},
role: 'Admin',
isCompanyAdmin: false,
isExpedited: false,
isHidden: false,
formAccessToken: 'de5459c2cbf3ca1cbdb0a5daceb6ab61',
responses: [ 5e8b71b7e75a0726242637b9 ],
createAccountToken: 'a826054b8055243c52247a656eed9340',
created: 2020-03-04T06:00:00.000Z,
_id: 5e8b71b6e75a0726242637b8,
firstname: 'Larue',
lastname: 'Armstrong',
username: 'LarueArmstrong',
personalEmail: 'LarueArmstrong@test.com',
company: 5e8b71abe75a072624263688,
location: 5e8b71b6e75a0726242637b5,
__v: 1,
completedSetup: 2020-03-07T06:00:00.000Z
},
{
image: {
url: 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png'
},
role: 'User',
isCompanyAdmin: false,
isExpedited: false,
isHidden: false,
formAccessToken: '7bf9d040c0009691191f3122c14d3d51',
responses: [ 5e8b71c2e75a07262426392a ],
createAccountToken: 'b284c3f43fad0081f967f06926ee1d6d',
created: 2019-11-25T06:00:00.000Z,
_id: 5e8b71c1e75a072624263929,
firstname: 'Lance',
lastname: 'Wolff',
username: 'LanceWolff',
personalEmail: 'LanceWolff@test.com',
company: 5e8b71bfe75a0726242638dd,
location: 5e8b71bfe75a0726242638de,
__v: 1,
completedSetup: 2020-03-02T06:00:00.000Z
},
{
image: {
url: 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png'
},
role: 'User',
isCompanyAdmin: false,
isExpedited: false,
isHidden: false,
formAccessToken: '319cd6d626a48617c012be6c25fe66a8',
responses: [ 5e8b71c5e75a072624263977 ],
createAccountToken: '43e9161a30f7ebd672315c761da9e3d7',
created: 2019-06-03T05:00:00.000Z,
_id: 5e8b71c5e75a072624263976,
firstname: 'Kailey',
lastname: 'Ruecker',
username: 'KaileyRuecker',
personalEmail: 'KaileyRuecker@test.com',
company: 5e8b71bfe75a0726242638dd,
location: 5e8b71bfe75a0726242638de,
__v: 1,
completedSetup: 2020-02-23T06:00:00.000Z
},
{
image: {
url: 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png'
},
role: 'User',
isCompanyAdmin: false,
isExpedited: false,
isHidden: false,
formAccessToken: 'ccb5fc2c6ff671a8cb5bdcf432cbcb1b',
responses: [ 5e8b71c7e75a0726242639bd ],
createAccountToken: '8d3aa2ecd6e0da797d8cbf9846c78c9b',
created: 2019-04-20T05:00:00.000Z,
_id: 5e8b71c6e75a0726242639bc,
firstname: 'Skyla',
lastname: 'Dicki',
username: 'SkylaDicki',
personalEmail: 'SkylaDicki@test.com',
company: 5e8b71bfe75a0726242638dd,
location: 5e8b71bfe75a0726242638de,
__v: 1,
completedSetup: 2019-10-22T05:00:00.000Z
},
{
image: {
url: 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png'
},
role: 'User',
isCompanyAdmin: false,
isExpedited: false,
isHidden: false,
formAccessToken: '0641d2cf09459724ffea35d5cc96e2f1',
responses: [ 5e8b71c7e75a0726242639e0 ],
createAccountToken: '0f5e1bfd23df18835378c314efbcb206',
created: 2019-12-02T06:00:00.000Z,
_id: 5e8b71c7e75a0726242639df,
firstname: 'Rasheed',
lastname: 'Walsh',
username: 'RasheedWalsh',
personalEmail: 'RasheedWalsh@test.com',
company: 5e8b71bfe75a0726242638dd,
location: 5e8b71bfe75a0726242638de,
__v: 1,
completedSetup: 2020-02-16T06:00:00.000Z
},
{
image: {
url: 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png'
},
role: 'User',
isCompanyAdmin: false,
isExpedited: false,
isHidden: false,
formAccessToken: '7f584ded07bfe5b20e95ef72ed2a7749',
responses: [ 5e8b71cae75a072624263a2b ],
createAccountToken: '0934cd5e7ccb75d476ecc23624e491f1',
created: 2020-02-27T06:00:00.000Z,
_id: 5e8b71cae75a072624263a2a,
firstname: 'Leanna',
lastname: 'Kuphal',
username: 'LeannaKuphal',
personalEmail: 'LeannaKuphal@test.com',
company: 5e8b71bfe75a0726242638dd,
location: 5e8b71bfe75a0726242638de,
__v: 1,
completedSetup: 2019-11-02T05:00:00.000Z
}
]
在完整列表(大约 60 多个结果)中,有几个是 2019 年 11 月之前的,就像这个片段中的那个来自 2019 年 10 月。
解决方案
像往常一样,现在我发现了这个问题,我感到很愚蠢。
问题出在getPoints
函数中。
它将接收到的每个子数组中的每个项目都视为一个点,并为它创建一个点。所以如果graphDatasets
看起来像:
[
['Invitations','Completions','Setups'],
[/* actual points data */]
]
并且点数数据有 7 个月的价值,它在points
数组中增加了 3 个点,扩展了点数。
我不得不完全重写结构graphDatasets
并相应地更新renderCanvas
函数:
/* Don't know why I didn't have this as arrays of objects before, it's so much simpler */
const graphDatasets = [
[
{label:'Invitations',payload:recentInvitations,searchProperty:'created'},
{label:'Completions',payload:recentCompletions,searchProperty:'created'},
{label:'Setups',payload:recentSetups,searchProperty:'completedSetup'}
],
[
{label:'Companies',payload:recentCompanies,searchProperty:'created'},
{label:'Locations',payload:recentLocations,searchProperty:'created'}
]
];
//converts full object data from server into {month: String, count: Number} objects
//Add searchProperty argument
function getPoints(items, searchProperty) {
let points = [];
//access the data to be turned into points using the value at the .payload property
items.payload.forEach((item) => {
/* find the date to compare against using the searchProperty provided */
const workingDate = new Date(item[searchProperty])
const monthYear = `${workingDate.getMonth() + 1}/${workingDate.getFullYear()}`;
let existingPoint = points.find(point => point.x === monthYear);
if (existingPoint) {
existingPoint.y ++;
} else {
points.push({x:monthYear, y: 1});
}
});
return points;
};
// Returns the max Y value in our data list
function getMaxY(data) {
var max = 0;
for(var i = 0; i < data.length; i ++) {
if(data[i].y > max) {
max = data[i].y;
}
}
max += 10 - max % 10;
return max;
}
/* Removes objects from combined data where month matches, in order to draw only one copy of that
month on the X axis */
function removeDuplicates(originalArray, objKey) {
var trimmedArray = [];
var values = [];
var value;
for(var i = 0; i < originalArray.length; i++) {
value = originalArray[i][objKey];
if(values.indexOf(value) === -1) {
trimmedArray.push(originalArray[i]);
values.push(value);
}
}
return trimmedArray;
};
/* compare two arrays and if there are any missing months in either array, add them with a y value of 0, then sortby month/year */
function equalize(arr1, arr2) {
let newArr = arr2.reduce(function (result, obj2) {
if (arr1.some(obj1 => obj1['x'] === obj2['x'])) {
return result;
}
return [...result, {'x' : obj2['x'], 'y':0}];
}, arr1);
newArr.sort(function (a, b) {
a = a.x.split('/');
b = b.x.split('/')
return new Date(a[1], a[0], 1) - new Date(b[1], b[0], 1)
});
return newArr;
};
function renderCanvas(canvas, data) {
console.log('drawing on canvas');
const colors = ['indigo','blue','green','orange','purple','teal','fuschia'];
if(canvas.getContext) {
var xPadding = 30;
var yPadding = 30;
var xLength = canvas.width - yPadding;
var yLength = canvas.height - xPadding;
var pointsArr = [];
data.forEach(function (obj) {
pointsArr.push(getPoints(obj, obj.searchProperty));
});
for (let i = 0; i < pointsArr.length -1 ; i++) {
pointsArr[i] = equalize(pointsArr[i], pointsArr[i+1]);
};
var combinedData = Array.prototype.concat.apply([], pointsArr);
combinedData.sort(function(a,b) {
return new Date(a.created) - new Date(b.created)
});
var filteredPoints = removeDuplicates(combinedData, 'x');
/* cuts X axis into a number of sections double the number of points */
var xSpacing = xLength / (filteredPoints.length * 2);
var yMax = getMaxY(combinedData);
var ctx = canvas.getContext('2d');
ctx.font = 'italic 8pt sans-serif';
ctx.beginPath();
ctx.lineWidth = 6;
ctx.moveTo(yPadding,0);
ctx.lineTo(yPadding,yLength);
ctx.lineTo(canvas.width,yLength);
ctx.stroke();
ctx.closePath();
// Return the y pixel for a graph point
function getYPixel(val) {
return yLength - ((yLength / yMax) * (val));
}
// Return the y pixel for a graph point
function getXPixel(val) {
return ((xSpacing + yPadding) + (xSpacing * (2 * val)))
}
function drawLine(points, color, legendVal) {
/* move one xSpacing out from y Axis */
ctx.moveTo(yPadding + xSpacing, getYPixel(points[0].y));
ctx.beginPath();
ctx.fillStyle=color;
ctx.strokeStyle=color;
points.forEach((point) => {
const x = getXPixel(points.indexOf(point));
const y = (getYPixel(point.y)) ;
ctx.lineWidth = 2;
ctx.lineTo(x,y);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.arc(x,y,5,0,360,false);
ctx.fillText(point.y, x + 2, y - 15);
ctx.closePath();
ctx.fill();
ctx.moveTo(x,y);
});
/* will need to update this (and other stuff) but use the label property of the data to label the line on the graph */
ctx.moveTo(canvas.width - yPadding, getYPixel(points[points.length-1].y));
ctx.fillText(legendVal, canvas.width - 20, getYPixel(points[points.length-1].y));
}
// Draw the X value texts
for(var i = 0; i < filteredPoints.length; i ++) {
if (i===0) {
ctx.fillText(filteredPoints[i].x, yPadding, yLength +20);
}else {
ctx.fillText(filteredPoints[i].x, (yPadding) + (xSpacing * (2 * i)), yLength + 20);
}
}
// Draw the Y value texts
ctx.textAlign = "right"
ctx.textBaseline = "middle";
for(var i = 0; i <= yMax; i += 10) {
if (i === yMax) {
ctx.fillText(i, xPadding - 10, getYPixel(i-1));
} else {
ctx.fillText(i, xPadding - 10, getYPixel(i));
}
};
pointsArr.forEach(async function (points) {
drawLine(points, colors[pointsArr.indexOf(points)], data[pointsArr.indexOf(points)].label);
});
}
};
NodeList.prototype.indexOf = Array.prototype.indexOf
canvases.forEach((canvas) => {
renderCanvas(
canvas,
graphDatasets[canvases.indexOf(canvas)]
);
});
推荐阅读
- c# - 从单点/坐标的角度求四角多边形的“左上/右上/左下/右下”坐标
- html - 原子设计模式 - 业务逻辑
- android - 如何从 Firebase 获取数据并将其显示在 recyclerview 中?
- python - Python 使用 Selenium 的 WebDriver window_handles 在选项卡之间切换 - 仅返回父窗口
- xamarin - 如何下载 Base64 格式的 Xamarin.Forms 文件?
- python - 网络抓取 - 我得到标签但没有值
- excel - 如何在网站上设置 ComboBox 的值?
- nuxt.js - 如何在 Nuxt 中加载静态 HTML 页面(避免 Nuxt 请求)
- windbg - 获取执行程序的详细版本信息
- matlab - matlab中具有动态维度的矩阵