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
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)
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.
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"
}
}
}
]
},
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.
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.