nest+swagger apibody not displayed - swagger

I'm using nest and swagger and I have a question.
I wrote the code as below. But in swagger nothing render in request body.
Is it a picktype problem?
Please tell me how to solve it.
thank you
#ApiOperation({ summary: 'about payment' })
#ApiBody({ type: PaymentDto })
#ApiResponse({
type: PaymentResponseDto,
})
#Patch('payment')
async payment(
#Body() paymentDto: PaymentDto,
)
export class PaymentDto extends PickType(Renewal, [
'renewalMethod',
'renewalPrice',
]) {}
#Entity()
export class Renewal{
#ApiProperty({
example: '9,900',
description: 'price',
})
#IsNumber()
#Column({ default: 0 })
renewalPrice: number;
#ApiProperty({
example: 'subs',
description: 'type',
})
#IsEnum(RenewalType)
#Column({ type: 'enum', enum: RenewalType })
renewalMethod: RenewalType;
}
result browser Image

Related

Return ONLY selected fields within a TypeORM find request

I'm struggling in returning only selected fields in my TypeORM find request.
Assuming the following request
const data = await AppDataSource.manager.find(User, {
select: {
id: true,
hash: true,
firstname: true,
lastname: false,
},
take: 10, // Just here to shrink dataset
});
The script works pretty well excepted that it return every field of my model, with default value initialized.
[
User {
prefix: 'usr',
hash: 'usr_835b0ad2-XXXXXX',
email: undefined,
accountValidated: false,
role: 'free',
myKeyOne: true,
myKeyTwo: false,
gender: 'unspecified',
lastConnexion: 2023-01-19T10:11:02.733Z,
pendingDeletion: false,
deletionDate: undefined,
firstname: 'Clément',
lastname: undefined,
password: undefined,
facebookId: undefined,
googleId: undefined,
id: 158
},
...
]
Of course, it's not usable as it, because I have extensive relations, and thus the payload would be extremely heavy.
Are you aware of a method / a way to remove all unnecessary fields ?
i.e. I'm expecting
[
User {
id: 124,
hash: 'urs_XXXX',
firstname: 'Clément',
},
...
]
In older versions of typeorm I think you need to select with an array of strings, try:
select: ["id", "hash", "firstname"],
See this older version of the docs: https://github.com/typeorm/typeorm/blob/bc60dd559ba42af083ddea17f01205c78c83c7e0/docs/find-options.md
After hours of researches I've finally found out why it behaved like this.
TypeORM relies on class definitions and typescript so...
if you have typescript default values OR if you have rewrite your constructor, all the "default" properties are injected.
Assuming a User model
❌ You should not do
#Entity({ name: 'users' })
class User {
#Column()
firstname?: string;
#Column({ nullable: true })
lastname?: string;
#Column({ unique: true, nullable: false })
email!: string;
#Column({ name: 'account_validated', nullable: false})
accountValidated?: boolean = false
//Your other fields...
}
✅ You should do
#Entity({ name: 'users' })
class User {
#Column()
firstname?: string;
#Column({ nullable: true })
lastname?: string;
#Column({ unique: true, nullable: false })
email!: string;
// Use default argument of the decorator
#Column({ name: 'account_validated', nullable: false, default: false})
accountValidated?: boolean
//Your other fields...
}
And if you need in some way to init a default, then create a public static method which return the Entity instead of using the constructor.
#Entity({ name: 'users' })
class User {
//...fields
public static init(...params): User {
let _user = new User()
//...populate your object
return _user
}
}

NestJs Swagger body array with mixed types

