[NestJS]Custom Parameter in Class Constructor - node.js

I am trying to pass custom arguments into a class that injects another service(ApiCallService).
export class FPCT {
constructor(
payload: {
isLive: boolean;
exchangeReference?: string | number;
},
private apiCallService: ApiCallService,
) {}
}
ApiCallService is already added to the module provider but the service keeps returning undefined when I try to access it.
I have tried to add the FPCT class as a provider but nest complains about it not being able to define the first parameter(the payload object)
The first parameter(the payload object) is going to be passed from a controller

You could write it like this:
#Module({
providers: [
FPCT,
ApiCallService,
{
provide: 'payload',
useValue: {
isLive: true,
exchangeReference: '1234',
},
},
],
})
export class MyModule {}
// ...
#Injectable()
export class FPCT {
constructor(
#Inject('payload')
payload: {
isLive: boolean;
exchangeReference?: string | number;
},
private apiCallService: ApiCallService,
) {}
}
You could use ModuleRef#get, or app.get(ApiCallService).
It all depends on what you're trying to do, which wasn't clear.

Related

Swagger codegen can't correctly generate a controller method with array of "oneof" response

I'm trying to generate a controller method through NestJS' swagger decorators.
A method should return an array of mixed ClassA and ClassB types, and the only solution I could find that returns that kind of response is
#ApiResponse({
isArray: true,
schema: {
items: {
oneOf: [
{ $ref: getSchemaPath(ClassA) },
{ $ref: getSchemaPath(ClassB) },
]
}
}
})
public generatedMethod(body: ..., observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<Array<ClassA | ClassB>>>;
but at the same time generates the following import:
import { ClassAClassB } from '../model/classAClassB';
without generating the class file.
What am I missing in the schema definition?
Thanks in advance
Any other configuration would return either a generated "mixed class type", but not in an array format
export type MixedClass = ClassA | ClassB
public generatedMethod(body: ..., observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<MixedClass>>;
or a whole wrong output
public generatedMethod(body: ..., observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<Array<>>>
public generatedMethod(body: ..., observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>
In order to use other schemas, you need to add them using #ApiExtraModels at the top of the class.
#ApiExtraModels(ClassA, ClassB) // Add this decorator
#Controller()
export class MyController {
#ApiResponse({
status: 200, // => **is added**
isArray: true,
schema: {
items: {
oneOf: [
{ $ref: getSchemaPath(ClassA) },
{ $ref: getSchemaPath(ClassB) },
]
}
}
})
myMethod(#Body body) {
...
}
}

Nestjs-Query Keycloak-connect

Nest-Keycloak-connect is protecting all DTOs, Public() Decorator not working when querying a housing. Getting always Unauthorized when i want to query public items.
import { ID, ObjectType } from "#nestjs/graphql";
import { FilterableField, Relation } from "#ptc-org/nestjs-query-graphql";
import { Public, Unprotected } from "nest-keycloak-connect";
import { PropertyDTO } from "../property/property.dto";
#Public()
#ObjectType("Housing")
#Relation("property",() => PropertyDTO, {disableRemove: true})
export class HousingDTO {
#FilterableField(() => ID)
id !: number
}
Housing Module
import { Module } from "#nestjs/common";
import { NestjsQueryGraphQLModule } from "#ptc-org/nestjs-query-graphql";
import { NestjsQueryTypeOrmModule } from "#ptc-org/nestjs-query-typeorm";
import { HousingDTO } from "./housing.dto";
import { HousingEntity } from "./housing.entity";
import { HousingInputDTO } from "./housing.input.dto";
#Module({
imports: [
NestjsQueryGraphQLModule.forFeature({
imports: [NestjsQueryTypeOrmModule.forFeature([HousingEntity])],
resolvers: [{
DTOClass: HousingDTO,
EntityClass: HousingEntity,
CreateDTOClass: HousingInputDTO,
}]
})
],
})
export class HousingModule {}
Keycloak configured and working when logged in. But i need also to query the housing when not logged in.
#Module({
imports: [
....
HousingModule,
....
KeycloakConnectModule.register({
authServerUrl: 'https://auth.xxx.com/auth/',
realm: 'xxx',
clientId: 'xxx',
secret: process.env.KEYCLOAK_SECRET,
policyEnforcement: PolicyEnforcementMode.PERMISSIVE, // optional
tokenValidation: TokenValidation.ONLINE, // optional
logLevels: ['warn', 'error'],
}),
],
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
{
provide: APP_GUARD,
useClass: ResourceGuard,
},
{
provide: APP_GUARD,
useClass: RoleGuard,
}
],
})
export class AppModule {}
It won't work, because you must use the Public decorator on the endpoints.
Simply put the decorator on one of your controller endpoints to achieve the unauthenticated access.
Example for that:
import { Resource, Roles, Scopes, Public, RoleMatchingMode } from 'nest-keycloak-connect';
import { Controller, Get, Delete, Put, Post, Param } from '#nestjs/common';
import { Product } from './product';
import { ProductService } from './product.service';
#Controller()
#Resource(Product.name)
export class ProductController {
constructor(private service: ProductService) {}
/**
* if you use decorator here, the endpoint will
* be accessible without authentication
**/
#Get()
#Public() // <-- Used here
async findAll() {
return await this.service.findAll();
}
}
You can read more here.

