How do I search mongodb based off dates? - javascript

Building a rental listing application using MERN stack. My Listing model is below:
const listingShcema = new mongoose.Schema(
{
hostedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
numberOfGuests: {
type: String,
required: true,
},
numberOfRooms: {
type: String,
required: true,
},
numberOfBeds: {
type: String,
required: true,
},
numberOfBaths: {
type: String,
required: true,
},
price: {
type: String,
requried: true,
},
location: {
streetAddress: { type: String },
city: { type: String },
state: { type: String },
postalCode: { type: String },
},
bookedDates: [
{
startDate: Date,
endDate: Date,
},
],
imgUrls: [
{
type: String,
},
],
amenities: [
{
type: String,
},
],
},
{ timestamps: true }
);
Now it is fairly easy to run queries on everything given by the users search query except for the dates they want to rent out. The listing model keeps track of all bookedDates. I want to be able search the mongodb for Listings that do not have bookedDates that match the dates supplied by the users search query (showing available listings to the user). I can't think of a way to do this?? I figured it is easy keeping track of only the booked dates instead of taking away booked dates from an array of all available dates.

Doing this directly in the DB is kind of awkward, especially if you're only storing the startDate and endDate for each booking. For example, if someone books a listing from the 1st to the 5th - if another user is searching for the same listing from the 3rd to the 7th, it doesn't match the booking saved, but the listing wouldn't still be counted as available.
I'd suggest taking another look at your model and perhaps even separating out the booked dates to their own documents.
But, keeping with what you have, assuming you're not booking too far in the future, it might be worth storing the bookedDates as a flat array. So if we have a listing booked from the 1st to the 3rd, and the 6th to the 8th, your array would look like this:
bookedDates: [
'2021-01-01',
'2021-01-02',
'2021-01-03',
'2021-01-06',
'2021-01-07',
'2021-01-08'
]
Then, if someone searches for the listing between the 2nd and 4th, you'd again break down those dates into a flat array, and then you should be able to use the $nin operator (https://docs.mongodb.com/manual/reference/operator/query/nin/):
const desiredDates = [
'2021-01-02',
'2021-01-03',
'2021-01-04'
]
Listing.find({ bookedDates: { $nin: desiredDates } })
To quote the relevant part of the page:
If the field holds an array, then the $nin operator selects the documents whose field holds an array with no element equal to a value in the specified array (e.g. , , etc.).
This is obviously going to work best if you have another way to filter out the majority of your listings, so your not doing an array-array check for every listing in your database.
You'll also have to keep bookedDates up-to-date by removing past dates.
Another option is just to query your listings and do the date filtering at the application level, in which case, you can probably keep the startDate and endDate format that you have.
Update for flattening dates
Something like this should work. I just brute force it - people are generally only going to book a listing for a few days mostly, so your loop is going to be quite small. There are some checks in there if it's for one day, and to make sure the start is before the end, but that's about it.
As a method, you can call it whenever you want, and it'll split two dates into a flattened string array in yyyy-mm-dd format
function getFlattenedDatesAr(inputStart, inputEnd) {
// convert to dates and make sure that start is before end
let startDate = new Date(inputStart)
let endDate = new Date(inputEnd)
if(startDate > endDate) {
let temp = startDate;
startDate = endDate;
endDate = temp;
}
// get our dates in yyyy-mm-dd format
const startDateStr = startDate.toISOString().substr(0, 10)
const endDateStr = endDate.toISOString().substr(0, 10)
// check if they've only booked for one day
if(startDateStr === endDateStr) {
return [startDateStr];
return;
}
// fill our our dates array
const bookedDates = [startDateStr]
let currDate = startDate;
while(true) {
// NOTE: setDate returns a timestamp, not a Date
const nextDateTS = currDate.setDate(currDate.getDate() + 1);
// get our date string and add it to our bookedDates array
currDate = new Date(nextDateTS)
const currDateStr = currDate.toISOString().substr(0, 10);
bookedDates.push(currDateStr);
// if our string matches our end date, we're done
if(currDateStr === endDateStr) {
break
}
}
return bookedDates
}
// assume these are the dates sent, in yyyy-mm-dd format
let inputStart = '2021-01-01'
let inputEnd = '2021-01-05'
const datesAr = getFlattenedDatesAr(inputStart, inputEnd)
console.log(datesAr);

Related

Dynamically check for field names in mongoose schema

I have a mongoose schema with a field defined as follows
ActiveWeek: {type: String},
Week1ScheduleEntries: [{
PlayerName: String,
Slots: { type: Array, default:[]}
}],
Week2ScheduleEntries: [{
PlayerName: String,
Slots: { type: Array, default:[]}
}],
The active week will roll over every week on every Friday when the admin of my web app will hit a button. So if this week it is 1, the next week it will be 2
Is it possible in my backend node to check for this value 1 ,2 etc dynamically.
This is what i mean
Week(1,2,3...etc)ScheduleEntries
I dont know the right way to syntactically check this, if it is at all possible.
Hi you can try the below code
const selectedEntry = await schema.findOne(/* you filter */)
selectedEntry?.[`Week${selectedEntry.ActiveWeek}ScheduleEntries`]?.map(entry => {
// you functions
});
This will get your job done.

How can I reverse the order of items returned from Model.find({});

