Fastify calling controller method multiple times when multiple preHandler hook is called - node.js

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));
};

Related

MongoDB Document Validation always fails

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!

Node.js - Swagger - Unable to render this definition

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)!

Mongoose populate not working upto 3 levels

I am trying to populate a fields that is nested inside my model but it is not populating.
This is a field inside my model.
pendingChanges: {
credentials: {
university: {
name: { type: String },
major: { type: String },
majorGpa: { type: Number },
},
school: {
name: { type: String },
degreeType: { type: String },
degree: { type: String },
},
subjects: [{ type: mongoose.Schema.Types.ObjectId, ref: 'subject' }],
workExperience: {
type: { type: String },
from: { type: Date },
to: { type: Date },
},
},
},
I am trying to populate subjects key nested inside.
This is what I have done so far.
const teacher = (await this.findById(id))
.populate({
path: 'pendingChanges',
populate: {
path: 'credentials',
populate: {
path: 'subjects',
},
},
});
I found the solution. Here's what I did to make it work.
const data = await this.findOne(query)
.populate({
path: 'pendingChanges.credentials.subjects',
});

Nested Objects in GraphQL Schema in NodeJS

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 },
}),
})

Want to output ID value of user and display it on console

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;
}
};

Resources