I have created an endpoint to create bouchers but they can be created with any feature and each feature has a diferent type.
Example:
POST /code
{
"code": "<String>"
"features": [{
"type": "expiredDate",
"expiredDate": "<ISODate>"
}, {
"type": "referrer",
"refererId": "<UUID>"
}]
}
But it could also be used like this:
POST /code
{
"code": "<String>"
"features": [{
"type": "referrer",
"refererId": "<UUID>"
}]
}
or
POST /code
{
"code": "<String>"
"features": [{
"type": "motivated-purchase",
"pursache": "<UUID>"
}]
}
or... many similars things you know
How can I especify it in nestjs for swagger? I tried with anyOf but I can't get it
If someone knows how to do it with the openapi nomenclature, it could also help me
I answer myself...
First you should create the diferents features DTO
export default class ExpiredDateFeatureDto {
#ApiProperty({ type: String, required: true, enum: ['expiredDate'] })
readonly type: string;
#ApiProperty({ type: String, required: true })
readonly expiredDate: string;
}
export default class ReferrerFeatureDto {
#ApiProperty({ type: String, required: true, enum: ['referrer'] })
readonly type: string;
#ApiProperty({ type: String, required: true })
readonly refererId: string;
}
export default class ExpiredDateFeatureDto {
#ApiProperty({ type: String, required: true, enum: ['motivated-purchase'] })
readonly type: string;
#ApiProperty({ type: String, required: true })
readonly pursache: string;
}
And add it in the main dto
#ApiExtraModels(ExpiredDateFeatureDto, ReferrerFeatureDto, ExpiredDateFeatureDto)
export default class CouponDto {
#ApiProperty()
readonly code: string;
#ApiProperty({
type: 'array',
items: {
oneOf: [{
$ref: getSchemaPath(ExpiredDateFeatureDto)
}, {
$ref: getSchemaPath(ReferrerFeatureDto)
}, {
$ref: getSchemaPath(ExpiredDateFeatureDto)
}],
},
})
#IsOptional()
readonly features: FeatureDto[];
}
It is very important to add the annotation #ApiExtraModels in the main dto

How can I display multiple ResponseDTOs' schemas in Swagger/NestJS?

I have this route which can return one of these two different DTOs:
#Get()
#ApiQuery({ name: 'legacy', description: "'Y' to get houses legacy" })
async findAllHouses(
#Query('legacy') legacy: string,
): Promise<HousesDto[] | HousesLegacyDto[]> {
...
}
I want to display both of these ResponseDTOs in swagger.
I've tried this decorator:
#ApiOkResponse({
schema: { oneOf: refs(HousesDto, HousesLegacyDto) },
})
// OR
#ApiOkResponse({
schema: {
oneOf: [
{ $ref: getSchemaPath(HousesDto) },
{ $ref: getSchemaPath(HousesLegacyDto) },
],
},
})
with #ApiExtraModels() on top of DTO classes and #ApiProperty() on each properties.
But I still get empty objects in Swagger and I suppose it would not have even taken array types in consideration.
How can I display both of these schemas properly?
Seems to me like a lot of very obscure solutions have been posted here and there, so I will try to clarify what needs to be done.
You have two DTOs:
export class SomeStatusDto {
#ApiProperty({
description: 'Id',
example: 1,
})
#IsNumber()
id: number;
#ApiProperty({
description: 'Status',
example: 'in_progress',
})
#IsString()
status: string;
}
export class ErrorStatusDto {
#ApiProperty({
description: 'Id',
example: 1,
})
#IsNumber()
id: number;
#ApiProperty({
description: 'Error',
example: 'Some error string',
})
#IsString()
error: string;
}
Then you have your controller:
#UseGuards(AccountTypesGuard)
#ApiOperation({ summary: 'Get status of...' })
#Get('status')
#ApiExtraModels(SomeStatusDto, ErrorStatusDto)
#ApiOkResponse({
schema: { anyOf: refs(SomeStatusDto, ErrorStatusDto) },
})
async getPullStatus(
#Request() req,
#Param('id', ParseIntPipe) someId: number,
): Promise<SomeStatusDto | ErrorStatusDto> {
// check if someId belongs to user
const idBelongsToUser = await this.myService.validateSomeId(
req.user.id,
someId,
);
if (!idBelongsToUser) {
throw new ForbiddenException(
`SomeId does not belong to user (someId=${someId}, userId=${req.user.id})`,
);
}
const key = `status-${someId}`;
const response = await this.redisService.getByKey(key);
return response ? response : {};
}
Note the solution below. You need to reference the DTOs as #ApiExtraModels() and then you can add them as anyOf: refs(...) in your schema.
#ApiExtraModels(SomeStatusDto, ErrorStatusDto)
#ApiOkResponse({
schema: { anyOf: refs(SomeStatusDto, ErrorStatusDto) },
})
Hope this helps somebody :)
so I encountered a similar issue and this is how you could get the output shown in the image above.
Using the #ApiResponse decorator you could set the two responses using the examples property, try the code sample below
#ApiResponse({
status: 200,
description: 'Successful response',
content: {
'application/json': {
examples: {
HousesDto: { value: HousesDto },
HousesLegacyDto: { value: HousesLegacyDto },
},
},
},
})

