ValidationPipe() does not work on override #Query in Nestjs/Crud - javascript

I'm trying to validate the parameters that come in the query of a get request, but for some reason, the validation pipe is unable to identify the elements of the query.
import {
Controller,
Post,
Query,
Body,
UseInterceptors,
Param,
Res,
Logger,
} from '#nestjs/common';
import { Crud, CrudController, Override } from '#nestjsx/crud';
import { OpenScheduleDto } from './open-schedule.dto';
#Crud(Schedule)
export class ScheduleController
implements CrudController<ScheduleService, Schedule> {
constructor(public service: ScheduleService) {}
get base(): CrudController<ScheduleService, Schedule> {
return this;
}
#Override()
async getMany(#Query() query: OpenScheduleDto) {
return query;
}
}
OpenSchedule.dto
import { IsNumber, IsOptional, IsString } from 'class-validator';
export class OpenScheduleDto {
#IsNumber()
companyId: number;
#IsNumber()
#IsOptional()
professionalId: number;
#IsString()
#IsOptional()
scheduleDate: string;
}
When I make a get request to http://localhost:3000/schedules?companyId=3&professionalId=1
I get unexpected errors:
{
"statusCode": 400,
"error": "Bad Request",
"message": [
{
"target": {
"companyId": "3",
"professionalId": "1"
},
"value": "3",
"property": "companyId",
"children": [],
"constraints": {
"isNumber": "companyId must be a number"
}
},
{
"target": {
"companyId": "3",
"professionalId": "1"
},
"value": "1",
"property": "professionalId",
"children": [],
"constraints": {
"isNumber": "professionalId must be a number"
}
}
]
}

That is because when you use #Query parameters, everything is a string. It does not have number or boolean as data types like json. So you have to transform your value to a number first. For that, you can use class-transformer's #Transform:
import { IsNumber, IsOptional, IsString } from 'class-validator';
import { Transform } from 'class-transformer';
export class OpenScheduleDto {
#Transform(id => parseInt(id))
#IsNumber()
companyId: number;
#Transform(id => id ? parseInt(id) : id)
#IsNumber()
#IsOptional()
professionalId?: number;
#IsString()
#IsOptional()
scheduleDate?: string;
}
Note though, that this is unsafe because e.g. parseInt('5abc010') is 5. So you might want to do additional checks in your transformation function.

Related

NestJS mongoose return model when i use ClassSerializerInterceptor

