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.
Related
I am building an education application and I am trying to add/update a field which is an array of objects with addToSet from a javascript array, and if the object already exists (matched with objectId) I want to update the already existing object's array (addToSet) and change another field of that same object.
My model looks like this (simplified):
const course = new Schema(
{
events: [
{
type: Schema.Types.ObjectId,
ref: 'event'
}
],
students: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'user'
},
status: {
type: String,
enum: ['notBooked', 'booked', 'attended', 'completed']
},
events: [
{
type: Schema.Types.ObjectId,
ref: 'event'
}
]
}
],
});
And ideally I would like to use an updateOne query to both addToSet to the course's list of events, while also updating the students list.
Currently I am using this code to accomplish my updates by first finding the course and then using javascript to update it, which works:
const studentsToAdd = this.attendees.map((attendee) => ({
user: attendee._id,
status: 'booked',
events: [this._id]
}));
const studentIds = studentsToAdd.map((student) => student.user);
const course = await courseModel.findById(this.course);
console.log(studentIds);
course.events.push(this._id);
course.students.forEach((student) => {
if (studentIds.some((s) => s.equals(student.user))) {
student.events.push(this._id);
student.status = 'booked';
studentsToAdd.splice(studentsToAdd.indexOf(studentsToAdd.find((s) => s.user.equals(student.user))), 1);
}
});
course.students.push(...studentsToAdd);
course.save();
However I am curious if it is possible to solve this using a single updateOne on the courseModel schema.
This is part of a mongoose middleware function, and "this" references an event object, which has its own _id, attendees of the event, and the course ID (named course).
The goal is to add the student object part of studentsToAdd to the students array of the course IF the student does not exist (signified by user being a reference by objectId), and if the student already exists then I want to add the eventId (this._id) to the events array for that particular student and set status for that student to "booked".
Is this possible? I have tried many iterations using $cond, $elemmatch, "students.$[]" and so forth but I am quite new to mongodb and am unsure how to go about this.
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);
I would query only a part of my document but without precising any value to retrieve, just to avoid to query the whole document. I have seen the different queries, even the where query, but seems all require a value to match with the document's properties,
for example:
here my Model.Schema:
const ScheduleSchema=new Schema({
month:{type: Number, required:true},
day:{type: Number, required:true},
hour:{
morning: {
time: [{
timePlot:String,
prospect:[{
type: documentType,
ref:"Prospect"
}]
}]
},
afternoon:{
time: [{
timePlot:String,
prospect:[{
type: documentType,
ref:"Prospect"
}]
}]
}
}
})
How would only fetch the afternoon section for example, however, the queries I have seen so far ask to me to provide some value to match with my path, so my question is how query for a subpath providing only the path instead of path:value. For example as following
Model.find({path: afternoon}, () => {...}
any hint would be great,
thanks
Specifying which parts of the document to return is done by providing a projection argument to find as the second parameter:
Model.find({path: afternoon}, 'path', () => {...}
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.
edited after #enRaiser's answer.
I have a sandbox mongoDB database with a single collection called "hotels", the document-schema of which looks like this:
var roomSchema = new mongoose.Schema({
type: String,
number: Number,
description: String,
photos: [ String ],
price: Number
});
var hotelSchema = new mongoose.Schema({
name: String,
stars: Number,
description: String,
photos: [ String ],
currency: String,
location: {
address: String,
coordinates: [ Number ] /* enforce later validation to have max of two numbers in the array */
},
rooms: [roomSchema],
reviews: [{
name: String,
id: String,
review: String,
rating: Number
}],
services: [ String ]
});
Now, I'd like to have two versions of schema for Hotel, one for a 'deep' data model and the other for a min model.
var hotelMinSchema = new mongoose.Schema({
name: String,
stars: Number,
location: {
address: String,
coordinates: [ Number ]
},
currency: String
})
module.exports = {
full: mongoose.model('hotel', hotelSchema),
min: mongoose.model('hotel', hotelMinSchema)
}
Aparently I'm not supposed to have two models for a collection.. not so sure. I get this error thrown.
OverwriteModelError: Cannot overwrite hotel model once compiled.
I think there should be a work-around for this. Any help or suggestion would be appreciated.
This is totally wrong way of developing any Database. even in MYSQL, I would not have think of this way of designing DB.
Firstly there are duplicate data. You alwayse have to take care of syncing them.
and 2nd, even in your full model there is duplication of comment. the comment info is present in both User ( i.e the commenter and the blogger)
Irrespective of the DB. when ever you think of solution you have to identify the real entities. here in your use case there are only two entity User and comment. So just make two model. not more.(in case of MYSQL, I would say just make two tables User table and comment table.)
Then set up a relation between them. for that in mongoose learn the how to make relation and how to populate that data based on relation. its just like setting up foreign key in MYSQL.
Sorry, I just found this out.
var hotelListPromise = Hotel.find({})
.select('name stars location currency')
.exec((err, hotelData) => {
// my callback stuff here
});