How to specify that at least one field is required out of three in total? - mongodb

In my MongoDB model, there are 3 fields in total. Any document added to this collection must contain at least 1 of those 3 fields.
How can this be specified in the validation staged?

You can enum validation constraint which collection creation as below:
db.createCollection("jobs", {
validator: {
$jsonSchema: {
bsonType: "object",
required: [ "status" ],
properties: {
status: {
enum: [ "Done", "Failed", "Initial" ],
description: "can only be one of the enum values and is required"
},
}
}
}
})
From the docs
Mongoose has several inbuilt validators. Strings have enum as one of the validators. So enum creates a validator and checks if the value is given in an array. E.g:
var userSchema = new mongooseSchema({
status: {
type: String,
enum : ['Done','Failed', 'Initial'],
default: 'Initial'
},
})
You can use custom validator to check if we have one of 3 keys in the object
const testSchema = mongoose.Schema({
field1: {
type: String,
validate: {
validator: function(v) {
if (this.field2 == undefined && this.field3 == undefined) {
return true;
}
return false;
},
},
},
field2: {
type: String,
validate: {
validator: function(v) {
if (this.field1 == undefined && this.field3 == undefined) {
return true;
}
return false;
},
},
},
field3: {
type: String,
validate: {
validator: function(v) {
if (this.field2 == undefined && this.field1 == undefined) {
return true;
}
return false;
},
},
},
});

Related

Fastify validate schema with yup - schema.validateSync is not a function

From the Fastify documentation in the section titled Using other validation libraries I'm trying to get yup to validate my schema but I keep getting schema.validateSync is not a function and I don't know why??
I want the schema to still be valid for creating the swagger document but I want to use yup to give me the validation I need.
const yup = require("yup");
const yupOptions = {
strict: false,
abortEarly: false,
stripUnknown: true,
recursive: true,
};
async function isUsernameAvailable(fastify: any, _options: Object) {
const users = fastify.mongo.db.collection("users");
fastify.get(
"/api/v1/onboarding/isUsernameAvailable/:username",
{
schema: {
params: {
type: "object",
properties: {
username: { type: "string", maxLength: 12, minLength: 1 },
},
required: ["username"],
},
response: {
200: {
type: "object",
properties: {
available: {
type: "boolean",
description: "Returns true if username is available",
},
},
},
},
},
validatorCompiler: ({ schema, method, url, httpPart }: any) => {
return function (data: any) {
try {
const result = schema.validateSync(data, yupOptions);
return { value: result };
} catch (e) {
return { error: e };
}
};
},
},
async (request: any, _reply: any) => {
const { username } = request.params;
const foundNUsernames = await users.countDocuments(
{ username },
{ limit: 1 }
);
const available: boolean = foundNUsernames === 0;
return { available };
}
);
}
export { isUsernameAvailable };
if I use the below, the validation works but the swagger doc doesn't build
schema: {
params: yup.object({
username: yup.string().lowercase().max(12).min(1).required(),
}),
}
if I remove the validatorCompiler then I get no errors, swagger does build but I cant use yup
validatorCompiler: ({ schema, method, url, httpPart }: any) => {
return function (data: any) {
try {
const result = schema.validateSync(data, yupOptions);
return { value: result };
} catch (e) {
return { error: e };
}
};
},
}
how can I satisfy both?
Why do I want to use yup? I want to validate emails and transform values into lowercase.

Change field in object in array of object

I have a field achivment with an array of objects. I need to update the field currentPoints in one object that I will find by field name in the array.
Code model of mongoose:
const achive = new Schema(
{
achiveId: ObjectId,
name: { type: String, required: true },
finishedPoints: { type: Number, required: true },
currentPoints: {
type: Number,
default: 0,
set: function (v) {
if (v >= this.finishedPoints) this.isFinished = true;
return v;
}
},
isFinished: { type: Boolean, default: false }
},
{ _id: false }
);
const achivesSchema = new Schema({
userId: ObjectId,
achivement: [achive]
});
Code query:
export async function progressAchive(req, res) {
const value = 3;
try {
const test = await Achives.updateOne(
{
userId: req.user._id,
achivement: { $elemMatch: { name: req.params.nameAchive } }
},
{ $set: { achivement: { currentPoints: value } } },
{ new: true }
);
res.json(test);
} catch (e) {
console.log(e);
}
}
Instead of updating, it removes all objects from the array and leaves them one object with the currentPoint field. How can I update this like I want?
You should use the following for update
const test = await Achives.updateOne(
{
userId: req.user._id,
},
{
$set:{"achivement.$[el].currentPoints": value}
},
{
arrayFilters:[{
"el.name": req.params.nameAchive
}],
new: true
}
);

