I am working on a project that uses Mongo/Mongoose/NodeJS.
I have a requirement for a relatively complex constraint on the data in the db and I am not sure if it is even possible to implement.
My schema is as follows:
const schema = new Schema(
{
id: ObjectId,
date: {
type: Date,
required: true,
unique: true,
},
eventType: {
type: String,
enum: ['GROUP_STAGE', 'ROUND_OF_16', 'QUARTER_FINAL', 'SEMI_FINAL', 'FINAL'],
required: true,
},
},
);
The required logic is:
An eventType can only exist once per calendar year, unless the eventType is 'GROUP_STAGE' which can exist multiple times in a calendar year.
I'm pretty inexperienced with Mongo/Mongoose, however I'm getting the feeling this restriction may not be possible.
I can put the logic in the NodeJS layer before insert/update if that is the only way to do it, however that feels a bit hacky and brittle.
If the answer to this requirement is "it can't be done" then I would still be grateful for the confirmation.
EDIT - Solution by following the advice in the accepted answer
const schema = new Schema(
{
id: ObjectId,
date: {
type: Date,
required: true,
unique: true,
},
eventType: {
type: String,
enum: ['GROUP_STAGE', 'ROUND_OF_16', 'QUARTER_FINAL', 'SEMI_FINAL', 'FINAL'],
required: true,
},
},
);
const getFindDuplicateEventTypeInASingleYearQuery = (values) => {
const { id, eventType, year } = values;
const query = {
eventType,
date: {
$gte: new Date(`${year}-01-01T00:00:00.000Z`),
$lt: new Date(`${year}-12-31T23:59:59.999Z`),
},
};
// If an id is supplied, exclude it from the query
// This is to prevent false flags when updating an object
if (id) {
query._id = { $ne: new mongoose.Types.ObjectId(id) };
}
return query;
};
const checkForInvalidDuplicateEventTypeInYear = async (queryValues, model) => {
if (queryValues.eventType !== 'GROUP_STAGE') {
const query = getFindDuplicateEventTypesInASingleYearQuery(queryValues);
const count = await model.countDocuments(query);
if (count > 0) {
return new Error(`There is already a fixture with the eventType ${queryValues.eventType} in ${queryValues.year}`);
}
}
return null;
};
async function handlePreSave(next) {
next(
await checkForInvalidDuplicateEventTypeInYear(
{
eventType: this.eventType,
year: this.date.getFullYear(),
},
this.constructor,
),
);
}
async function handlePreFindOneAndUpdate(next) {
const { eventType, date } = this.getUpdate();
next(
await checkForInvalidDuplicateEventTypeInYear(
{
id: this.getQuery()._id,
eventType,
year: date.split('-')[0],
},
this.model,
),
);
}
schema.pre('save', handlePreSave);
schema.pre('findOneAndUpdate', handlePreFindOneAndUpdate);
module.exports = mongoose.model('Fixture', schema);