Skip to content

Commit e8096c0

Browse files
jobs: refactor controllers/services to use unified pagination and auto param/query decorators
1 parent 8dcb9b8 commit e8096c0

File tree

5 files changed

+149
-120
lines changed

5 files changed

+149
-120
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Field } from '../../common/decorators/field.decorator';
2+
3+
export class JobFilterQueryDto {
4+
@Field({
5+
name: 'search',
6+
description: 'Search in job title and description',
7+
isString: { message: 'search must be a string.' },
8+
optional: true,
9+
inQuery: true,
10+
type: String,
11+
})
12+
search?: string;
13+
14+
@Field({
15+
name: 'companyId',
16+
description: 'Filter by company ID',
17+
isInt: { message: 'companyId must be an integer.' },
18+
optional: true,
19+
inQuery: true,
20+
type: Number,
21+
})
22+
companyId?: number;
23+
24+
@Field({
25+
name: 'location',
26+
description: 'Filter by location',
27+
isString: { message: 'location must be a string.' },
28+
optional: true,
29+
inQuery: true,
30+
type: String,
31+
})
32+
location?: string;
33+
34+
@Field({
35+
name: 'isRemote',
36+
description: 'Filter by remote status',
37+
isBoolean: { message: 'isRemote must be a boolean.' },
38+
optional: true,
39+
inQuery: true,
40+
type: Boolean,
41+
})
42+
isRemote?: boolean;
43+
44+
@Field({
45+
name: 'tags',
46+
description: 'Filter by tag names (comma-separated)',
47+
isString: { message: 'tags must be a comma-separated string.' },
48+
optional: true,
49+
inQuery: true,
50+
type: String,
51+
})
52+
tags?: string;
53+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Field } from '../../common/decorators/field.decorator';
2+
3+
export class CompanyPathParamsDto {
4+
@Field({
5+
name: 'companyId',
6+
description: 'Company ID',
7+
isInt: { message: 'companyId must be an integer.' },
8+
inPath: true,
9+
type: Number,
10+
})
11+
companyId!: number;
12+
}
13+
14+
export class TagPathParamsDto {
15+
@Field({
16+
name: 'tagName',
17+
description: 'Tag name',
18+
isString: { message: 'tagName must be a string.' },
19+
inPath: true,
20+
type: String,
21+
})
22+
tagName!: string;
23+
}
24+
25+
export class JobIdPathParamsDto {
26+
@Field({
27+
name: 'id',
28+
description: 'Job ID',
29+
isInt: { message: 'id must be an integer.' },
30+
inPath: true,
31+
type: Number,
32+
})
33+
id!: number;
34+
}

src/jobs/job.controller.ts