How to update any amount of fields in a nested documen in Mongoose?

I need to update different fields of a nested array in Mongoose. Sometimes I will send runId and runStatus, some other times siteFetched and some other times siteInfo.
I have tried with the following code but the $set operator replaces the old fields.
The model:
campaignId: { type: String },
keywords: [{
keyword: { type: String },
serp: {
runId: { type: String },
runStatus: { type: String },
siteFetched: { type: Boolean },
sitesInfo: [{
title: { type: String },
url: { type: String },
description: { type: String },
}],
},
},
],
Here is the code to update
const campaign = await Campaign.findOneAndUpdate(
{ _id: campaignId, "keywords.keyword": keyword },
{
$set: { "keywords.$.apifySerp": {...serp }},
}
);
the value for serp varies like
const serp = {
runId: '1kLgbnvpADsDJyP1x',
runStatus: 'READY'
}
and
const serp = {
siteFetched: true
}
Here is the code that solved my problem.
const serp = {
siteFetched: true,
};
let update = Object.keys(serp).reduce((acc, cur) => {
acc[`keywords.$.apifySerp.${cur}`] = serp[cur];
return acc;
}, {});

mongoose - how to get a schema's final document without an insertion

Say I have a schema like this.
{
__content: {
default: "",
index: true,
type: Mixed,
validate: {
validator(v)
{
return !!(
typeof v === "string" ||
(
typeof v === "object" &&
!Array.isArray(v)
)
)
}
}
},
__hidden: {
default: false,
index: true,
type: Boolean
},
__title: {
required: true,
index: true,
type: String,
},
__type: {
default: "text",
enum: ["text", "table"],
index: true,
type: String
},
}
Is it possible to return what the schema would be like if I made a blank insert e.g. Model.create({}) without an actual insertion? Right now, my idea is to insert it into a throwaway collection and just get the return

Query mongoose model using variable as key in nested document

I am having some trouble using model.find to return a list of documents that match a variable condition within my nested schema. I am using node, express, and mongoose for this project.
Ideally I want to use a variable as the object key within the query so I can dynamically grab the day of the week and check the open hours. I haven't had any success so far in finding an answer for this online.
Here is my model.
const restaurantSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
coordinates: {
latitude: { type: Number, required: true },
longitude: { type: Number, required: true }
},
happy_hour: {
sunday: {
open: { type: Boolean },
open_at: { type: Number },
close_at: { type: Number }
},
monday: {
open: { type: Boolean },
open_at: { type: Number },
close_at: { type: Number }
},
tuesday: {
open: { type: Boolean },
open_at: { type: Number },
close_at: { type: Number }
},
wednesday: {
open: { type: Boolean },
open_at: { type: Number },
close_at: { type: Number }
},
thursday: {
open: { type: Boolean },
open_at: { type: Number },
close_at: { type: Number }
},
friday: {
open: { type: Boolean },
open_at: { type: Number },
close_at: { type: Number }
},
saturday: {
open: { type: Boolean },
open_at: { type: Number },
close_at: { type: Number }
}
}
});
Here is a snippet from the controller that is handling the logic for this express route. The snippet below has a static key called "Friday", but I want to dynamically get the day of the week and pass that in as a variable, something like $friday: { open: true } or what not.
exports.search = (req, res, next) => {
const radius = req.query.radius || 5;
const latitude = req.query.latitude;
const longitude = req.query.longitude;
const day = date.get_day();
const minutes = date.get_minutes();
console.log(query[day]);
if ( latitude == undefined || longitude == undefined ) {
res.status(404).json({
message: "Missing longitude or latitude"
})
}
Restaurant.find({
hours: {
friday: {
open: true
}
}
})
.exec()
.then(results => {
res.status(200).json({
data: results.map( result => {
You can try this
var day = req.params.dayName;
var query = {};
query["hours"] = {[day] :true};
collection.find(query, function (err, item) { ... });
You have multiple options to do that.
The query that mongoose expects, is just an object.
const query = { hours : {} };
hours[getTheCurrentDay()].open = true;
// Or with es6
const query = { hours : { [getTheCurrentDay()] : { open : true } };
// Or with pure string
const query = { `hours.${getTheCurrentDay()}.open` : true };
Restaurant.find( query ).exec()...