Skip to content
Snippets Groups Projects
Commit ff4c1e01 authored by Ivan Alglave's avatar Ivan Alglave
Browse files

fix: PUT internships/studentId doesn't require a tracking body member anymore,...

fix: PUT internships/studentId doesn't require a tracking body member anymore, and will correctly update. Changed save to make sure studentId is unique on insert. Custom error handling.
parent 0a7bd773
No related branches found
No related tags found
1 merge request!5Crud internship
import { import { Injectable, NotFoundException } from '@nestjs/common';
Injectable,
NotFoundException,
InternalServerErrorException,
} from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose'; import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose'; import { Model } from 'mongoose';
import { CONFLICT } from 'src/shared/HttpError';
import { STATE_1 } from 'src/shared/InternshipState';
import { STATUS_NOK } from 'src/shared/InternshipStatus';
import { CreateInternshipDto } from '../dto/create-internship.dto'; import { CreateInternshipDto } from '../dto/create-internship.dto';
import { InternshipDto } from '../dto/internship.dto'; import { InternshipDto } from '../dto/internship.dto';
import { TrackingDto } from '../dto/nested-create/tracking.dto';
import { Internship } from '../schemas/internship.schema'; import { Internship } from '../schemas/internship.schema';
@Injectable() @Injectable()
export class InternshipDao { export class InternshipDao {
constructor( constructor(
@InjectModel(Internship.name) @InjectModel(Internship.name)
private readonly _groupModel: Model<Internship>, private readonly _internshipModel: Model<Internship>,
) {} ) {}
find = (): Promise<Internship[]> => find = (): Promise<Internship[]> =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
this._groupModel.find({}, {}, {}, (err, value) => { this._internshipModel.find({}, {}, {}, (err, value) => {
if (err) reject(err.message); if (err) reject(err.message);
if (!value) reject('No values'); if (!value) reject('No values');
resolve(value); resolve(value);
...@@ -28,7 +26,7 @@ export class InternshipDao { ...@@ -28,7 +26,7 @@ export class InternshipDao {
findByStudentId = (studentId: string): Promise<Internship | void> => findByStudentId = (studentId: string): Promise<Internship | void> =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
this._groupModel.findOne({ studentId }, {}, {}, (err, value) => { this._internshipModel.findOne({ studentId }, {}, {}, (err, value) => {
if (err) reject(err.message); if (err) reject(err.message);
if (!value) reject(new NotFoundException()); if (!value) reject(new NotFoundException());
resolve(value); resolve(value);
...@@ -37,29 +35,34 @@ export class InternshipDao { ...@@ -37,29 +35,34 @@ export class InternshipDao {
save = (internship: CreateInternshipDto): Promise<Internship> => save = (internship: CreateInternshipDto): Promise<Internship> =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
// do smth // Use updateOne with `upsert: true` to only insert when no other document has the same studentId to prevent duplicata
const _internship: InternshipDto = { const decoratedInternship = this.toInternshipDtoWithTracking(internship);
...internship, this._internshipModel.updateOne(
tracking: { { studentId: internship.studentId },
state: 'state-1', { $setOnInsert: decoratedInternship },
status: 'pending', {
upsert: true,
runValidators: true,
}, },
}; (err, value) => {
new this._groupModel(_internship).save((err, value) => { const { upsertedCount } = value;
if (err) reject(err.message); if (err) reject(err.message);
if (!value) reject(new InternalServerErrorException()); if (upsertedCount === 0) reject(CONFLICT);
resolve(value); resolve(decoratedInternship as Internship);
}); },
);
}); });
findByStudentIdAndUpdate = ( findByStudentIdAndUpdate = (
studentId: string, studentId: string,
internship: InternshipDto, internship: CreateInternshipDto,
): Promise<Internship | void> => ): Promise<Internship | void> =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
this._groupModel.findOneAndReplace( // Check if information modification is allowed -> current state is information input by student and updating is allowed
{ studentId }, const decoratedInternship = this.toInternshipDtoWithTracking(internship);
internship, this._internshipModel.findOneAndReplace(
{ studentId, 'tracking.state': STATE_1, 'tracking.status': STATUS_NOK },
decoratedInternship,
{ {
new: true, new: true,
runValidators: true, runValidators: true,
...@@ -73,9 +76,21 @@ export class InternshipDao { ...@@ -73,9 +76,21 @@ export class InternshipDao {
findByStudentIdAndRemove = (studentId: string): Promise<Internship | void> => findByStudentIdAndRemove = (studentId: string): Promise<Internship | void> =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
this._groupModel.findOneAndDelete({ studentId }, {}, (err) => { this._internshipModel.findOneAndDelete({ studentId }, {}, (err) => {
if (err) reject(err.message); if (err) reject(err.message);
resolve(); resolve();
}); });
}); });
toInternshipDtoWithTracking = (
createInternshipDto: CreateInternshipDto,
): InternshipDto => {
return {
...createInternshipDto,
tracking: {
state: 'state-1',
status: 'nok',
},
};
};
} }
...@@ -3,9 +3,9 @@ import { IsString, IsNotEmpty } from 'class-validator'; ...@@ -3,9 +3,9 @@ import { IsString, IsNotEmpty } from 'class-validator';
export class TrackingDto { export class TrackingDto {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
status: string; state: string;
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
state: string; status: string;
} }
export class TrackingEntity { export class TrackingEntity {
status: string;
state: string; state: string;
status: string;
constructor(partial: Partial<TrackingEntity>) { constructor(partial: Partial<TrackingEntity>) {
Object.assign(this, partial); Object.assign(this, partial);
......
...@@ -8,9 +8,10 @@ import { ...@@ -8,9 +8,10 @@ import {
Body, Body,
UseInterceptors, UseInterceptors,
} from '@nestjs/common'; } from '@nestjs/common';
import { BAD_TRACKING_STATE, CUSTOM } from 'src/shared/HttpError';
import * as InternshipStates from 'src/shared/InternshipState';
import { HttpInterceptor } from '../interceptors/http.interceptor'; import { HttpInterceptor } from '../interceptors/http.interceptor';
import { CreateInternshipDto } from './dto/create-internship.dto'; import { CreateInternshipDto } from './dto/create-internship.dto';
import { InternshipDto } from './dto/internship.dto';
import { InternshipEntity } from './entities/internship.entity'; import { InternshipEntity } from './entities/internship.entity';
import { InternshipService } from './internships.service'; import { InternshipService } from './internships.service';
...@@ -41,11 +42,21 @@ export class InternshipsController { ...@@ -41,11 +42,21 @@ export class InternshipsController {
@Put(':studentId') @Put(':studentId')
update( update(
@Param() params: { studentId: string }, @Param() params: { studentId: string },
@Body() internshipDto: InternshipDto, @Body() internshipDto: CreateInternshipDto,
): Promise<InternshipEntity | void> { ): Promise<InternshipEntity | void> {
return this._groupsService.update(params.studentId, internshipDto); return this._groupsService.update(params.studentId, internshipDto);
} }
@Put(':studentId/tracking')
updateState(
@Param() params: { studentId: string },
@Body() body: { state: string; content: string },
) {
if (!InternshipStates.isAllowedState(body.state))
throw BAD_TRACKING_STATE(body.state);
// Treat request and update tracking -> implement service + dao
}
@Delete(':studentId') @Delete(':studentId')
delete( delete(
@Param() params: { studentId: string }, @Param() params: { studentId: string },
......
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InternshipDao } from './dao/internships.dao'; import { InternshipDao } from './dao/internships.dao';
import { CreateInternshipDto } from './dto/create-internship.dto'; import { CreateInternshipDto } from './dto/create-internship.dto';
import { InternshipDto } from './dto/internship.dto';
import { InternshipEntity } from './entities/internship.entity'; import { InternshipEntity } from './entities/internship.entity';
@Injectable() @Injectable()
...@@ -19,7 +18,7 @@ export class InternshipService { ...@@ -19,7 +18,7 @@ export class InternshipService {
update = ( update = (
studentId: string, studentId: string,
internship: InternshipDto, internship: CreateInternshipDto,
): Promise<InternshipEntity | void> => ): Promise<InternshipEntity | void> =>
this._internshipsDao.findByStudentIdAndUpdate(studentId, internship); this._internshipsDao.findByStudentIdAndUpdate(studentId, internship);
......
import {
HttpException,
NotFoundException,
ConflictException,
UnprocessableEntityException,
} from '@nestjs/common';
export const NOT_FOUND = new NotFoundException();
export const CONFLICT = new ConflictException();
export const BAD_TRACKING_STATE = (badState: string) =>
new UnprocessableEntityException(`Unknown state [${badState}]`);
export const CUSTOM = (reason: string, errorStatus: number) =>
new HttpException(reason, errorStatus);
export const STATE_1 = 'state-1';
export const STATE_2 = 'state-2';
export const STATE_3 = 'state-3';
export const STATE_4 = 'state-4';
export const STATE_5 = 'state-5';
export const STATE_6 = 'state-6';
export const STATE_7 = 'state-7';
export const STATES = [
STATE_1,
STATE_2,
STATE_3,
STATE_4,
STATE_5,
STATE_6,
STATE_7,
];
export const isAllowedState = (potentialState: string): boolean =>
STATES.includes(potentialState);
export const STATUS_OK = 'ok';
export const STATUS_NOK = 'nok';
export type InternshipState = 'ok' | 'nok';
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment