From 01bb9cd1998805ec37c5060b861a924c7c2273ca Mon Sep 17 00:00:00 2001 From: ahnfikd7 <99137905+ahnfikd7@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:29:26 -0500 Subject: [PATCH 1/4] Added endpoint for accept-reject and moving to next stage --- .../applications/applications.controller.ts | 29 +++++++++++- .../src/applications/applications.service.ts | 45 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/apps/backend/src/applications/applications.controller.ts b/apps/backend/src/applications/applications.controller.ts index bfd8ee8..f9c769f 100644 --- a/apps/backend/src/applications/applications.controller.ts +++ b/apps/backend/src/applications/applications.controller.ts @@ -10,8 +10,9 @@ import { Body, BadRequestException, NotFoundException, + UnauthorizedException, } from '@nestjs/common'; -import { Response } from './types'; +import { ApplicationStage, Response } from './types'; import { ApplicationsService } from './applications.service'; import { CurrentUserInterceptor } from '../interceptors/current-user.interceptor'; import { AuthGuard } from '@nestjs/passport'; @@ -19,6 +20,8 @@ import { GetApplicationResponseDTO } from './dto/get-application.response.dto'; import { getAppForCurrentCycle } from './utils'; import { UserStatus } from '../users/types'; import { Application } from './application.entity'; +import { UpdateResult } from 'typeorm'; +import { ApplicationStatus } from './dto/application-status'; @Controller('apps') @UseInterceptors(CurrentUserInterceptor) @@ -38,6 +41,30 @@ export class ApplicationsController { return await this.applicationsService.submitApp(application, user); } + @Post('/decision') + @UseGuards(AuthGuard('jwt')) + async makeDecision( + @Body('applicantId', ParseIntPipe) applicantId: number, + @Body('decision') decision: 'ACCEPT' | 'REJECT', + @Request() req, + ): Promise { + //Authorization check for admin and recruiters + if (![UserStatus.ADMIN, !UserStatus.RECRUITER].includes(req.user.status)) { + throw new UnauthorizedException(); + } + + //Check if the user exists and if the user has an application for the current cycle + const applicant = await this.applicationsService.findCurrent(applicantId); + if (!applicant) { + throw new NotFoundException( + `Application for user with ID ${applicantId} not found or not for the current cycle`, + ); + } + + //Delegate the decision making to the service. + await this.applicationsService.processDecision(applicantId, decision); + } + @Get('/:userId') @UseGuards(AuthGuard('jwt')) async getApplication( diff --git a/apps/backend/src/applications/applications.service.ts b/apps/backend/src/applications/applications.service.ts index 70243c0..433e7d6 100644 --- a/apps/backend/src/applications/applications.service.ts +++ b/apps/backend/src/applications/applications.service.ts @@ -88,6 +88,51 @@ export class ApplicationsService { throw new UnauthorizedException(); } + /** + * Updates the application stage of the applicant. + * Moves the stage to either the next stage or to rejected. + * + * @param applicantId the id of the applicant. + * @param decision enum that contains either the applicant was 'ACCEPT' or 'REJECT' + * @returns { void } only updates the stage of the applicant. + */ + async processDecision( + applicantId: number, + decision: 'ACCEPT' | 'REJECT', + ): Promise { + const applicant = await this.findCurrent(applicantId); + + let newStage: ApplicationStage; + switch (applicant.stage) { + case ApplicationStage.RESUME: + newStage = + decision === 'ACCEPT' + ? ApplicationStage.TECHNICAL_CHALLENGE + : ApplicationStage.REJECTED; + break; + case ApplicationStage.TECHNICAL_CHALLENGE: + newStage = + decision === 'ACCEPT' + ? ApplicationStage.INTERVIEW + : ApplicationStage.REJECTED; + break; + case ApplicationStage.INTERVIEW: + newStage = + decision === 'ACCEPT' + ? ApplicationStage.ACCEPTED + : ApplicationStage.REJECTED; + break; + default: + throw new BadRequestException( + `Cannot make a decision on current stage ${applicant.stage}`, + ); + } + applicant.stage = newStage; + + //Save the updated stage + await this.applicationsRepository.save(applicant); + } + async findAll(userId: number): Promise { const apps = await this.applicationsRepository.find({ where: { user: { id: userId } }, From 6da21ab083cd7ffe37e51f45396581bac3eacb00 Mon Sep 17 00:00:00 2001 From: ahnfikd7 <99137905+ahnfikd7@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:44:15 -0500 Subject: [PATCH 2/4] Added endpoint for accept-reject and moving to next stage- removed some imports --- apps/backend/src/applications/applications.controller.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/backend/src/applications/applications.controller.ts b/apps/backend/src/applications/applications.controller.ts index f9c769f..fa3f900 100644 --- a/apps/backend/src/applications/applications.controller.ts +++ b/apps/backend/src/applications/applications.controller.ts @@ -12,7 +12,7 @@ import { NotFoundException, UnauthorizedException, } from '@nestjs/common'; -import { ApplicationStage, Response } from './types'; +import { Response } from './types'; import { ApplicationsService } from './applications.service'; import { CurrentUserInterceptor } from '../interceptors/current-user.interceptor'; import { AuthGuard } from '@nestjs/passport'; @@ -20,8 +20,6 @@ import { GetApplicationResponseDTO } from './dto/get-application.response.dto'; import { getAppForCurrentCycle } from './utils'; import { UserStatus } from '../users/types'; import { Application } from './application.entity'; -import { UpdateResult } from 'typeorm'; -import { ApplicationStatus } from './dto/application-status'; @Controller('apps') @UseInterceptors(CurrentUserInterceptor) From 713965587515440fe522eb7b422f6146b8890eb4 Mon Sep 17 00:00:00 2001 From: ahnfikd7 <99137905+ahnfikd7@users.noreply.github.com> Date: Mon, 11 Mar 2024 22:48:07 -0400 Subject: [PATCH 3/4] Changed the type error in decision --- apps/backend/src/app.module.ts | 1 + .../src/applications/applications.controller.ts | 10 ++++++++-- apps/backend/src/applications/applications.service.ts | 10 +++++----- apps/backend/src/applications/types.ts | 5 +++++ 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index a47cebe..986ae34 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -13,6 +13,7 @@ import { ApplicationsModule } from './applications/applications.module'; imports: [ TypeOrmModule.forRoot({ type: 'postgres', + database: 'c4c-ops', host: process.env.NX_DB_HOST, port: 5432, username: process.env.NX_DB_USERNAME, diff --git a/apps/backend/src/applications/applications.controller.ts b/apps/backend/src/applications/applications.controller.ts index fa3f900..92ea1e0 100644 --- a/apps/backend/src/applications/applications.controller.ts +++ b/apps/backend/src/applications/applications.controller.ts @@ -12,7 +12,7 @@ import { NotFoundException, UnauthorizedException, } from '@nestjs/common'; -import { Response } from './types'; +import { Decision, Response } from './types'; import { ApplicationsService } from './applications.service'; import { CurrentUserInterceptor } from '../interceptors/current-user.interceptor'; import { AuthGuard } from '@nestjs/passport'; @@ -51,6 +51,12 @@ export class ApplicationsController { throw new UnauthorizedException(); } + //Check if the string decision matches with the Decision enum + const decisionEnum: Decision = Decision[decision as keyof typeof Decision]; + if (!decisionEnum) { + throw new BadRequestException('Invalid decision value'); + } + //Check if the user exists and if the user has an application for the current cycle const applicant = await this.applicationsService.findCurrent(applicantId); if (!applicant) { @@ -60,7 +66,7 @@ export class ApplicationsController { } //Delegate the decision making to the service. - await this.applicationsService.processDecision(applicantId, decision); + await this.applicationsService.processDecision(applicantId, decisionEnum); } @Get('/:userId') diff --git a/apps/backend/src/applications/applications.service.ts b/apps/backend/src/applications/applications.service.ts index 433e7d6..93435fa 100644 --- a/apps/backend/src/applications/applications.service.ts +++ b/apps/backend/src/applications/applications.service.ts @@ -8,7 +8,7 @@ import { MongoRepository } from 'typeorm'; import { UsersService } from '../users/users.service'; import { Application } from './application.entity'; import { getAppForCurrentCycle, getCurrentCycle } from './utils'; -import { Response } from './types'; +import { Decision, Response } from './types'; import * as crypto from 'crypto'; import { User } from '../users/user.entity'; import { Position, ApplicationStage, ApplicationStep } from './types'; @@ -98,7 +98,7 @@ export class ApplicationsService { */ async processDecision( applicantId: number, - decision: 'ACCEPT' | 'REJECT', + decision: Decision, ): Promise { const applicant = await this.findCurrent(applicantId); @@ -106,19 +106,19 @@ export class ApplicationsService { switch (applicant.stage) { case ApplicationStage.RESUME: newStage = - decision === 'ACCEPT' + decision === Decision.ACCEPT ? ApplicationStage.TECHNICAL_CHALLENGE : ApplicationStage.REJECTED; break; case ApplicationStage.TECHNICAL_CHALLENGE: newStage = - decision === 'ACCEPT' + decision === Decision.ACCEPT ? ApplicationStage.INTERVIEW : ApplicationStage.REJECTED; break; case ApplicationStage.INTERVIEW: newStage = - decision === 'ACCEPT' + decision === Decision.ACCEPT ? ApplicationStage.ACCEPTED : ApplicationStage.REJECTED; break; diff --git a/apps/backend/src/applications/types.ts b/apps/backend/src/applications/types.ts index 3ae67ab..ebc0a69 100644 --- a/apps/backend/src/applications/types.ts +++ b/apps/backend/src/applications/types.ts @@ -30,3 +30,8 @@ export enum Position { PM = 'PRODUCT_MANAGER', DESIGNER = 'DESIGNER', } + +export enum Decision { + ACCEPT = 'ACCEPT', + REJECT = 'REJECT', +} From e298f18920ed85f99ead288817603d4ea099938b Mon Sep 17 00:00:00 2001 From: Kenny Jung Date: Sun, 7 Apr 2024 15:20:50 -0400 Subject: [PATCH 4/4] Address PR comments --- apps/backend/src/app.module.ts | 1 - .../applications/applications.controller.ts | 18 +++------ .../src/applications/applications.service.ts | 39 +++++++------------ 3 files changed, 18 insertions(+), 40 deletions(-) diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index 986ae34..a47cebe 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -13,7 +13,6 @@ import { ApplicationsModule } from './applications/applications.module'; imports: [ TypeOrmModule.forRoot({ type: 'postgres', - database: 'c4c-ops', host: process.env.NX_DB_HOST, port: 5432, username: process.env.NX_DB_USERNAME, diff --git a/apps/backend/src/applications/applications.controller.ts b/apps/backend/src/applications/applications.controller.ts index 92ea1e0..52f6635 100644 --- a/apps/backend/src/applications/applications.controller.ts +++ b/apps/backend/src/applications/applications.controller.ts @@ -39,32 +39,24 @@ export class ApplicationsController { return await this.applicationsService.submitApp(application, user); } - @Post('/decision') + @Post('/decision/:appId') @UseGuards(AuthGuard('jwt')) async makeDecision( - @Body('applicantId', ParseIntPipe) applicantId: number, - @Body('decision') decision: 'ACCEPT' | 'REJECT', + @Param('appId', ParseIntPipe) applicantId: number, + @Body('decision') decision: Decision, @Request() req, ): Promise { //Authorization check for admin and recruiters - if (![UserStatus.ADMIN, !UserStatus.RECRUITER].includes(req.user.status)) { + if (![UserStatus.ADMIN, UserStatus.RECRUITER].includes(req.user.status)) { throw new UnauthorizedException(); } //Check if the string decision matches with the Decision enum - const decisionEnum: Decision = Decision[decision as keyof typeof Decision]; + const decisionEnum: Decision = Decision[decision]; if (!decisionEnum) { throw new BadRequestException('Invalid decision value'); } - //Check if the user exists and if the user has an application for the current cycle - const applicant = await this.applicationsService.findCurrent(applicantId); - if (!applicant) { - throw new NotFoundException( - `Application for user with ID ${applicantId} not found or not for the current cycle`, - ); - } - //Delegate the decision making to the service. await this.applicationsService.processDecision(applicantId, decisionEnum); } diff --git a/apps/backend/src/applications/applications.service.ts b/apps/backend/src/applications/applications.service.ts index 93435fa..855a10d 100644 --- a/apps/backend/src/applications/applications.service.ts +++ b/apps/backend/src/applications/applications.service.ts @@ -12,6 +12,7 @@ import { Decision, Response } from './types'; import * as crypto from 'crypto'; import { User } from '../users/user.entity'; import { Position, ApplicationStage, ApplicationStep } from './types'; +import { stagesMap } from './applications.constants'; @Injectable() export class ApplicationsService { @@ -100,37 +101,23 @@ export class ApplicationsService { applicantId: number, decision: Decision, ): Promise { - const applicant = await this.findCurrent(applicantId); + const application = await this.findCurrent(applicantId); let newStage: ApplicationStage; - switch (applicant.stage) { - case ApplicationStage.RESUME: - newStage = - decision === Decision.ACCEPT - ? ApplicationStage.TECHNICAL_CHALLENGE - : ApplicationStage.REJECTED; - break; - case ApplicationStage.TECHNICAL_CHALLENGE: - newStage = - decision === Decision.ACCEPT - ? ApplicationStage.INTERVIEW - : ApplicationStage.REJECTED; - break; - case ApplicationStage.INTERVIEW: - newStage = - decision === Decision.ACCEPT - ? ApplicationStage.ACCEPTED - : ApplicationStage.REJECTED; - break; - default: - throw new BadRequestException( - `Cannot make a decision on current stage ${applicant.stage}`, - ); + if (decision === Decision.REJECT) { + newStage = ApplicationStage.REJECTED; + } else { + const stagesArr = stagesMap[application.position]; + const stageIndex = stagesArr.indexOf(application.stage); + if (stageIndex === -1) { + return; + } + newStage = stagesArr[stageIndex + 1]; } - applicant.stage = newStage; + application.stage = newStage; //Save the updated stage - await this.applicationsRepository.save(applicant); + await this.applicationsRepository.save(application); } async findAll(userId: number): Promise {