Skip to content

Commit cdcdef5

Browse files
Merge pull request #10 from codebuilderinc/fix/swagger-job-endpoint-query-params
Fix Swagger GET request body issue for job endpoints
2 parents c92244d + 59f6480 commit cdcdef5

File tree

3 files changed

+53
-27
lines changed

3 files changed

+53
-27
lines changed

src/common/decorators/api.decorator.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,36 @@ export function Api(options: ApiOptions): MethodDecorator {
167167
// Attempt to infer the request body type from the method's parameter types
168168
// if it hasn't been explicitly provided in the options.
169169
// This looks for DTO or Input classes in the method signature.
170+
// IMPORTANT: Skip DTOs that are specified in queriesFrom or pathParamsFrom
170171
if (!options.bodyType) {
171172
try {
173+
// Build list of DTOs used for queries/path params to exclude from body inference
174+
const queryParamTypes = [];
175+
if (options.queriesFrom) {
176+
if (Array.isArray(options.queriesFrom)) {
177+
queryParamTypes.push(...options.queriesFrom);
178+
} else {
179+
queryParamTypes.push(options.queriesFrom);
180+
}
181+
}
182+
183+
const pathParamTypes = [];
184+
if (options.pathParamsFrom) {
185+
if (Array.isArray(options.pathParamsFrom)) {
186+
pathParamTypes.push(...options.pathParamsFrom);
187+
} else {
188+
pathParamTypes.push(options.pathParamsFrom);
189+
}
190+
}
191+
192+
const excludedTypes = [...queryParamTypes, ...pathParamTypes];
193+
172194
const paramTypes: any[] = Reflect.getMetadata('design:paramtypes', target, propertyKey) || [];
173195
const inferred = paramTypes.find((t) => {
174196
if (!t) return false;
175197
const isPrimitive = [String, Number, Boolean, Array, Object].includes(t);
198+
// Skip if this type is used for query or path params
199+
if (excludedTypes.includes(t)) return false;
176200
return !isPrimitive && /Dto|Input/i.test(t.name || '');
177201
});
178202
if (inferred) {

src/jobs/dto/job-order-by.dto.ts

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { ApiProperty } from '@nestjs/swagger';
2-
import { IsOptional, IsEnum } from 'class-validator';
1+
import {Field} from '@/common/decorators/field.decorator';
32

43
/**
54
* Sort order enum for job queries
@@ -23,48 +22,53 @@ export enum SortOrder {
2322
* - company: Company name (alphabetical)
2423
*/
2524
export class JobOrderByDto {
26-
@ApiProperty({
25+
@Field({
26+
name: 'createdAt',
2727
description: 'Sort by creation date',
2828
enum: SortOrder,
29-
required: false,
29+
optional: true,
30+
inQuery: true,
31+
isEnum: {entity: SortOrder},
3032
})
31-
@IsOptional()
32-
@IsEnum(SortOrder)
3333
createdAt?: SortOrder;
3434

35-
@ApiProperty({
35+
@Field({
36+
name: 'updatedAt',
3637
description: 'Sort by last update date',
3738
enum: SortOrder,
38-
required: false,
39+
optional: true,
40+
inQuery: true,
41+
isEnum: {entity: SortOrder},
3942
})
40-
@IsOptional()
41-
@IsEnum(SortOrder)
4243
updatedAt?: SortOrder;
4344

44-
@ApiProperty({
45+
@Field({
46+
name: 'postedAt',
4547
description: 'Sort by original posting date',
4648
enum: SortOrder,
47-
required: false,
49+
optional: true,
50+
inQuery: true,
51+
isEnum: {entity: SortOrder},
4852
})
49-
@IsOptional()
50-
@IsEnum(SortOrder)
5153
postedAt?: SortOrder;
5254

53-
@ApiProperty({
55+
@Field({
56+
name: 'title',
5457
description: 'Sort by job title',
5558
enum: SortOrder,
56-
required: false,
59+
optional: true,
60+
inQuery: true,
61+
isEnum: {entity: SortOrder},
5762
})
58-
@IsOptional()
59-
@IsEnum(SortOrder)
6063
title?: SortOrder;
6164

62-
@ApiProperty({
65+
@Field({
66+
name: 'company',
6367
description: 'Sort by company name',
6468
enum: SortOrder,
65-
required: false,
69+
optional: true,
70+
inQuery: true,
71+
isEnum: {entity: SortOrder},
6672
})
67-
@IsOptional()
68-
@IsEnum(SortOrder)
6973
company?: SortOrder;
7074
}

src/jobs/job.controller.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class JobController {
5151
description: 'Retrieves a paginated list of job listings with optional filtering',
5252
paginatedResponseType: JobDto,
5353
envelope: true,
54-
queriesFrom: [PaginationArgs, JobFilterQueryDto],
54+
queriesFrom: [PaginationArgs, JobFilterQueryDto, JobOrderByDto],
5555
})
5656
async findAll(
5757
@Query() paginationArgs: PaginationArgs,
@@ -86,7 +86,7 @@ export class JobController {
8686
pathParamsFrom: CompanyPathParamsDto,
8787
paginatedResponseType: JobDto,
8888
envelope: true,
89-
queriesFrom: [PaginationArgs],
89+
queriesFrom: [PaginationArgs, JobOrderByDto],
9090
})
9191
async findByCompany(
9292
@Param('companyId', ParseIntPipe) companyId: number,
@@ -107,7 +107,7 @@ export class JobController {
107107
pathParamsFrom: TagPathParamsDto,
108108
paginatedResponseType: JobDto,
109109
envelope: true,
110-
queriesFrom: [PaginationArgs],
110+
queriesFrom: [PaginationArgs, JobOrderByDto],
111111
})
112112
async findByTag(@Param('tagName') tagName: string, @Query() paginationArgs: PaginationArgs, @Query() orderBy: JobOrderByDto) {
113113
return this.jobService.findByTag(tagName, paginationArgs, orderBy);
@@ -123,8 +123,6 @@ export class JobController {
123123
responses: [{status: 200, description: 'Jobs fetched and notifications sent.'}],
124124
})
125125
async fetchJobs(): Promise<{redditJobs: any[]; web3CareerJobs: any[]; summary: any}> {
126-
6;
127-
128126
// Fetch Reddit jobs
129127
const redditSubreddits = ['remotejs', 'remotejobs', 'forhire', 'jobs', 'webdevjobs'];
130128
const redditPosts = await this.redditService.fetchRedditPosts(redditSubreddits);

0 commit comments

Comments
 (0)