TypeORM QueryBuilder add parameter that is in string

I want to add query parameter to my queryBuilder but it returns error invalid input syntax for type json. What is the correct way to send this parameter?
My query:
const foundedUserContacts = await this.userBookContactRepository
.createQueryBuilder('userBookContacts')
.select(['userBookContacts.contacts'])
.andWhere(`contacts #> '[{ "phoneNumber": :phoneNumber }]'`, {
phoneNumber: user.phoneNumber,
})
.getMany();
This works ok:
const foundedUserContacts = await this.userBookContactRepository
.createQueryBuilder('userBookContacts')
.select(['userBookContacts.contacts'])
.andWhere(`contacts #> '[{ "phoneNumber": "${user.phoneNumber}" }]'`)
.getMany();
My schema is:
#Entity({ name: 'userBookContacts' })
#Unique('userUuid_userPhone', ['userUuid', 'phoneNumber'])
export class UserBookContact {
#PrimaryGeneratedColumn()
id: number;
#Column()
userUuid: string;
#Index()
#Column()
phoneNumber: string;
#Column({
type: 'jsonb',
nullable: true,
default: '[]',
})
contacts: UserContact[];
}

Loopback POST array of entry?

I want to insert 10 entries with one query against 10 queries.
I read that it's possible to do it by sending an array like this :
But I get this error:
Do I need to set something? I don't know what to do at all.
Repo with a sample : https://github.com/mathias22osterhagen22/loopback-array-post-sample
Edit:
people-model.ts:
import {Entity, model, property} from '#loopback/repository';
#model()
export class People extends Entity {
#property({
type: 'number',
id: true,
generated: true,
})
id?: number;
#property({
type: 'string',
required: true,
})
name: string;
constructor(data?: Partial<People>) {
super(data);
}
}
export interface PeopleRelations {
// describe navigational properties here
}
export type PeopleWithRelations = People & PeopleRelations;
The problem with your code was :
"name": "ValidationError", "message": "The People instance is not
valid. Details: 0 is not defined in the model (value: undefined);
1 is not defined in the model (value: undefined); name can't be
blank (value: undefined).",
Here in above as in your #requestBody schema, you are applying to insert a single object property, where as in your body are sending the array of [people] object.
As you can see in your people.model.ts you have declared property name to be required, so system finds for the property "name", which obviously not available in the given array of object as primary node.
As you are passing index array, so its obvious error that you don't have any property named 0 or 1, so it throws error.
The below is the code hat you should apply to get insert the multiple, items of the type.
#post('/peoples', {
responses: {
'200': {
description: 'People model instance',
content: {
'application/json': {
schema: getModelSchemaRef(People)
}
},
},
},
})
async create(
#requestBody({
content: {
'application/json': {
schema: {
type: 'array',
items: getModelSchemaRef(People, {
title: 'NewPeople',
exclude: ['id'],
}),
}
},
},
})
people: [Omit<People, 'id'>]
): Promise<{}> {
people.forEach(item => this.peopleRepository.create(item))
return people;
}
You can also use this below
Promise<People[]> {
return await this.peopleRepository.createAll(people)
}
You can pass the array of your people model by modifying the request body.If you need more help you can leave comment.
I think you have a clear solution now. "Happy Loopbacking :)"

Resources