This command
Model.find({});
is used by Mongoose go return all the models of type Model. However whenever I display the array of JSON that it is returned it is in reverse order.
I could just loop through the array backwards but was curious if there is a way to have Mongoose return the array so that the last model I added would be the first returned.
Thanks.
You need to use sort on the published date. How i did
vObj
.find()
.sort({ publishedAt: "-1" })
.limit(50)
.then((videos) => {
//skip it
})
You need to add timestamp to your model.This is how i did.
var videoSchema = new mongoose.Schema({
title: { type: String, required: true, },
description: String,
imageurl:{type:String,required:true,},
publishedAt:{type:String,required:true,},
},{ timestamps: true });

Select documents by range of _id values in mongoose

I am creating an application that allows the user to create subtables from master tables, where he/she can specify a range of rows to be included in the subtable.
This is my Subtable model:
// Imports
const mongoose = require('mongoose');
const timestamps = require('mongoose-timestamp');
// Setup
const Schema = mongoose.Schema;
const ViewSchema = new Schema({
name: {
type: String,
required: true
},
table: {
type: mongoose.Types.ObjectId,
required: true,
ref: 'Table'
},
range: [
[
{
start: { type: mongoose.Types.ObjectId, ref: 'Row' },
end: { type: mongoose.Types.ObjectId, ref: 'Row' }
}
]
],
columns: {
type: Object,
required: true
}
});
ViewSchema.plugin(timestamps);
module.exports = mongoose.model('View', ViewSchema);
I currently store the starting row of the range and the ending row.
Now I wanted to know that if there is a way I can select only the documents that are inserted after the starting row and before the ending row so I don't have to pull 1000's of rows and filter them on the client side.
And if this is not possible, then I would love to know about any other solution.
Thanks a bunch!
A very naive solution to do this is using $gt and $lt operators, like this:
RowModel.find({ _id: { $gt: ObjectIdOfTheStart, $lt: ObjectIdOfTheEnd } });
So this will return all the rows which have an ObjectId greater than that of the start row and lower than that of the end row.
More on $gt and $lt.
Also as #the_mahasagar mentioned that you must use something like start and end dates, querying ObjectId does the same as well, as mentioned by Steve Ridout here.
Word of caution, I wouldn't recommend doing something like this. you should use some other kind of identifier for querying these rows.

Dynamoose - cannot query and where with 2 keys

I have opened a related issue on GitHub, but maybe someone here will be able to help quicker.
Summary:
ValidationException: Query key condition not supported
I need to find records in last (amount) seconds on a given location.
Pretty simple, but already related to other issues:
One and another one
WORKS:
Activity.query('locationId').eq(locationId).exec();
DOES NOT WORK:
Activity.query('locationId').eq(locationId).where('createdAt').ge(date).exec();
Code sample:
Schema
const Activity = (dynamoose: typeof dynamooseType) => dynamoose.model<ActivityType, {}>('Activity',
new Schema({
id: {
type: String,
default: () => {
return uuid();
},
hashKey: true,
},
userId: {
type: String,
},
locationId: {
type: String,
rangeKey: true,
index: {
global: true,
},
},
createdAt: { type: Number, rangeKey: true, required: true, default: Date.now },
action: {
type: Number,
},
}, {
expires: 60 * 60 * 24 * 30 * 3, // activity logs to expire after 3 months
}));
Code which executes the function
Funny part is that I found this as workaround proposed to be used until they merge PR giving ability to specify timestamps as keys, but unfortunately it does not work.
async getActivitiesInLastSeconds(locationId: string, timeoutInSeconds: number) {
const Activity = schema.Activity(this.dynamoose);
const date = moment().subtract(timeoutInSeconds, 'seconds').valueOf();
return await Activity.query('locationId').eq(locationId)
.where('createdAt').ge(date).exec();
}
I suspect createdAt is not a range key of your table / index. You need to either do .filter('createdAt').ge(date) or modify your table / index schema.
I'm pretty sure the problem is that when you specifying rangeKey: true on the createdAt property you are telling that to be used on the global index (I don't think that is the correct term). That range key will be linked to the id property.
I believe the easiest solution would be to change your locationId index to be something like the following:
index: {
global: true,
rangeKey: 'createdAt',
},
That way you are being very explicit about which index you want to set createdAt as the rangeKey for.
After making that change please remember to sync your changes with either your local DynamoDB server or the actual DynamoDB service, so that the changes get populated both in your code and on the database system.
Hopefully this helps! If it doesn't fix your problem please feel free to comment and I'll help you further.

Find objects created in last week in mongo

I have a collection which has a field called timestamp containing date object. I have this query:
db.articles.find({
timestamp:{
'$lte':new Date(),
'$gte': //Something to get the last week's date
}
})
Also if it is possible, Can I sort these returned documents by length of an array in this document. Here is the schema:
section: String,
title: String,
abstract: String,
url: String,
image: {
url: String,
caption: String
},
votes:{
up: [ObjectID],
down: [ObjectID]
},
comments:[ObjectID],
timestamp: Date
I want to sort the returned objects by size of difference of votes.up and votes.down. Right now I am sorting the returned objects in Javascript where this actually returns the data.
Seems the solution should look like
db.articles.find({
timestamp: {
$gte: new Date(new Date() - 7 * 60 * 60 * 24 * 1000)
}
});
it will return the previous week data i.e.,from sunday to saturday of previous week which is local where sunday is a starting day.
{
$match: {
createdAt: {
$gte: moment().day(-7).toDate(),
$lt: moment().startOf('week').toDate()
},
}
}
]);
I found a solution get the objects created in last week.
db.articles.find({timestamp:{'$lte':new Date(),'$gte':new Date(Date()-7)}})
This gets the work done. But I cant figure out how to sort the returned objects by the size of arrays.

Categories

Resources