I've started to play with NestJS and TypeScript.
I made a simple application that returns the user from MongoDB by ID on a GET request. And the response to client should not have some fields such as password
But if i use .lean() i get response with _id === {}:
{
"_id": {},
"firstName": "sdfsdf",
"lastName": "dfgdfg5y",
"time": 234234
}
If I don't use the lean(), I get mongoose model object:
{
"$__": {
"activePaths": {
"paths": {
"_id": "init",
"firstName": "init",
"lastName": "init",
"password": "init",
"time": "init"
},
"states": {
"ignore": {},
"default": {},
"init": {
"_id": true,
"firstName": true,
"lastName": true,
"password": true,
"time": true
},
"modify": {},
"require": {}
},
"stateNames": [
"require",
"modify",
"init",
"default",
"ignore"
]
},
"strictMode": true,
"skipId": true,
"selected": {},
"fields": {},
"exclude": null,
"_id": {}
},
"$isNew": false,
"_doc": {
"_id": {},
"firstName": "sdfsdf",
"lastName": "dfgdfg5y",
"password": "some pass",
"time": 234234
}
}
Iterestingly, the console.log() in the user.controller.ts prints the object I need to the console:
{
_id: new ObjectId("6245e409e06ac89e5f03851b"),
firstName: 'sdfsdf',
lastName: 'dfgdfg5y',
password: 'some pass',
time: 234234
}
I expect to get an object with _id and an excluded password
user.module.ts
import { User, UserSchema } from './schemas/user.schema';
import { MongooseModule } from '#nestjs/mongoose';
import { UserService } from './user.service';
import { Module } from '#nestjs/common';
import { UserController } from './user.controller';
#Module({
imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])],
controllers: [UserController],
providers: [UserService]
})
export class UserModule {}
user.controller.ts
import { UserService } from './user.service';
import { UserDto } from './dto/user.dto';
import { ClassSerializerInterceptor, Controller, UseInterceptors, Get, Param } from '#nestjs/common';
#Controller('user')
#UseInterceptors(ClassSerializerInterceptor)
export class UserController {
constructor(private readonly service: UserService) {}
#Get('/:id')
async getOne(#Param('id') id: string): Promise<UserDto> {
const user = await this.service.findOne(id);
console.log(user); // <===============================================
return new UserDto(user);
}
}
user.service.ts
import { UserDto } from './dto/user.dto';
import { User, UserDocument } from './schemas/user.schema';
import { Model } from 'mongoose';
import { Injectable } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
#Injectable()
export class UserService {
constructor(#InjectModel(User.name) private UserModel: Model<UserDocument>) {}
async findOne(id: string): Promise<UserDto> {
return await this.UserModel.findById(id).lean();
}
}
user.dto.ts
import { Exclude, Expose } from 'class-transformer';
export class UserDto {
#Expose()
_id: string;
firstName: string;
lastName: string;
time: number;
#Exclude()
password: string;
constructor(partial: Partial<UserDto>) {
Object.assign(this, partial);
}
}
user.schema.ts
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
export type UserDocument = User & Document;
#Schema()
export class User {
#Prop()
firstName: string;
#Prop()
lastName: string;
#Prop()
password: string;
#Prop()
time: number;
}
export const UserSchema = SchemaFactory.createForClass(User);
I'm starting to hate it a little bit
Removeļ¼š
#Exclude()
Then:
#Injectable()
export class InterceptorForClassSerializer extends ClassSerializerInterceptor {
serialize(response: any, options: ClassTransformOptions) {
const rawDataJSON = JSON.stringify(response);
return super.serialize(JSON.parse(rawDataJSON), options);
}
}
#SerializeOptions({
excludePrefixes: ['password']
})
#UseInterceptors(InterceptorForClassSerializer)

Mongoose: Sorting is not working as expected

I'm facing a strang problem while sorting documents in mongoose.
I have a huge scheme named as Property that contain a price object something like this:
{
info: {
price: {
price: 361000 // number
}
}
}
Property Model:
#Field(() => Number, { nullable: true })
#Prop({ type: SchemaTypes.Number })
price: number;
Now, I have to sort the properties via property price i.e info.price.price.
What I have done:
const properties = await this.propertiesModel
.find({})
.skip(skipBy)
.limit(size)
.sort({ "info.price.price": -1 }); // sorting
The results are not as expected:
{
"info": {
"price": {
"price": 999000
}
}
},
{
"info": {
"price": {
"price": 99900 // wrong
}
}
},
{
"info": {
"price": {
"price": 997000
}
}
}
The schema is already using the Number type for price but seems like it's sorting as a string somehow.

Implementing GraphQL Global Object Identification in NestJS (code-first approach)