How to validate Dynamic key -> value DTO validation in nest js?

import { ApiProperty } from '#nestjs/swagger';
import { IsString, ValidateNested } from 'class-validator';
export class TestDto {
#ApiProperty()
test: string;
}
export class UserReqDto {
#ApiProperty()
#IsString()
id: string;
#ApiProperty()
#ValidateNested({ each: true })
data: object;
}
const sampleData = {
id: 'asbd',
data: {
['any dynamic key 1']: {
test: '1',
},
['any dynamic key 2']: {
test: '2',
},
},
};
Here UserReqDto is my main DTO and TestDto is child DTO.
I need to validate sampleData type of data.
How can I do that?
in data field i need to validate object of TestDto type's objects
You can use Map<string, TestDto> as type for data field:
#ApiProperty()
#ValidateNested({ each: true })
data: Map<string, TestDto>
Note: Nested object must be an instance of a class, otherwise #ValidateNested won't know what class is target of validation so you can use class-transformer to transform data value to instance of TestDto.
#ApiProperty()
#ValidateNested({ each: true })
#Type(() => TestDto)
data: Map<string, TestDto>

validating an array of uuids in nestjs swagger body

It sounds like a quite simple question but I've been searching for a solution for a very long time now. I want to validate an array of UUIDs in an endpoint.
Like this:
["9322c384-fd8e-4a13-80cd-1cbd1ef95ba8", "986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e"]
I have already successfully implemented it as a JSON object { "id": ["9322c384-fd8e-4a13-80cd-1cbd1ef95ba8", "986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e"]} with the following code:
public getIds(
#Body(ValidationPipe)
uuids: uuidDto
) {
console.log(uuids);
}
import { ApiProperty } from '#nestjs/swagger';
import { IsUUID } from 'class-validator';
export class uuidDto {
#IsUUID('4', { each: true })
#ApiProperty({
type: [String],
example: [
'9322c384-fd8e-4a13-80cd-1cbd1ef95ba8',
'986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e',
],
})
id!: string;
}
But unfortunately I can't customize the function that calls that endpoint. So I need a solution to only validate a array of uuids.
instead of type string , write string[]. like below:
import { ApiProperty } from '#nestjs/swagger';
import { IsUUID } from 'class-validator';
export class uuidDto {
#IsUUID('4', { each: true })
#ApiProperty({
type: string[],
example: [
'9322c384-fd8e-4a13-80cd-1cbd1ef95ba8',
'986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e',
],
})
id!: string[];
}
You can build a custom validation pipe for it:
#Injectable()
export class CustomClassValidatorArrayPipe implements PipeTransform {
constructor(private classValidatorFunction: (any)) {}
transform(value: any[], metadata: ArgumentMetadata) {
const errors = value.reduce((result, value, index) => {
if (!this.classValidatorFunction(value))
result.push(`${value} at index ${index} failed validation`)
return result
}, [])
if (errors.length > 0) {
throw new BadRequestException({
status: HttpStatus.BAD_REQUEST,
message: 'Validation failed',
errors
});
}
return value;
}
}
In your controller:
#Post()
createExample(#Body(new CustomClassValidatorArrayPipe(isUUID)) body: string[]) {
...
}
Ensure to use the lowercase functions from class-validator. It has to be isUUID instead of IsUUID. (This is used for the manual validation with class-validator.)
CustomClassValidatorArrayPipe is build modular. You can validate any other type with it. For example a MongoId: #Body(new CustomClassValidatorArrayPipe(isMongoId)) body: ObjectId[]
Result
If you send this:
POST http://localhost:3000/example
Content-Type: application/json
[
"986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e",
"123",
"test"
]
Server will reply:
{
"status": 400,
"message": "Validation failed",
"errors": [
"123 at index 1 failed validation",
"test at index 2 failed validation"
]
}

