Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/common/decorators/api.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,36 @@ export function Api(options: ApiOptions): MethodDecorator {
// Attempt to infer the request body type from the method's parameter types
// if it hasn't been explicitly provided in the options.
// This looks for DTO or Input classes in the method signature.
// IMPORTANT: Skip DTOs that are specified in queriesFrom or pathParamsFrom
if (!options.bodyType) {
try {
// Build list of DTOs used for queries/path params to exclude from body inference
const queryParamTypes = [];
if (options.queriesFrom) {
if (Array.isArray(options.queriesFrom)) {
queryParamTypes.push(...options.queriesFrom);
} else {
queryParamTypes.push(options.queriesFrom);
}
}

const pathParamTypes = [];
if (options.pathParamsFrom) {
if (Array.isArray(options.pathParamsFrom)) {
pathParamTypes.push(...options.pathParamsFrom);
} else {
pathParamTypes.push(options.pathParamsFrom);
}
}

const excludedTypes = [...queryParamTypes, ...pathParamTypes];

const paramTypes: any[] = Reflect.getMetadata('design:paramtypes', target, propertyKey) || [];
const inferred = paramTypes.find((t) => {
if (!t) return false;
const isPrimitive = [String, Number, Boolean, Array, Object].includes(t);
// Skip if this type is used for query or path params
if (excludedTypes.includes(t)) return false;
return !isPrimitive && /Dto|Input/i.test(t.name || '');
});
if (inferred) {
Expand Down
48 changes: 26 additions & 22 deletions src/jobs/dto/job-order-by.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsOptional, IsEnum } from 'class-validator';
import {Field} from '@/common/decorators/field.decorator';

/**
* Sort order enum for job queries
Expand All @@ -23,48 +22,53 @@ export enum SortOrder {
* - company: Company name (alphabetical)
*/
export class JobOrderByDto {
@ApiProperty({
@Field({
name: 'createdAt',
description: 'Sort by creation date',
enum: SortOrder,
required: false,
optional: true,
inQuery: true,
isEnum: {entity: SortOrder},
})
@IsOptional()
@IsEnum(SortOrder)
createdAt?: SortOrder;

@ApiProperty({
@Field({
name: 'updatedAt',
description: 'Sort by last update date',
enum: SortOrder,
required: false,
optional: true,
inQuery: true,
isEnum: {entity: SortOrder},
})
@IsOptional()
@IsEnum(SortOrder)
updatedAt?: SortOrder;

@ApiProperty({
@Field({
name: 'postedAt',
description: 'Sort by original posting date',
enum: SortOrder,
required: false,
optional: true,
inQuery: true,
isEnum: {entity: SortOrder},
})
@IsOptional()
@IsEnum(SortOrder)
postedAt?: SortOrder;

@ApiProperty({
@Field({
name: 'title',
description: 'Sort by job title',
enum: SortOrder,
required: false,
optional: true,
inQuery: true,
isEnum: {entity: SortOrder},
})
@IsOptional()
@IsEnum(SortOrder)
title?: SortOrder;

@ApiProperty({
@Field({
name: 'company',
description: 'Sort by company name',
enum: SortOrder,
required: false,
optional: true,
inQuery: true,
isEnum: {entity: SortOrder},
})
@IsOptional()
@IsEnum(SortOrder)
company?: SortOrder;
}
8 changes: 3 additions & 5 deletions src/jobs/job.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class JobController {
description: 'Retrieves a paginated list of job listings with optional filtering',
paginatedResponseType: JobDto,
envelope: true,
queriesFrom: [PaginationArgs, JobFilterQueryDto],
queriesFrom: [PaginationArgs, JobFilterQueryDto, JobOrderByDto],
})
async findAll(
@Query() paginationArgs: PaginationArgs,
Expand Down Expand Up @@ -86,7 +86,7 @@ export class JobController {
pathParamsFrom: CompanyPathParamsDto,
paginatedResponseType: JobDto,
envelope: true,
queriesFrom: [PaginationArgs],
queriesFrom: [PaginationArgs, JobOrderByDto],
})
async findByCompany(
@Param('companyId', ParseIntPipe) companyId: number,
Expand All @@ -107,7 +107,7 @@ export class JobController {
pathParamsFrom: TagPathParamsDto,
paginatedResponseType: JobDto,
envelope: true,
queriesFrom: [PaginationArgs],
queriesFrom: [PaginationArgs, JobOrderByDto],
})
async findByTag(@Param('tagName') tagName: string, @Query() paginationArgs: PaginationArgs, @Query() orderBy: JobOrderByDto) {
return this.jobService.findByTag(tagName, paginationArgs, orderBy);
Expand All @@ -123,8 +123,6 @@ export class JobController {
responses: [{status: 200, description: 'Jobs fetched and notifications sent.'}],
})
async fetchJobs(): Promise<{redditJobs: any[]; web3CareerJobs: any[]; summary: any}> {
6;

// Fetch Reddit jobs
const redditSubreddits = ['remotejs', 'remotejobs', 'forhire', 'jobs', 'webdevjobs'];
const redditPosts = await this.redditService.fetchRedditPosts(redditSubreddits);
Expand Down