Joi validation of with when() on external property - joi

I have something like this:
let someCoolString = 'mememe';
const schema = Joi.object({
someFieldName: Joi.string()
.when(someCoolString, {
is: "mememe",
then: Joi.required(),
otherwise: Joi.forbidden(),
})
});
But this obviously doesn't work, as someCoolString is not the Joi.object's property. Any idea of how to check for it?

You can use context:
const schema = Joi.object().keys({
someFieldName: Joi.number().when('$condition', {
is: Joi.boolean().valid(true).required(),
then: Joi.required(),
otherwise: Joi.forbidden()
})
});
let someCoolString = 'mememe';
let someCoolString2 = 'not_meme';
function isMeme(str) {
return str == 'mememe'
};
// error: null
schema.validate({});
// someFieldName required
schema.validate({}, {context: {condition: isMeme(someCoolString)}});
// someFieldName forbidden
schema.validate({ someField: 10 }, {context: {condition: isMeme(someCoolString2)}});

Related

Mongoose Schema properties validation with Typescript NextJS

i am trying to save new document to mongo db, the Schema validation is not working for me, i am trying ti make required true, but i still can add new document without the required field.
this is my schema:
// lib/models/test.model.ts
import { Model, Schema } from 'mongoose';
import createModel from '../createModel';
interface ITest {
first_name: string;
last_name: string;
}
type TestModel = Model<ITest, {}>;
const testSchema = new Schema<ITest, TestModel>({
first_name: {
type: String,
required: [true, 'Required first name'],
},
last_name: {
type: String,
required: true,
},
});
const Test = createModel<ITest, TestModel>('tests', testSchema);
module.exports = Test;
this is createModel:
// lib/createModel.ts
import { Model, model, Schema } from 'mongoose';
// Simple Generic Function for reusability
// Feel free to modify however you like
export default function createModel<T, TModel = Model<T>>(
modelName: string,
schema: Schema<T>
): TModel {
let createdModel: TModel;
if (process.env.NODE_ENV === 'development') {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
// #ts-ignore
if (!global[modelName]) {
createdModel = model<T, TModel>(modelName, schema);
// #ts-ignore
global[modelName] = createdModel;
}
// #ts-ignore
createdModel = global[modelName];
} else {
// In production mode, it's best to not use a global variable.
createdModel = model<T, TModel>(modelName, schema);
}
return createdModel;
}
and this is my tests file:
import { connection } from 'mongoose';
import type { NextApiRequest, NextApiResponse } from 'next';
const Test = require('../../../lib/models/test.model');
import { connect } from '../../../lib/dbConnect';
const ObjectId = require('mongodb').ObjectId;
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
switch (req.method) {
case 'POST': {
return addPost(req, res);
}
}
}
async function addPost(req: NextApiRequest, res: NextApiResponse) {
try {
connect();
// const { first_name, last_name } = req.body;
const test = new Test({
first_name: req.body.first_name,
last_name: req.body.last_name,
});
let post = await test.save();
// return the posts
return res.json({
message: JSON.parse(JSON.stringify(post)),
success: true,
});
// Erase test data after use
//connection.db.dropCollection(testModel.collection.collectionName);
} catch (err) {
//res.status(400).json(err);
res.status(400).json({
message: err,
success: false,
});
}
}
in the Postman, i send a request body without the required field (first_name) and i still can add it.
any help?

Mongodb .post unable to add data to the collection

I am trying to take user input and then add a drug(medicine) to MongoDB. But it is not working and I am getting the error "Add proper parameter first". The user input should be patient name, drug name, dosage, frequency, adherence, and reason for not taking medicine. Please help!
app.post("/add-drug", (req, res) => {
try {
if (req.body && req.body.patient_name && req.body.drug_name && req.body.dosage && req.body.frequency && req.body.adherence && req.body.reason) {
let new_drug = new drug();
new_drug.patient_name = req.body.patient_name
new_drug.drug_name = req.body.drug_name;
new_drug.dosage = req.body.dosage;
new_drug.frequency = req.body.frequency;
new_drug.adherence = req.body.adherence;
new_drug.reason = req.body.reason;
new_drug.user_id = req.user.id;
new_drug.save((err, data) => {
if (err) {
res.status(400).json({
errorMessage: err,
status: false
});
} else {
res.status(200).json({
status: true,
title: 'Drug Added successfully.'
});
}
});
} else {
res.status(400).json({
errorMessage: 'Add proper parameter first!',
status: false
});
}
} catch (e) {
res.status(400).json({
errorMessage: 'Something went wrong!',
status: false
});
}
});
The model file looks like this:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
drugSchema = new Schema( {
patient_name: String,
drug_name: String,
dosage: Number,
frequency: Number,
adherence: Number,
reason: String,
user_id: Schema.ObjectId,
}),
drug = mongoose.model('drug', drugSchema);
module.exports = drug;
it is supposed to be <field>:<value> inside your app.post method, not <field>=<value>
The new_drug.save() method is asynchronous, so it returns a promise that you can await on:
app.post("/add-drug", async(req, res) => {
//...
await new_drug.save();
})

