I am trying to add in validation on my MongoDB server and everything I give it fails document validation.
The most common error I keep getting:
details: { operatorName: '$and', clausesNotSatisfied: [Array] }
The validation logic I'm using:
{
title: 'Facebook Conversation',
type: 'object',
properties: {
participants: {
type: 'array',
items: {
type: 'object',
properties: {
name: {
type: 'string'
}
},
required: [
'name'
],
additionalProperties: true
}
},
messages: {
type: 'array',
items: {
type: 'object',
properties: {
sender_name: {
type: 'string'
},
timestamp_ms: {
type: 'number'
},
content: {
type: 'string'
},
type: {
type: 'string'
},
photos: {
type: 'array',
items: {
type: 'object',
properties: {
uri: {
type: 'string'
},
creation_timestamp: {
type: 'number'
}
},
required: [
'uri',
'creation_timestamp'
]
}
},
ip: {
type: 'string'
},
sticker: {
type: 'object',
properties: {
uri: {
type: 'string'
}
}
},
payment_info: {
type: 'object',
properties: {
amount: {
type: 'number'
},
currency: {
type: 'string'
},
creationTime: {
type: 'number'
},
completedTime: {
type: 'number'
},
senderName: {
type: 'string'
},
receiverName: {
type: 'string'
}
}
},
call_duration: {
type: 'number'
},
missed: {
type: 'boolean'
},
gifs: {
type: 'array',
items: {
type: 'object',
properties: {
uri: {
type: 'string'
}
},
required: [
'uri'
]
}
},
share: {
type: 'object',
properties: {
link: {
type: 'string'
},
share_text: {
type: 'string'
}
}
},
videos: {
type: 'array',
items: {
type: 'object',
properties: {
uri: {
type: 'string'
},
creation_timestamp: {
type: 'number'
},
thumbnail: {
type: 'object',
properties: {
uri: {
type: 'string'
}
}
}
},
required: [
'uri',
'creation_timestamp',
'thumbnail'
]
}
},
reactions: {
type: 'array',
items: {
type: 'object',
properties: {
reaction: {
type: 'string'
},
actor: {
type: 'string'
}
},
required: [
'reaction',
'actor'
]
}
},
audio_files: {
type: 'array',
items: {
type: 'object',
properties: {
uri: {
type: 'string'
},
creation_timestamp: {
type: 'number'
}
},
required: [
'uri',
'creation_timestamp'
]
}
},
users: {
type: 'array',
items: {
type: 'object',
properties: {
name: {
type: 'string'
}
},
required: [
'name'
]
}
},
files: {
type: 'array',
items: {
type: 'object',
properties: {
uri: {
type: 'string'
},
creation_timestamp: {
type: 'number'
}
}
}
}
},
required: [
'sender_name',
'timestamp_ms',
'content'
]
}
},
title: {
type: 'string'
},
is_still_participant: {
type: 'boolean'
},
thread_type: {
type: 'string'
},
thread_path: {
type: 'string'
}
}
}
An example of a JSON file that fails document validation:
{
"participants": [
{
"name": "Person"
},
{
"name": "Me"
}
],
"messages": [
{
"sender_name": "Person",
"timestamp_ms": 1550000006885,
"content": "Message content",
"type": "Generic"
}
],
"title": "Person",
"is_still_participant": true,
"thread_type": "Regular",
"thread_path": "inbox/Person"
}
Typescript code I'm using:
apiRouter.post("/", async (req: any, res: any) => {
res.setHeader("Content-Type", "application/json; charset=utf-8");
try {
let newConversation = JSON.stringify(req.body);
const result = await collections.conversations?.insertOne(newConversation);
result
? res.status(201).send(`Successfully created a new conversation`)
: res.status(500).send("Failed to create a new conversation.");
} catch (error) {
if (error instanceof Error) {
console.error(error);
res.status(400).send(error.message);
} else {
console.log('Unexpected error', error);
}
}
});
If I use JSON.stringify on the req.body, I get this error:
Cannot create property '_id' on string
So I'm really confused as to what I'm doing wrong, I actually allowed JSON to pass without validation and then exported the schema in Compass and used that as validation logic and that doesn't work as well. But maybe that's not meant to be used as validation logic and that's why. But if anybody could help me figure this out, I'd appreciate it.
I figured it out, when I added the JSON as validation in MongoDB, I used Compass and it didn't accept the $schema tag and when I removed it, it allowed me to apply it, so I assumed I entered everything correctly.
However I was looking around MongoDB's website to see examples of JSON schemas and I noticed they enclosed it with $jsonSchema: {}, they were using the shell so I assumed that's why. But I decided to try it and finally documents are successfully validating!
Related
I have a fastify route method with the following schema.
fastify.post('/club', createClubSchema, createClub(fastify));
const createClubSchema = {
schema: {
tags: ['club'],
security: [
{
ApiKeyAuth: [],
},
],
body: {
type: 'object',
required: ['name', 'description'],
properties: {
name: { type: 'string', minLength: 3 },
description: { type: 'string', minLength: 3 },
logoUrl: { type: 'string', minLength: 3 },
},
},
response: {
200: {
type: 'object',
properties: {
status: { type: 'string' },
data: {
type: 'object',
properties: {
name: { type: 'string' },
description: { type: 'string' },
id: { type: 'number' },
color: { type: 'string' },
logoUrl: { type: 'string' },
createdAt: { type: 'string' },
updatedAt: { type: 'string' },
},
},
},
},
},
},
preHandler: [grantAccess('create', 'club')],
};
Now this route is in folder where there is autohooks.js is located that has a prehandler hook of it's own which checks whether the request has token in it for authentication purposes.
The problem is after the preHandler hook is called on both the area the createClub controller method is called, so a total of 2 times.
What is the issue? and how can i solve this?
EDIT
This is the autoHooks.js file
const authentication = require('../../middlewares/authentication');
module.exports = async function (fastify, opts, next) {
fastify.addHook('preHandler', authentication.authenticate(fastify));
};
I am following this toutorial: https://github.com/codeBelt/open-api-documentation/blob/master/src/openApiDocumentation.js
Can I validate my openApiDocumentation.js file somewhow? I get:
Unable to render this definition
The provided definition does not specify a valid version field.
Please indicate a valid Swagger or OpenAPI version field. Supported version fields are swagger: "2.0" and those that match openapi: 3.0.n (for example, openapi: 3.0.0).
I am attaching mi .js file. Maybe you guys will see a typo here. Thanks in advance.
.js file:
const USER_TYPES = {
EXCHANGE: 'xxx',
GIVEAWAY: 'xxx'
}
const openApiDocumentation = {
openapi: '3.0.1',
info: {
version: '1.3.0',
title: 'xxx',
description: 'xxx',
contact: {
name: 'xxx',
email: 'xxx',
}
},
license: {
name: 'Apache 2.0',
url: 'https://www.apache.org/licenses/LICENSE-2.0.html',
},
servers: [
{
url: 'http://localhost:4000/',
description: 'Local server',
},
],
tags: [
{
name: 'Books CRUD operations',
},
],
paths: {
'/findAllBooks': {
get: {
tags: ['CRUD operations'],
description: 'Get all Book offers',
operationId: 'getUsers',
parameters: [
{
name: 'page',
in: 'query',
schema: {
type: 'integer',
default: 1,
},
required: true,
description: 'Page numer used pagination.',
},
],
responses: {
'200': {
description: 'Books were obtained',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/Books',
},
},
},
},
'500': {
description: 'Missing parameters',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/Error',
},
example: {
message: 'page qyery parameter is missing',
internal_code: 'missing_parameters',
},
},
},
},
},
},
},
},
components: {
schemas: {
coverImg: {
type: 'string',
example: 'http:',
},
image: {
type: 'string',
example: 'http',
},
category: {
type: 'string',
example: 'Crafts & Hobbies',
},
linkTypes: {
type: 'object',
properties: {
coverImg: {
$ref: '#/components/schemas/coverImg',
},
images: {
type: 'array',
items: {
$ref: '#/components/schemas/image',
},
}
}
},
offerID: {
type: 'string',
example: '27301927',
},
userID: {
type: 'string',
example: 'efdc5192',
},
title: {
type: 'string',
example: 'Quilting For Dummies',
},
description: {
type: 'string',
example: 'You ',
},
categories: {
type: 'array',
items: {
$ref: '#/components/schemas/category',
},
},
links: {
type: 'object',
items: {
$ref: '#/components/schemas/linkTypes',
},
},
offerType: {
type: 'string',
enum: USER_TYPES,
default: USER_TYPES.EXCHANGE,
},
Book: {
type: 'object',
properties: {
offerID: {
$ref: '#/components/schemas/offerID',
},
userID: {
$ref: '#/components/schemas/userID',
},
title: {
$ref: '#/components/schemas/title',
},
description: {
$ref: '#/components/schemas/description',
},
categories: {
$ref: '#/components/schemas/categories',
},
imageLinks: {
$ref: '#/components/schemas/links',
},
offerType: {
$ref: '#/components/schemas/offerType',
},
},
},
Books: {
type: 'object',
properties: {
users: {
type: 'array',
items: {
$ref: '#/components/schemas/Book',
},
},
},
},
Error: {
type: 'object',
properties: {
message: {
type: 'string',
},
internal_code: {
type: 'string',
},
},
},
},
},
};
There are few mistakes,
license must be inside the info object
info: {
version: '1.3.0',
title: 'xxx',
description: 'xxx',
contact: {
name: 'xxx',
email: 'xxx' // make sure you have used valid email address!
},
license: {
name: 'Apache 2.0',
url: 'https://www.apache.org/licenses/LICENSE-2.0.html'
}
}
enum will allow only array not an object and here you have passed object USER_TYPES, corrected below:
const USER_TYPES = {
EXCHANGE: 'xxx',
GIVEAWAY: 'xxx'
};
const USER_TYPES_ENUM = [
USER_TYPES.EXCHANGE,
USER_TYPES.GIVEAWAY
];
offerType: {
type: 'string',
enum: USER_TYPES_ENUM,
default: USER_TYPES.EXCHANGE,
},
For best practice use https://editor.swagger.io/ (also they have provided a option to convert json to yaml under Edit > Convert to YAML)!
I'm creating a GraphQL Server using Node JS.
I'm trying to replicate the mongo Schema which has a nested object purely for organisation. This is my mongo schema:
var plansSchema = new Schema({
planName: {
type: String,
required: [true, "Plan name is required"]
},
pricing: {
monthly: Number,
scanEnvelope: Number,
initalScan: Number,
perPage: Number,
forwardMail: Number,
forwardParcel: Number,
shred: Number,
perMonthPerGram: Number,
freeStorePerGram: Number,
setup: Number,
idFree: Number
},
expires: Number,
private: Boolean,
deleted: Boolean,
date: { type: Date, default: Date.now },
});
I'm trying to replicate this in a GraphQL schema, so far I have the following:
const PlanType = new GraphQLObjectType({
name: "Plan",
fields: () => ({
id: { type: GraphQLString },
planName: { type: GraphQLString },
pricing: new GraphQLObjectType({
name: "Pricing",
fields: () => ({
expires: { type: GraphQLInt },
private: { type: GraphQLBoolean },
monthly: { type: GraphQLInt },
scanEnvelope: { type: GraphQLInt },
initalScan: { type: GraphQLInt },
perPage: { type: GraphQLInt },
forwardMail: { type: GraphQLInt },
forwardParcel: { type: GraphQLInt },
shred: { type: GraphQLInt },
perMonthPerGram: { type: GraphQLInt },
freeStorePerGram: { type: GraphQLInt },
setup: { type: GraphQLInt },
idFree: { type: GraphQLInt }
})
})
})
});
But I'm getting the following errro in GraphiQL
{
"errors": [
{
"message": "The type of Plan.pricing must be Output Type but got: undefined."
}
]
}
Each field in the GraphQLFieldConfigMapThunk or GraphQLFieldConfigMap that you set as your fields must be a GraphQLFieldConfig object that includes properties like type, args, resolve, etc. You cannot set a field to a GraphQLObjectType like you're doing with the pricing field. In other words, your code should look more like this:
const PricingType = new GraphQLObjectType({
name: "Pricing",
fields: () => ({
expires: { type: GraphQLInt },
private: { type: GraphQLBoolean },
monthly: { type: GraphQLInt },
scanEnvelope: { type: GraphQLInt },
initalScan: { type: GraphQLInt },
perPage: { type: GraphQLInt },
forwardMail: { type: GraphQLInt },
forwardParcel: { type: GraphQLInt },
shred: { type: GraphQLInt },
perMonthPerGram: { type: GraphQLInt },
freeStorePerGram: { type: GraphQLInt },
setup: { type: GraphQLInt },
idFree: { type: GraphQLInt }
})
})
const PlanType = new GraphQLObjectType({
name: "Plan",
fields: () => ({
id: { type: GraphQLString },
planName: { type: GraphQLString },
pricing: { type: PricingType },
}),
})
I have a Mongoose model called Session with a field named course (Course model) and I want to perform full text search on sessions with full text search, also I wanna aggregate results using fields from course sub field and to select some fields like course, date, etc.
I tried the following:
Session.aggregate(
[
{
$match: { $text: { $search: 'web' } }
},
{ $unwind: '$course' },
{
$project: {
course: '$course',
date: '$date',
address: '$address',
available: '$available'
}
},
{
$group: {
_id: { title: '$course.title', category: '$course.courseCategory', language: '$course.language' }
}
}
],
function(err, result) {
if (err) {
console.error(err);
} else {
Session.deepPopulate(result, 'course course.trainer
course.courseCategory', function(err, sessions) {
res.json(sessions);
});
}
}
);
My models:
Session
schema = new mongoose.Schema(
{
date: {
type: Date,
required: true
},
course: {
type: mongoose.Schema.Types.ObjectId,
ref: 'course',
required: true
},
palnning: {
type: [Schedule]
},
attachments: {
type: [Attachment]
},
topics: {
type: [Topic]
},
trainer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'trainer'
},
trainingCompany: {
type: mongoose.Schema.Types.ObjectId,
ref: 'training-company'
},
address: {
type: Address
},
quizzes: {
type: [mongoose.Schema.Types.ObjectId],
ref: 'quiz'
},
path: {
type: String
},
limitPlaces: {
type: Number
},
status: {
type: String
},
available: {
type: Boolean,
default: true
},
createdAt: {
type: Date,
default: new Date()
},
updatedAt: {
type: Date
}
},
{
versionKey: false
}
);
Course
let schema = new mongoose.Schema(
{
title: {
type: String,
required: true
},
description: {
type: String
},
shortDescription: {
type: String
},
duration: {
type: Duration
},
slug: {
type: String
},
slugs: {
type: [String]
},
program: {
content: {
type: String
},
file: {
type: String
}
},
audience: [String],
requirements: [String],
language: {
type: String,
enum: languages
},
price: {
type: Number
},
sections: [Section],
attachments: {
type: [Attachment]
},
tags: [String],
courseCategory: {
type: mongoose.Schema.Types.ObjectId,
ref: 'course-category',
required: true
},
trainer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'trainer'
},
trainingCompany: {
type: mongoose.Schema.Types.ObjectId,
ref: 'training-company'
},
status: {
type: String,
default: 'draft',
enum: courseStatus
},
path: {
type: String
},
cover: {
type: String,
required: true
},
duration: {
type: Number,
min: 1
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date
}
},
{ versionKey: false }
);
I am not sure if what I tried is gonna bring me what I want and I am getting this error concerning the $unwind operator:
MongoError: exception: Value at end of $unwind field path '$course'
must be an Array, but is a OID
Any kind of help will be really appreciated.
You can try below aggregation.
You are missing $lookup required to pull course document by joining on course object id from session document to id in the course document.
$project stage to keep the desired fields in the output.
Session.aggregate([
{
"$match": {
"$text": {
"$search": "web"
}
}
},
{
"$lookup": {
"from": "courses",
"localField": "course",
"foreignField": "_id",
"as": "course"
}
},
{
"$project": {
"course": 1,
"date": 1,
"address": 1,
"available": 1
}
}
])
Course is an array with one course document. You can use the $arrayElemAt to project the document.
"course": {"$arrayElemAt":["$course", 0]}
I am using a sailsjs framework for an app. I am building, and I am trying to extract the id value from a model I have:
const memoryCreatorId = _(Memory.creator).map('id').value();
console.log(memoryCreatorId);
const message = {
app_id: '***********************',
contents: {"en": "Yeah Buddy, Rolling Like a Big Shot!"},
filters: [{"field": "tag", "key": "userId", "relation": "=", "value": memoryCreatorId}],
ios_badgeType: 'Increase',
ios_badgeCount: 1
};
return PushNotificationService.sendNotification(message);
I'm basically trying to get what would be the Memory.creator.User.id value. So basically the userid of the person who creates a memory. I'm trying to get it from the "Memory" model "creator" attribute, which maps to the "User" model, and from the "User" model, extract the "id" attribute. Thanks for your help in advance!
Memory model below:
Memory.js
const _ = require('lodash');
module.exports = {
attributes: {
creator: {
model: 'User'
},
title: {
type: 'string'
},
description: {
type: 'text'
},
contentUrl: {
type: 'string',
url: true
},
cropRect: {
type: 'string'
},
likers: {
collection: 'User',
via: 'memoryLikes'
},
comments: {
collection: 'Comment',
via: 'memory'
},
update: {
model: 'Update',
},
cause: {
model: 'Cause',
}
}
};
User model is a follows:
User.js
'use strict';
const uuid = require('node-uuid');
const CipherService = require('../services/CipherService');
const BraintreeService = require('../services/BraintreeService');
module.exports = {
attributes: {
id: {
type: 'string',
primaryKey: true,
defaultsTo: () => uuid.v4(),
unique: true,
index: true,
uuidv4: true
},
firstName: {
type: 'string',
defaultsTo: ''
},
lastName: {
type: 'string',
defaultsTo: ''
},
email: {
type: 'string',
email: true,
required: true,
unique: true
},
password: {
type: 'string'
},
passwordResetToken: {
type: 'string'
},
passwordResetTokenExpires: {
type: 'string'
},
type: {
type: 'string',
enum: ['admin', 'member']
},
city: {
type: 'string'
},
state: {
type: 'string'
},
address: {
type: 'string'
},
institution: {
model: 'Institution'
},
major: {
type: 'string'
},
contentUrl: {
type: 'string',
url: true,
defaultsTo: AwsService.getAssetImageUrl('user-default.png')
},
cropRect: {
type: 'string'
},
graduationYear: {
type: 'integer'
},
donations: {
collection: 'Donation',
via: 'donor'
},
memories: {
collection: 'Memory',
via: 'creator'
},
causes: {
collection: 'Cause',
via: 'followers',
dominant: true
},
adminCauses: {
collection: 'Cause',
via: 'admins'
},
isLeader: {
type: 'boolean',
defaultsTo: false
},
isCurrentStudent: {
type: 'boolean',
defaultsTo: false
},
isAdmin: {
type: 'boolean',
defaultsTo: false
},
adminTitle: {
type: 'string'
},
paymentProfile: {
model: 'PaymentProfile'
},
jsonWebTokens: {
collection: 'Jwt',
via: 'owner'
},
memoryLikes: {
collection: 'Memory',
via: 'likers'
},
updateLikes: {
collection: 'Update',
via: 'likers'
},
toJSON: function() {
return User.clean(this);
}
},
beforeUpdate: (values, next) => {
CipherService.hashPassword(values).then(() => next()).catch(next);
},
beforeCreate: (values, next) => {
CipherService.hashPassword(values).then(() => next()).catch(next);
},
clean: (user) => {
//let obj = user.toObject();
delete user.password;
delete user.jsonWebTokens;
delete user.passwordResetToken;
delete user.passwordResetTokenExpires;
return user;
}
};