Nest js authorization where should i give user calss property of Roles

in nest js Documentation i've read about Basic RBAC implementation but the last thing of this section says
"To make sure this example works, your User class must look as follows"
class User {
roles: Role[];
}
where should this line is going to be in
Check out authentication part of documentation. In implementing passport strategies paragraph you have UsersService defined like this:
import { Injectable } from '#nestjs/common';
// This should be a real class/interface representing a user entity
export type User = any;
#Injectable()
export class UsersService {
private readonly users = [
{
userId: 1,
username: 'john',
password: 'changeme',
},
{
userId: 2,
username: 'maria',
password: 'guess',
},
];
async findOne(username: string): Promise<User | undefined> {
return this.users.find(user => user.username === username);
}
}
You can create user.ts file near this service and import it here instead of defining type. How this class should look depends on source from which you get it. In this example users are hard-coded but usually that would be some kind of database entity.
Hard-coded example
For this hard-coded example I would do User class like this:
user.ts
import { Role } from "./role.enum";
export class User {
userId: number;
username: string;
password: string;
roles: Role[];
}
Where roles are in enum defined in authorization part of documentation
role.enum.ts
export enum Role {
User = 'user',
Admin = 'admin',
}
All this is joined inside service like this:
users.service.ts
import { Injectable } from '#nestjs/common';
import { User } from './user.entity';
import { Role } from "./role.enum";
#Injectable()
export class UsersService {
private readonly users: User[] = [
{
userId: 1,
username: 'john',
password: 'changeme',
roles: [Role.Admin]
},
{
userId: 2,
username: 'maria',
password: 'guess',
roles: [Role.User]
},
];
async findOne(username: string): Promise<User | undefined> {
return this.users.find(user => user.username === username);
}
}
Database example
Usually you would use some kind of database (more on database integration here), when using TypeOrm those classes would look like this:
user.entity.ts
import { Role } from "../role.enum";
import { Injectable } from '#nestjs/common';
import { UserEntity } from '../hard-coded/user';
import { InjectRepository } from "#nestjs/typeorm";
import { Repository } from "typeorm";
#Entity()
export class UserEntity {
#PrimaryGeneratedColumn() userId: number;
#Column() username: string;
#Column() password: string;
// should have some kind of join table
#ManyToMany() roles: Role[];
}
users.service.ts
#Injectable()
export class UsersService {
constructor(#InjectRepository(UserEntity) private usersRepository: Repository<UserEntity>){}
async findOne(username: string): Promise<UserEntity | undefined> {
return this.usersRepository.findOne({ username });
}
}

Resources