Using the $inc function across the MongoDB documents

I am currently working on a moderation system for my Discord bot and came across an unexpected issue. I've been using the $inc function to increase the values for a single document, though I have sadly not achieved to use the $inc function across multiple different documents, meaning I would like to increase ($inc) the value of the new document according to the numbers of the previous document.
Example: Cases
Current code:
async run(client, message, args, Discord) {
const targetMention = message.mentions.users.first()
const userid = args[0]
const targetId = client.users.cache.find(user => user.id === userid)
const username = targetMention.tag
if(targetMention){
args.shift()
const userId = targetMention.id
const WarnedBy = message.author.tag
const reason = args.join(' ')
if(!reason) {
message.delete()
message.reply('You must state the reason behind the warning you are attempting to apply.').then(message => {
message.delete({ timeout: 6000})
});
return;
}
const warningApplied = new Discord.MessageEmbed()
.setColor('#ffd200')
.setDescription(`A warning has been applied to ${targetMention.tag} :shield:`)
let reply = await message.reply(warningApplied)
let replyID = reply.id
message.reply(replyID)
const warning = {
UserId: userId,
WarnedBy: WarnedBy,
Timestamp: new Date().getTime(),
Reason: reason,
}
await database().then(async database => {
try{
await warnsSchema.findOneAndUpdate({
Username: username,
MessageID: replyID
}, {
$inc: {
Case: 1
},
WarnedBy: WarnedBy,
$push: {
warning: warning
}
}, {
upsert: true
})
} finally {
database.connection.close()
}
})
}
if(targetId){
args.shift()
const userId = message.member.id
const WarnedBy = message.author.tag
const reason = args.join(' ')
if(!reason) {
message.delete()
message.reply('You must state the reason behind the warning you are attempting to apply.').then(message => {
message.delete({ timeout: 6000})
});
return;
}
const warning = {
userId: userId,
WarnedBy: WarnedBy,
timestamp: new Date().getTime(),
reason: reason
}
await database().then(async database => {
try{
await warnsSchema.findOneAndUpdate({
userId,
}, {
$inc: {
Case: 1
},
WarnedBy: WarnedBy,
$push: {
warning: warning
}
}, {
upsert: true
})
} finally {
database.connection.close()
}
const warningApplied = new Discord.MessageEmbed()
.setColor('#ffd200')
.setDescription(`A warning has been applied to ${targetId.tag} :shield:`)
message.reply(warningApplied)
message.delete();
})
}
}
Schema attached to the Code:
const warnsSchema = database.Schema({
Username: {
type: String,
required: true
},
MessageID: {
type: String,
required: true
},
Case: {
type: Number,
required: true
},
warning: {
type: [Object],
required: true
}
})
module.exports = database.model('punishments', warnsSchema)
Answer to my own question. For all of those who are attempting to do exactly the same as me, there is an easier way to get this to properly work. The $inc (increase) function will not work as the main property of a document. An easier way to implement this into your database would be by creating a .json file within your Discord bot files and adding a line such as the following:
{
"Number": 0
}
After that, you'd want to "npm i fs" in order to read directories in live time.
You can proceed to add a function to either increase or decrease the value of the "Number".
You must make sure to import the variable to your current coding document by typing:
const {Number} = require('./config.json')
config.json can be named in any way, it just serves as an example.
Now you'd be able to console.log(Number) in order to make sure the number is what you expected it to be, as well as you can now increase it by typing Number+=[amount]
Hope it was helpful.

Why is mongoose not allowing me to find by $in on _id unless I have a schema wrapping another schema?

