diff --git a/src/config/index.ts b/src/config/index.ts index 916f17f78890c853628a00a582386df2355940fb..e948aaeb8cdb7af0e37d8b5ec06c5e704881c2c0 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,6 +1,7 @@ import { IConfig } from './config.model'; import { readFileSync } from 'fs'; import * as path from 'path'; +import { exit } from 'process'; // Store file local path in var for lisibility const CONFIG_DEV = 'config.json'; @@ -22,10 +23,11 @@ switch (process.env.NODE_ENV) { ) as IConfig; break; default: - // This shouldn't happen - console.log('\x1b[31mFATAL: Cannot load config.\x1b[0m'); - config = null; - break; + // This happens when the environment isn't set + console.log( + '\x1b[31mFATAL: Cannot load config.\nInvalid application environment. Did you read the \x1b[4mREADME\x1b[0m\x1b[31m ?\x1b[0m', + ); + exit(-1); } // Export config diff --git a/src/internships/dao/internships.dao.ts b/src/internships/dao/internships.dao.ts index 091d4d56a7d0f73bc45ba48029be6a446e21988e..b5bfdfc5babc8139f359b4db96dd05c6e8545377 100644 --- a/src/internships/dao/internships.dao.ts +++ b/src/internships/dao/internships.dao.ts @@ -1,8 +1,17 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; -import { BAD_REQUEST, CONFLICT, INTERNAL } from 'src/shared/HttpError'; -import { STATE_STUDENT_ENTERS_INTERNSHIP_INFORMATION } from 'src/shared/InternshipState'; +import { BAD_REQUEST, CONFLICT } from 'src/shared/HttpError'; +import { + isStateValid, + STATE_COMPANY_SIGNS_INTERNSHIP_AGREEMENT, + STATE_DEAN_SIGNS_INTERNSHIP_AGREEMENT, + STATE_RESPONSIBLE_ACCEPTS_INTERNSHIP_INFORMATION, + STATE_RESPONSIBLE_SIGNS_INTERNSHIP_AGREEMENT, + STATE_SECRETARY_ESTABLISHES_INTERNSHIP_AGREEMENT, + STATE_STUDENT_ENTERS_INTERNSHIP_INFORMATION, + STATE_STUDENT_SIGNS_INTERNSHIP_AGREEMENT, +} from 'src/shared/InternshipState'; import { CreateInternshipDto } from '../dto/create-internship.dto'; import { InternshipDto } from '../dto/internship.dto'; import { Internship } from '../schemas/internship.schema'; @@ -78,6 +87,128 @@ export class InternshipDao { ); }); + private updateTrackingState = ( + studentId: string, + state: string, + nextState: string, + contentHolder: string, + content: string | boolean, + callback: (err: any, value: any) => void, + ) => { + this._internshipModel.findOneAndUpdate( + { + studentId, + 'tracking.state': state, + }, + { + $set: { + 'tracking.state': nextState, + [contentHolder]: content, + }, + }, + { + new: true, + runValidators: true, + }, + callback, + ); + }; + + findByStudentIdAndUpdateTracking = ( + studentId: string, + state: string, + content: string | boolean, + ): Promise<Internship | void> => + new Promise((resolve, reject) => { + if (!isStateValid(state)) reject(BAD_REQUEST); + console.log(typeof content); + let nextState: string, contentHolder: string; + let valid = false; + switch (state) { + case STATE_STUDENT_ENTERS_INTERNSHIP_INFORMATION: { + if (typeof content === 'boolean' && content === true) { + nextState = STATE_RESPONSIBLE_ACCEPTS_INTERNSHIP_INFORMATION; + contentHolder = 'tracking.studentEntersInternshipInformation'; + valid = true; + } else reject(BAD_REQUEST); + break; + } + case STATE_RESPONSIBLE_ACCEPTS_INTERNSHIP_INFORMATION: { + if (typeof content === 'boolean' && content === true) { + // content === true -> Responsible agrees with internship + nextState = STATE_SECRETARY_ESTABLISHES_INTERNSHIP_AGREEMENT; + contentHolder = 'tracking.responsibleAcceptsInternshipInformation'; + valid = true; + } else if (typeof content === 'boolean' && content === false) { + // content === false -> Responsible did not agree with internship, go back to student entering information + nextState = STATE_STUDENT_ENTERS_INTERNSHIP_INFORMATION; + contentHolder = 'tracking.responsibleAcceptsInternshipInformation'; + valid = true; + } else reject(BAD_REQUEST); + break; + } + case STATE_SECRETARY_ESTABLISHES_INTERNSHIP_AGREEMENT: { + if (typeof content !== 'string') reject(BAD_REQUEST); + else { + nextState = STATE_STUDENT_SIGNS_INTERNSHIP_AGREEMENT; + contentHolder = 'tracking.secretaryEstablishesInternshipAgreement'; + valid = true; + } + break; + } + case STATE_STUDENT_SIGNS_INTERNSHIP_AGREEMENT: { + if (typeof content !== 'string') reject(BAD_REQUEST); + else { + nextState = STATE_RESPONSIBLE_SIGNS_INTERNSHIP_AGREEMENT; + contentHolder = 'tracking.studentSignsInternshipAgreement'; + valid = true; + } + break; + } + case STATE_RESPONSIBLE_SIGNS_INTERNSHIP_AGREEMENT: { + if (typeof content !== 'string') reject(BAD_REQUEST); + else { + nextState = STATE_COMPANY_SIGNS_INTERNSHIP_AGREEMENT; + contentHolder = 'tracking.responsibleSignsInternshipAgreement'; + valid = true; + } + break; + } + case STATE_COMPANY_SIGNS_INTERNSHIP_AGREEMENT: { + if (typeof content !== 'string') reject(BAD_REQUEST); + else { + nextState = STATE_DEAN_SIGNS_INTERNSHIP_AGREEMENT; + contentHolder = 'tracking.companySignsInternshipAgreement'; + valid = true; + } + break; + } + case STATE_DEAN_SIGNS_INTERNSHIP_AGREEMENT: { + if (typeof content !== 'string') reject(BAD_REQUEST); + else { + nextState = '/'; + contentHolder = 'tracking.deanSignsInternshipAgreement'; + valid = true; + } + break; + } + } + if (valid) { + this.updateTrackingState( + studentId, + state, + nextState, + contentHolder, + content, + (err, value) => { + if (err) reject(err); + else if (!value) reject(BAD_REQUEST); + else resolve(value); + }, + ); + } + }); + findByStudentIdAndRemove = (studentId: string): Promise<Internship | void> => new Promise((resolve, reject) => { this._internshipModel.findOneAndDelete({ studentId }, {}, (err) => { diff --git a/src/internships/internships.controller.ts b/src/internships/internships.controller.ts index 70c798990da55caabe07b1915aec4b57ce78b633..1fb011eb7d7a98fa8e58cf5981bf003c48d7033a 100644 --- a/src/internships/internships.controller.ts +++ b/src/internships/internships.controller.ts @@ -18,25 +18,25 @@ import { InternshipService } from './internships.service'; @Controller('internships') @UseInterceptors(HttpInterceptor) export class InternshipsController { - constructor(private readonly _groupsService: InternshipService) {} + constructor(private readonly _internshipsService: InternshipService) {} @Get() findAll(): Promise<InternshipEntity[] | void> { - return this._groupsService.findAll(); + return this._internshipsService.findAll(); } @Get(':studentId') findOne( @Param() params: { studentId: string }, ): Promise<InternshipEntity | void> { - return this._groupsService.findOne(params.studentId); + return this._internshipsService.findOne(params.studentId); } @Post() create( @Body() internshipDto: CreateInternshipDto, ): Promise<InternshipEntity> { - return this._groupsService.create(internshipDto); + return this._internshipsService.create(internshipDto); } @Put(':studentId') @@ -44,23 +44,28 @@ export class InternshipsController { @Param() params: { studentId: string }, @Body() internshipDto: CreateInternshipDto, ): Promise<InternshipEntity | void> { - return this._groupsService.update(params.studentId, internshipDto); + return this._internshipsService.update(params.studentId, internshipDto); } @Put(':studentId/tracking') updateState( @Param() params: { studentId: string }, - @Body() body: { state: string; content: string }, - ) { + @Body() body: { state: string; content?: string | boolean }, + ): Promise<InternshipEntity | void> { if (!InternshipStates.isStateValid(body.state)) throw BAD_TRACKING_STATE(body.state); - // Treat request and update tracking -> implement service + dao + // AMINE : Handle PDF file upload -> save file in /pdf/ folder and set content as local file URL. In case of step with no file, set content as true/false + return this._internshipsService.updateTracking( + params.studentId, + body.state, + body.content, + ); } @Delete(':studentId') delete( @Param() params: { studentId: string }, ): Promise<InternshipEntity | void> { - return this._groupsService.delete(params.studentId); + return this._internshipsService.delete(params.studentId); } } diff --git a/src/internships/internships.service.ts b/src/internships/internships.service.ts index 5bdb50dd5bb6ea13ece244b2b4c14ab7c9cc11b7..9d3af2080dcac5cd1119734812c3638d8a54f3f1 100644 --- a/src/internships/internships.service.ts +++ b/src/internships/internships.service.ts @@ -22,6 +22,17 @@ export class InternshipService { ): Promise<InternshipEntity | void> => this._internshipsDao.findByStudentIdAndUpdate(studentId, internship); + updateTracking = ( + studentId: string, + state: string, + content: string | boolean, + ): Promise<InternshipEntity | void> => + this._internshipsDao.findByStudentIdAndUpdateTracking( + studentId, + state, + content, + ); + delete = (studentId: string): Promise<InternshipEntity | void> => this._internshipsDao.findByStudentIdAndRemove(studentId); } diff --git a/src/main.ts b/src/main.ts index 73418ec40e7590e32d10c7c56387f31a8057b1fd..c5c630c00749273794025342b1f60b6be049f12f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,21 +1,17 @@ import { ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; -import { exit } from 'process'; import { AppModule } from './app.module'; import config from './config'; async function bootstrap() { - const app = await NestFactory.create(AppModule); const env = process.env.NODE_ENV; + const app = await NestFactory.create(AppModule); if (env === 'dev') { app.enableCors(); } else if (env === 'prod') { // enableCors for SPECIFIC origin only, aka the way it's supposed to be } else { - console.log( - '\x1b[31mFATAL: Invalid application environment.\nDid you read the \x1b[4mREADME\x1b[0m\x1b[31m ?\x1b[0m', - ); - exit(-1); + // unknown environement ? } await app.useGlobalPipes( new ValidationPipe({