I am trying to implement Global Object Identification described in GraphQL's documentation in NestJS.
1.) I started by creating a Node interface:
import { ID, InterfaceType, Field } from '#nestjs/graphql'
#InterfaceType()
export abstract class Node {
#Field(type => ID)
id: number
}
2.) I implemented it in my model:
import { Table } from "sequelize-typescript";
import { ObjectType } from "#nestjs/graphql";
import { Node } from "src/node/node-interface";
#ObjectType({
implements: Node
})
#Table
export class User extends Model {
// [Class body here...]
}
3.) Then I created a Query that would return users:
import { Resolver, Query} from "#nestjs/graphql";
import { User } from "./user-model";
#Resolver(of => User)
export class UserResolver {
#Query(returns => [Node])
async users() {
let users = await User.findAll();
console.log(users);
return users;
}
}
4.) Then I performed the test query from the documentation:
{
__schema {
queryType {
fields {
name
type {
name
kind
}
args {
name
type {
kind
ofType {
name
kind
}
}
}
}
}
}
}
5.) But instead of receiving the proper response:
{
"__schema": {
"queryType": {
"fields": [
// This array may have other entries
{
"name": "node",
"type": {
"name": "Node",
"kind": "INTERFACE"
},
"args": [
{
"name": "id",
"type": {
"kind": "NON_NULL",
"ofType": {
"name": "ID",
"kind": "SCALAR"
}
}
}
]
}
]
}
}
}
6.) I get this:
{
"data": {
"__schema": {
"queryType": {
"fields": [
{
"name": "users",
"type": {
"name": null,
"kind": "NON_NULL"
},
"args": []
}
]
}
}
}
}
I have no clue what I am doing wrong. I'd appreciate any help with this.
Maybe it's too late, but I'm at Node Resolver node must be nullable
import * as GQL from '#nestjs/graphql';
#GQL.Resolver(() => Node, {})
export class NodeResolver {
#GQL.Query(() => Node, {
name: 'node',
defaultValue: [],
nullable: true,
})
node(
#GQL.Args('id', { type: () => GQL.ID } as GQL.ArgsOptions)
id: Scalars['ID'],
): Promise<Node> {
// Implement
return null;
}
}
result:
{
"name": "node",
"type": {
"name": "Node",
"kind": "INTERFACE",
},
"args": [
{
"name": "id",
"type": {
"kind": "NON_NULL",
"ofType": {
"name": "ID",
"kind": "SCALAR"
}
}
}
]
},

Many-to-Many-to-Many relationship in Objection.js

I have a project where an user can have many platforms. These platforms can have many passwords. Currently I have following database structure:
Im trying to use eager loading to get the following object:
{
"id": 1,
"username": "Keith",
"platforms": [
{
"id": 1,
"name": "Jira",
"passwords": [
{
"id": 1,
"password": "hash"
},
{
"id": 2,
"password": "otherhash"
}
]
},
{
"id": 2,
"name": "Confluence",
"passwords": [
{
"id": 3,
"password": "anotherhash"
},
{
"id": 4,
"password": "anotherone"
}
]
}
]
}
I spent a few hours and couldnt figure out. How could I define the relations to get this structure? Is this possible?
As far as I know that is not possible to do without creating own model for that 3-way join table.
So models would look something like this:
class User extends objection.Model {
static get tableName() {
return 'user';
}
static get relationMappings() {
return {
platformPasswords: {
relation: Model.HasManyRelation,
modelClass: UserPlatformPassword,
join: {
from: 'user.id',
to: 'user_platform_password.user_id'
}
}
}
}
}
class Platform extends objection.Model {
static get tableName() {
return 'platform';
}
}
class Password extends objection.Model {
static get tableName() {
return 'password';
}
}
class UserPlatformPassword extends objection.Model {
static get tableName() {
return 'user_platform_password';
}
static get relationMappings() {
return {
password: {
relation: Model.HasOne,
modelClass: Password,
join: {
from: 'user_platform_password.password_id',
to: 'password.id'
}
},
platform: {
relation: Model.HasOne,
modelClass: Platform,
join: {
from: 'user_platform_password.platform_id',
to: 'platform.id'
}
}
}
}
}
Maybe there are some other ways to model those relations at least in a way that they work when doing eager selects, but I'm having hard time to understand how it could work in case when you would like to insert / upsert that nested data, where multiple relations are dealing with different fields of the same join table.

Get String instead of Number