Please, if anyone can help explain the below situation. I've lost hours only to discover a working solution but it doesn't make sense why it needs to be implemented this way.
For some reason if I connect mongo over nodejs, using 1 mongoose schema, I cannot search via $in.
When I wrap that single schema in another schema, with no additional information $in returns a match.
Note: the command line works always. So it is something to do with the way the schema needs to be impelemnted.
I am new to mongoose so maybe there is something obvious i am missing but it does not make any sense. Any help appreciated.
thanks
creation of data in docker
Note there is only one record:
#!/bin/bash
set -e
mongo <<EOF
use ${MONGO_DB_NAME}
db.createCollection("persons")
db.persons.insert({"name": "CPU"})
EOF
schema.ts
import { Model, Schema, Document, Connection } from 'mongoose';
interface IPerson {
_id: string;
name: string;
};
type TSchemaPerson = Schema<IPerson>;
const SchemaPerson: TSchemaPerson = new Schema({
_id: String,
name: String
});
// ##########################################################################
// ODD BEHAVIOUR ?
// I have to wrap SchemaPerson inside SchemaPersons,
// even though its basically a duplication in order to get $in to return a result
// --------------------------------------------------------
// \/
const SchemaPersons: Schema<TSchemaPerson> = new Schema({SchemaPerson});
// ##########################################################################
const collectionName: string = 'Person';
export type TDocumentPerson = Model<Document & IPerson>;
export type TConnPersons = (con: Connection) => TDocumentPerson;
// ##########################################################################
// ODD BEHAVIOUR ?
// If I return SchemaPerson instead of SchemaPersons here then $in does not return a result --------
// \/
const ModelPersons: TConnPersons = con => con.model(collectionName, SchemaPersons);
// ##########################################################################
export default ModelPersons;
graphql - resolver.ts
const persons: QueryResolvers['persons'] = async (parent, args, context) => {
console.log('persons()');
const { dbConn }: IContext = context;
if (dbConn) {
const Person: TDocumentPerson = ModelPersons(dbConn);
let result;
let resultIn;
try {
result = await Person.find().exec();
console.log('result = ', result); // Result in both cases = [ { _id: 5f36e199ebd39f53b437834b, name: 'CPU' } ]
resultIn = await Person.find({
_id: {
$in: [new ObjectId('5f36e199ebd39f53b437834b')]
}
}).exec();
// ##########################################################################
// ODD BEHAVIOUR ?
// Result with SchemaPersons = [ { _id: 5f36e199ebd39f53b437834b, name: 'CPU' } ]
// Result with SchemaPerson = [ ]
console.log('resultIn = ', resultIn);
// ##########################################################################
} catch (err) {
console.log('err = ', err);
}
}
};
Note if I change the model to a collection then it does work. See below:
schema.ts - connecting as collection
import { Schema, Collection, Connection } from 'mongoose';
interface IPerson {
_id: string;
name: string;
};
type TSchemaPerson = Schema<IPerson>;
const SchemaPerson: TSchemaPerson = new Schema({
_id: String,
name: String
});
export type TCollectionPersons = Collection & IPerson;
export type TConnPersons = (con: Connection) => TCollectionPersons;
const CollectionPersons: TConnPersons = con => con.collection('persons', SchemaPerson)
export default CollectionPersons;
resolver.ts
const persons: TCollectionPersons = CollectionPersons(dbConn);
let result;
let resultIn;
let resultById
try {
result = await persons.find().toArray();
console.log('result = ', result);
// Result [ { _id: 5f36e199ebd39f53b437834b, name: 'CPU' } ]
resultIn = await persons.find({
_id: {
$in: [new ObjectId('5f36e199ebd39f53b437834b')]
}
}).toArray();
console.log('resultIn = ', resultIn);
// Result [ { _id: 5f36e199ebd39f53b437834b, name: 'CPU' } ]
// cannot use findById on collection.
// resultById = await persons.findById('5f36e199ebd39f53b437834b');
} catch (err) {
console.log('err = ', err);
}
}

MongoDB query - pass the function to the Model.find()

I have issue with querying MongoDB (Mongoose) by passing the function as parameter in Model.find() -> like this Model.find(searchCondition). I hope that you can help me.
// Fetching patients from the database
exports.getPatients = (req, res, next) => {
const criterionSearchCategory = req.query.kriterijumPretrage;
const ageSearchCategory = req.query.kriterijumGodina;
const searchInputValue = req.query.pojamPretrage;
console.log({ [criterionSearchCategory]: { [ageSearchCategory]: Number([searchInputValue]) }});
// Patient search condition, based on selected option from select dropdown
function searchCondition() {
if (criterionSearchCategory == 'undefined') {
return {};
} else if (criterionSearchCategory == 'age') {
return { [criterionSearchCategory]: { [ageSearchCategory] : Number([searchInputValue]) }}
} else {
return { [criterionSearchCategory]: { $in: [ "/^" + searchInputValue + "/i" ]}}
}
}
...
const patientQuery = Patient.find(searchCondition);
getPatients(patientsPerPage: number, currentPage: number, criterionSearchCategory: string, searchInputValue: string, ageSearchCategory: any) {
const queryParams = `?pacijenataPoStranici=${patientsPerPage}&trenutnaStranica=${currentPage}&kriterijumPretrage=${criterionSearchCategory}&pojamPretrage=${searchInputValue}&kriterijumGodina=${ageSearchCategory}`;
this.http
.get<{ message: string, patients: any, maxPatients: number }>( BACKEND_URL + queryParams)
// Execute map on every data that makes it through Observable stream
.pipe(map((patientData) => {
I want to menton when I pass the query params manually, for example const patientQuery = Patient.find({ age: { '$gt': 30 } }); appropriate patients will be fetched correctly , but when I pass the function , like this const patientQuery = Patient.find(searchCondition); then does not work.
The first question, is it possible to pass the function as parameter like this?
Any suggestion will be appreciate. Thank you