Lines changed: 55 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,25 @@ import {
1212
HttpCode,
1313
HttpStatus,
1414
} from '@nestjs/common';
15-
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery, ApiBearerAuth } from '@nestjs/swagger';
15+
import { ApiTags, ApiQuery, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
16+
import { Api } from '../common/decorators/api.decorator';
1617
import { JobService } from './job.service';
1718
import { CreateJobDto } from './dto/create-job.dto';
1819
import { UpdateJobDto } from './dto/update-job.dto';
1920
import { JobOrderByDto } from './dto/job-order-by.dto';
21+
import { CompanyPathParamsDto, TagPathParamsDto, JobIdPathParamsDto } from './dto/job-path-params.dto';
22+
import { JobFilterQueryDto } from './dto/job-filter-query.dto';
2023
import { RedisAuthGuard } from '../auth/redis-auth.guard';
2124
import { UserEntity as User } from '../common/decorators/user.decorator';
2225
import { ApiPaginationQuery } from './../common/decorators/api-nested-query.decorator';
2326
import { PaginationArgs } from '../common/pagination/pagination.args';
2427
import { Web3CareerService } from './web3career.service';
2528
import { RedditService } from './reddit.service';
2629
import { NotificationsService } from './notifications.service';
30+
import { PaginatedResponse } from '../common/models/paginated-response';
31+
import { CreateJobDto as JobDto } from './dto/create-job.dto';
32+
33+
class PaginatedJobResponse extends PaginatedResponse(JobDto) {}
2734

2835
/**
2936
* Job Controller
@@ -49,11 +56,11 @@ export class JobController {
4956
* Fetch new jobs from Reddit and Web3Career, store them, and send notifications
5057
*/
5158
@Get('fetch')
52-
@ApiOperation({
59+
@Api({
5360
summary: 'Fetch new jobs from Reddit and Web3Career',
5461
description: 'Fetches new jobs from both sources, stores them, and sends notifications.',
62+
responses: [{ status: 200, description: 'Jobs fetched and notifications sent.' }],
5563
})
56-
@ApiResponse({ status: 200, description: 'Jobs fetched and notifications sent.' })
5764
async fetchJobs(): Promise<{ redditJobs: any[]; web3CareerJobs: any[] }> {
5865
// Fetch Reddit jobs
5966
const redditSubreddits = [
@@ -83,15 +90,13 @@ export class JobController {
8390
* Retrieves all job listings for a specific company
8491
*/
8592
@Get('company/:companyId')
86-
@ApiOperation({
93+
@Api({
8794
summary: 'Get jobs by company',
8895
description: 'Retrieves all job listings for a specific company',
89-
})
90-
@ApiParam({ name: 'companyId', description: 'Company ID', type: Number })
91-
@ApiPaginationQuery()
92-
@ApiResponse({
93-
status: 200,
94-
description: 'Company jobs retrieved successfully',
96+
pathParamsFrom: CompanyPathParamsDto,
97+
paginatedResponseType: JobDto,
98+
envelope: true,
99+
queriesFrom: [PaginationArgs],
95100
})
96101
async findByCompany(
97102
@Param('companyId', ParseIntPipe) companyId: number,
@@ -116,15 +121,13 @@ export class JobController {
116121
* Retrieves all job listings that have a specific tag
117122
*/
118123
@Get('tag/:tagName')
119-
@ApiOperation({
124+
@Api({
120125
summary: 'Get jobs by tag',
121126
description: 'Retrieves all job listings that have a specific tag',
122-
})
123-
@ApiParam({ name: 'tagName', description: 'Tag name', type: String })
124-
@ApiPaginationQuery()
125-
@ApiResponse({
126-
status: 200,
127-
description: 'Tagged jobs retrieved successfully',
127+
pathParamsFrom: TagPathParamsDto,
128+
paginatedResponseType: JobDto,
129+
envelope: true,
130+
queriesFrom: [PaginationArgs],
128131
})
129132
async findByTag(
130133
@Param('tagName') tagName: string,
@@ -149,23 +152,16 @@ export class JobController {
149152
* Creates a new job entry in the database with the provided information
150153
*/
151154
@Post()
152-
@UseGuards(RedisAuthGuard)
153-
@ApiBearerAuth()
154-
@ApiOperation({
155+
@Api({
155156
summary: 'Create a new job listing',
156157
description: 'Creates a new job listing with title, description, company info, and other details',
157-
})
158-
@ApiResponse({
159-
status: 201,
160-
description: 'Job successfully created',
161-
})
162-
@ApiResponse({
163-
status: 400,
164-
description: 'Invalid job data provided',
165-
})
166-
@ApiResponse({
167-
status: 401,
168-
description: 'Unauthorized - authentication required',
158+
bodyType: CreateJobDto,
159+
authenticationRequired: true,
160+
responses: [
161+
{ status: 201, description: 'Job successfully created' },
162+
{ status: 400, description: 'Invalid job data provided' },
163+
{ status: 401, description: 'Unauthorized - authentication required' },
164+
],
169165
})
170166
@HttpCode(HttpStatus.CREATED)
171167
async create(@Body() createJobDto: CreateJobDto, @User() user: any) {
@@ -177,19 +173,12 @@ export class JobController {
177173
* Supports filtering by company, location, remote status, and tags
178174
*/
179175
@Get()
180-
@ApiOperation({
176+
@Api({
181177
summary: 'Get all job listings',
182178
description: 'Retrieves a paginated list of job listings with optional filtering',
183-
})
184-
@ApiPaginationQuery()
185-
@ApiQuery({ name: 'search', required: false, description: 'Search in job title and description' })
186-
@ApiQuery({ name: 'companyId', required: false, description: 'Filter by company ID' })
187-
@ApiQuery({ name: 'location', required: false, description: 'Filter by location' })
188-
@ApiQuery({ name: 'isRemote', required: false, description: 'Filter by remote status', type: Boolean })
189-
@ApiQuery({ name: 'tags', required: false, description: 'Filter by tag names (comma-separated)' })
190-
@ApiResponse({
191-
status: 200,
192-
description: 'List of job listings retrieved successfully',
179+
paginatedResponseType: JobDto,
180+
envelope: true,
181+
queriesFrom: [PaginationArgs, JobFilterQueryDto],
193182
})
194183
async findAll(
195184
@Query() paginationArgs: PaginationArgs,
@@ -228,18 +217,14 @@ export class JobController {
228217
* Returns detailed information about a single job including company, tags, and metadata
229218
*/
230219
@Get(':id')
231-
@ApiOperation({
220+
@Api({
232221
summary: 'Get job by ID',
233222
description: 'Retrieves a specific job listing with all related data',
234-
})
235-
@ApiParam({ name: 'id', description: 'Job ID', type: Number })
236-
@ApiResponse({
237-
status: 200,
238-
description: 'Job details retrieved successfully',
239-
})
240-
@ApiResponse({
241-
status: 404,
242-
description: 'Job not found',
223+
pathParamsFrom: JobIdPathParamsDto,
224+
responses: [
225+
{ status: 200, description: 'Job details retrieved successfully' },
226+
{ status: 404, description: 'Job not found' },
227+
],
243228
})
244229
async findOne(@Param('id', ParseIntPipe) id: number) {
245230
return this.jobService.findOne(id);
@@ -250,24 +235,17 @@ export class JobController {
250235
* Updates job information - requires authentication
251236
*/
252237
@Put(':id')
253-
@UseGuards(RedisAuthGuard)
254-
@ApiBearerAuth()
255-
@ApiOperation({
238+
@Api({
256239
summary: 'Update job listing',
257240
description: 'Updates an existing job listing with new information',
258-
})
259-
@ApiParam({ name: 'id', description: 'Job ID', type: Number })
260-
@ApiResponse({
261-
status: 200,
262-
description: 'Job updated successfully',
263-
})
264-
@ApiResponse({
265-
status: 404,
266-
description: 'Job not found',
267-
})
268-
@ApiResponse({
269-
status: 401,
270-
description: 'Unauthorized - authentication required',
241+
authenticationRequired: true,
242+
bodyType: UpdateJobDto,
243+
pathParamsFrom: JobIdPathParamsDto,
244+
responses: [
245+
{ status: 200, description: 'Job updated successfully' },
246+
{ status: 404, description: 'Job not found' },
247+
{ status: 401, description: 'Unauthorized - authentication required' },
248+
],
271249
})
272250
async update(@Param('id', ParseIntPipe) id: number, @Body() updateJobDto: UpdateJobDto, @User() user: any) {
273251
return this.jobService.update(id, updateJobDto, user.id);
@@ -278,24 +256,16 @@ export class JobController {
278256
* Removes a job listing from the database - requires authentication
279257
*/
280258
@Delete(':id')
281-
@UseGuards(RedisAuthGuard)
282-
@ApiBearerAuth()
283-
@ApiOperation({
259+
@Api({
284260
summary: 'Delete job listing',
285261
description: 'Removes a job listing from the database',
286-
})
287-
@ApiParam({ name: 'id', description: 'Job ID', type: Number })
288-
@ApiResponse({
289-
status: 200,
290-
description: 'Job deleted successfully',
291-
})
292-
@ApiResponse({
293-
status: 404,
294-
description: 'Job not found',
295-
})
296-
@ApiResponse({
297-
status: 401,
298-
description: 'Unauthorized - authentication required',
262+
authenticationRequired: true,
263+
pathParamsFrom: JobIdPathParamsDto,
264+
responses: [
265+
{ status: 200, description: 'Job deleted successfully' },
266+
{ status: 404, description: 'Job not found' },
267+
{ status: 401, description: 'Unauthorized - authentication required' },
268+
],
299269
})
300270
@HttpCode(HttpStatus.OK)
301271
async remove(@Param('id', ParseIntPipe) id: number, @User() user: any) {

src/jobs/job.module.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { JobController } from './job.controller';
55
import { CommonModule } from '../common/common.module';
66
import { Web3CareerService } from './web3career.service';
77
import { RedditService } from './reddit.service';
8-
import { NotificationsService } from './notifications.service';
8+
// Notifications are now provided globally by NotificationsModule
99

1010
/**
1111
* Job Module
@@ -25,7 +25,7 @@ import { NotificationsService } from './notifications.service';
2525
CommonModule, // Provides database, authentication, and other shared services
2626
],
2727
controllers: [JobController], // REST API endpoints
28-
providers: [JobService, Web3CareerService, RedditService, NotificationsService], // Business logic and GraphQL resolvers //JobResolver
29-
exports: [JobService, Web3CareerService, RedditService, NotificationsService], // Allow other modules to use these services
28+
providers: [JobService, Web3CareerService, RedditService], // Business logic and GraphQL resolvers //JobResolver
29+
exports: [JobService, Web3CareerService, RedditService], // Allow other modules to use these services
3030
})
3131
export class JobModule {}

0 commit comments

Comments
 (0)