I'm working on a quiz app that is based on a 'string' answered instead of the variable 'number'. I tried different syntax but it just displays a blank with no data/error displayed. As you can see my code below, I'll give you an example logic that I want.
if(answer.correct == "answerstring"){displayvariable == "STRINGTOBEDISPLAYED"};
Here is my 'question.json'
{
"questions": [
{
"flashCardFront": "<img src='assets/questionimg/12_plate1.gif' />",
"flashCardBack": "12",
"flashCardFlipped": false,
"questionText": "What number is this?",
"answers": [
{"answer": "12", "correct": true, "selected": false},
{"answer": "17", "correct": false, "selected": false},
{"answer": "NOTHING", "correct": false, "selected": false}
]
},
{
"flashCardFront": "<img src='assets/questionimg/8_plate2.gif' />",
"flashCardBack": "8",
"flashCardFlipped": false,
"questionText": "What is number is this?",
"answers": [
{"answer": "3", "correct": false, "selected": false},
{"answer": "8", "correct": true, "selected": false},
{"answer": "NOTHING", "correct": false, "selected": false}
]
},
{
"flashCardFront": "<img src='assets/questionimg/29_plate3.gif' />",
"flashCardBack": "29",
"flashCardFlipped": false,
"questionText": "What is this?",
"answers": [
{"answer": "70", "correct": false, "selected": false},
{"answer": "NOTHING", "correct": false, "selected": false},
{"answer": "29", "correct": true, "selected": false}
]
}
]
}
My data.ts where my dataprovider is located.
import { Injectable } from '#angular/core';
import 'rxjs/add/operator/map';
import { HttpClient } from '#angular/common/http';
/*
Generated class for the DataProvider provider.
See https://angular.io/guide/dependency-injection for more info on providers
and Angular DI.
*/
#Injectable()
export class DataProvider {
data: any;
constructor(public http: HttpClient) {
}
load(){
if(this.data){
return Promise.resolve(this.data);
}
return new Promise(resolve => {
this.http.get('assets/data/questions.json').subscribe((data:any) => {
this.data = data.questions;
resolve(this.data);
});
});
}
}
My typescript where the quiz is being processed
import { Component, ViewChild} from '#angular/core';
import { NavController} from 'ionic-angular';
import { DataProvider } from '../../providers/data/data';
/**
* Generated class for the IshiharaQuestionsPage page.
*
* See https://ionicframework.com/docs/components/#navigation for more info on
* Ionic pages and navigation.
*/
#Component({
selector: 'page-ishihara-questions',
templateUrl: 'ishihara-questions.html',
})
export class IshiharaQuestionsPage {
#ViewChild('slides') slides: any;
hasAnswered: boolean = false;
score: number = 0;
cvd: string;
slideOptions: any;
questions: any;
constructor(public navCtrl: NavController, public dataService: DataProvider) {
}
ionViewDidLoad() {
this.slides.lockSwipes(true);
this.dataService.load().then((data) => {
data.map((question) => {
let originalOrder = question.answers;
question.answers = this.randomizeAnswers(originalOrder);
return question;
});
this.questions = data;
});
}
nextSlide(){
this.slides.lockSwipes(false);
this.slides.slideNext();
this.slides.lockSwipes(true);
}
selectAnswer(answer, question){
this.hasAnswered = true;
answer.selected = true;
question.flashCardFlipped = true;
if(answer.correct){
this.score++;
}
setTimeout(() => {
this.hasAnswered = false;
this.nextSlide();
answer.selected = false;
question.flashCardFlipped = false;
}, 3000);
}
randomizeAnswers(rawAnswers: any[]): any[] {
for (let i = rawAnswers.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
let temp = rawAnswers[i];
rawAnswers[i] = rawAnswers[j];
rawAnswers[j] = temp;
}
return rawAnswers;
}
restartQuiz() {
this.score = 0;
this.slides.lockSwipes(false);
this.slides.slideTo(1, 1000);
this.slides.lockSwipes(true);
}
}
and this is where I want to display that string.
<ion-slide>
<ion-card>
<h2 text-justify padding>The 24 plate quiz suggests that you might currenly belong to this CVD type:</h2>
<br>
<br>
<br>
<h1 color="danger">{{cvd}}</h1> <<-----------------THIS LINE
<br>
<br>
<br>
<h2>Final Score: {{score}}/24</h2>
<button (click)="restartQuiz()" ion-button full color="primary">Start Again</button>
</ion-card>
</ion-slide>
Ionic syntax are basically uses TypeScript or more specifically similar to Angular (JS/2+) code, in Angular(JS/2+) for string comparison or to check equality we use '===' (triple equal to) rather '=='. Please replace '==' with '===', and it should be working OK.

Categories

Resources