Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat session: service, controller, entity #15

Merged
merged 38 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0a4de94
Fix missing dependencies and imports (#9)
AlbertoBaroso Nov 23, 2023
27f282f
docs: updated project description, useful links, and contributors in …
AlbertoBaroso Dec 7, 2023
9f52fdf
feat session: service, controller, entity
whiitex Dec 11, 2023
7014267
Merge branch 'dev' of https://github.com/MuNuChapterHKN/HKrecruitment…
whiitex Dec 11, 2023
97aa948
feat upd recruitment-session: service, controller, entity
whiitex Dec 28, 2023
7f1e3d8
feat upd recruitment-session: service, controller, entity
whiitex Dec 28, 2023
f3aedbf
upd dependencies in shared/abilities
whiitex Jan 7, 2024
70964e7
mock
whiitex Jan 7, 2024
0f046a7
mock formatted
whiitex Jan 7, 2024
37c060e
upd date array
whiitex Jan 12, 2024
0e21b0e
upd date array, format
whiitex Jan 12, 2024
e7bf426
upd date array2
whiitex Jan 12, 2024
0fddaee
upd date array3
whiitex Jan 12, 2024
d8daacc
upd date array4
whiitex Jan 12, 2024
39b010c
upd date array5
whiitex Jan 12, 2024
3600b0b
mock shared -> required/optional fields
whiitex Jan 13, 2024
4470ea8
mock shared -> required/optional fields 2
whiitex Jan 13, 2024
dffa94f
upd date string
whiitex Jan 13, 2024
c314bd9
upd date string2
whiitex Jan 13, 2024
dfe1433
upd date string3
whiitex Jan 13, 2024
d4bf3b9
upd date string check
whiitex Jan 13, 2024
c2d69bd
mock recruitment session service, insert data mock
whiitex Jan 14, 2024
2c77714
mock service
whiitex Jan 14, 2024
09e737b
mock update
whiitex Jan 14, 2024
5b51f0c
upd shared mock
whiitex Jan 14, 2024
8971cf1
fix: relative import of recruitment-session from shared folder
AlbertoBaroso Jan 19, 2024
f95f5db
fix: recruitment-session service Delete test
AlbertoBaroso Jan 19, 2024
70e4e5a
fix: removed lastModified from UpdateRecruitmentSessionDto
AlbertoBaroso Jan 19, 2024
5fc8fed
fix: ability check on recruitment session creation
AlbertoBaroso Jan 19, 2024
9190905
feat: check if recruitment session has pending interviews before dele…
AlbertoBaroso Jan 21, 2024
d0e4524
feat: check for conflicts and consistency when updating a recruitment…
AlbertoBaroso Jan 21, 2024
2d8bfea
fix: check ability for update recruitment session
AlbertoBaroso Jan 21, 2024
f40d727
refactor: removed unused imports in recruitment-session.controller.ts
AlbertoBaroso Jan 21, 2024
3bcac19
fix: use const for unchanged variable in createRecruitmentSession ser…
AlbertoBaroso Jan 21, 2024
dc45a8e
refactor: removed unused code in creatre-recruitment-session.dto.ts
AlbertoBaroso Jan 21, 2024
ce62385
fix: updated Date[] in create and update Recruitment session DTOs
AlbertoBaroso Jan 21, 2024
74d1a9c
test: Recruitment Session Controller tests
AlbertoBaroso Jan 21, 2024
598f2dc
refactor: removed unused imports
AlbertoBaroso Jan 21, 2024
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
25 changes: 25 additions & 0 deletions api/src/recruitment-session/create-recruitment-session.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { RecruitmentSession, RecruitmentSessionState } from "@hkrecruitment/shared/recruitment-session";
import { ApiProperty } from '@nestjs/swagger';

export class CreateRecruitmentSessionDto implements RecruitmentSession {
@ApiProperty()
state: RecruitmentSessionState;
whiitex marked this conversation as resolved.
Show resolved Hide resolved

@ApiProperty()
slotDuration: number;

@ApiProperty()
interviewStart: Date;

@ApiProperty()
interviewEnd: Date;

@ApiProperty()
days: [Date];

@ApiProperty()
createdAt: Date;
whiitex marked this conversation as resolved.
Show resolved Hide resolved

@ApiProperty()
lastModified: Date;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { RecruitmentSession, RecruitmentSessionState } from '@hkrecruitment/shared/recruitment-session';
import { Exclude, Expose } from 'class-transformer';

@Exclude()
export class RecruitmentSessionResponseDto implements Partial<RecruitmentSession> {
@Expose() id: number;
@Expose() createdAt: Date;
}

102 changes: 102 additions & 0 deletions api/src/recruitment-session/recruitment-session.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {
Body,
Controller,
BadRequestException,
NotFoundException,
ConflictException,
Param,
Post,
Delete,
Req,
Patch,
ForbiddenException,
} from '@nestjs/common';
import { RecruitmentSessionService } from './recruitment-session.service';
import { createRecruitmentSessionSchema, RecruitmentSession, RecruitmentSessionState, updateRecruitmentSessionSchema } from '@hkrecruitment/shared/recruitment-session';
import { Action, AppAbility, checkAbility } from "@hkrecruitment/shared"
import { JoiValidate } from '../joi-validation/joi-validate.decorator';
import {
ApiBadRequestResponse,
ApiBearerAuth,
ApiForbiddenResponse,
ApiNotFoundResponse,
ApiCreatedResponse,
ApiOkResponse,
ApiTags,
ApiConflictResponse,
ApiNoContentResponse,
} from '@nestjs/swagger';
import { CheckPolicies } from 'src/authorization/check-policies.decorator';
import { CreateRecruitmentSessionDto } from './create-recruitment-session.dto';
import { UpdateRecruitmentSessionDto } from './update-recruitment-session.dto';
import * as Joi from 'joi';
import { Ability } from 'src/authorization/ability.decorator';
import { AuthenticatedRequest } from 'src/authorization/authenticated-request.types';
import { plainToClass } from 'class-transformer';
import { RecruitmentSessionResponseDto } from './recruitment-session-response.dto';

@ApiBearerAuth()
@ApiTags('recruitment-session')
@Controller('recruitment-session')
export class RecruitmentSessionController {
constructor(private readonly recruitmentSessionService: RecruitmentSessionService) {}

// CREATE NEW RECRUITMENT SESSION
@ApiBadRequestResponse()
@ApiForbiddenResponse()
@ApiConflictResponse({
description: 'The recruitment session cannot be created', //
})
@ApiCreatedResponse()
@JoiValidate({
body: createRecruitmentSessionSchema,
})
@CheckPolicies((ability) => ability.can(Action.Create, 'RecruitmentSession'))
@Post()
async createRecruitmentSession(@Body() rSess: CreateRecruitmentSessionDto): Promise<RecruitmentSession> {
AlbertoBaroso marked this conversation as resolved.
Show resolved Hide resolved
return this.recruitmentSessionService.createRecruitmentSession({...rSess});
whiitex marked this conversation as resolved.
Show resolved Hide resolved
}

// UPDATE A RECRUITMENT SESSION
@Patch(':session_id')
@ApiBadRequestResponse()
@ApiForbiddenResponse()
@ApiOkResponse()
@JoiValidate({
param: Joi.number().positive().integer().required().label('session_id'),
body: updateRecruitmentSessionSchema,
})
async updateRecruitmentSession(
@Param('session_id') sessionId: number,
@Body() updateRecruitmentSession: UpdateRecruitmentSessionDto,
@Ability() ability: AppAbility,
@Req() req: AuthenticatedRequest,
): Promise<RecruitmentSessionResponseDto> {
const session = await this.recruitmentSessionService.findRecruitmentSessionById(sessionId);

if (session === null) throw new NotFoundException();

const sessionToCheck = {
...updateRecruitmentSession,
sessionId: session.id,
};
if (
!checkAbility(ability, Action.Update, sessionToCheck, 'RecruitmentSession', [
'applicantId',
])
)
throw new ForbiddenException();

const updatedRecruitmentSession = await this.recruitmentSessionService.updateRecruitmentSession(
{
...session,
...updateRecruitmentSession,
lastModified: new Date(),
},
);

return plainToClass(RecruitmentSessionResponseDto, updatedRecruitmentSession);
}


}
30 changes: 30 additions & 0 deletions api/src/recruitment-session/recruitment-session.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { RecruitmentSession as RecruitmentSessionInterface, RecruitmentSessionState } from '@hkrecruitment/shared/src/recruitment-session'


@Entity()
export class RecruitmentSession implements RecruitmentSessionInterface {
@PrimaryGeneratedColumn('increment')
id: number;

@Column()
state: RecruitmentSessionState;

@Column()
slotDuration: number;

@Column()
interviewStart: Date;
whiitex marked this conversation as resolved.
Show resolved Hide resolved

@Column()
interviewEnd: Date;

@Column()
days: [Date];

@Column()
createdAt: Date;

@Column()
lastModified: Date;
}
34 changes: 34 additions & 0 deletions api/src/recruitment-session/recruitment-session.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { RecruitmentSession } from "./recruitment-session.entity";
import { CreateRecruitmentSessionDto } from "./create-recruitment-session.dto";


@Injectable()
export class RecruitmentSessionService {
constructor(
@InjectRepository(RecruitmentSession)
private readonly recruitmentSessionRepository: Repository<RecruitmentSession>
) {}

async createRecruitmentSession(rSess: CreateRecruitmentSessionDto): Promise<RecruitmentSession> {
whiitex marked this conversation as resolved.
Show resolved Hide resolved
return await this.recruitmentSessionRepository.save(rSess);
}

async findAllRecruitmentSessions(): Promise<RecruitmentSession[]> {
return await this.recruitmentSessionRepository.find();
}

async findRecruitmentSessionById(id: number): Promise<RecruitmentSession> {
return await this.recruitmentSessionRepository.findOne({where: {id} });
}

async deletRecruitmentSession(rSess: RecruitmentSession): Promise<RecruitmentSession> {
return await this.recruitmentSessionRepository.remove(rSess);
}

async updateRecruitmentSession(rSess: RecruitmentSession): Promise<RecruitmentSession> {
return await this.recruitmentSessionRepository.save(rSess);
}
}
23 changes: 23 additions & 0 deletions api/src/recruitment-session/update-recruitment-session.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { RecruitmentSession, RecruitmentSessionState } from '@hkrecruitment/shared/recruitment-session';
import { ApiProperty } from '@nestjs/swagger';


export class UpdateRecruitmentSessionDto implements Partial<RecruitmentSession> {
@ApiProperty({ required: false })
state?: RecruitmentSessionState;

@ApiProperty({ required: false })
slotDuration?: number;

@ApiProperty({ required: false })
interviewStart?: Date;

@ApiProperty({ required: false })
interviewEnd?: Date;

@ApiProperty({ required: false })
days?: [Date];

@ApiProperty({ required: false })
lastModified?: Date;
AlbertoBaroso marked this conversation as resolved.
Show resolved Hide resolved
}
6 changes: 4 additions & 2 deletions shared/src/abilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { applyAbilitiesForPerson, Person, Role } from "./person";
import { Application, applyAbilitiesOnApplication } from "./application";
import { applyAbilitiesOnAvailability, Availability } from "./availability";
import { TimeSlot } from "./timeslot";
import { RecruitmentSession } from "recruitment-session";

export interface UserAuth {
sub: string;
Expand All @@ -26,8 +27,9 @@ type SubjectsTypes =
| Partial<Person>
| Partial<Application>
| Partial<Availability>
| Partial<TimeSlot>;
type SubjectNames = "Person" | "Application" | "Availability" | "TimeSlot";
| Partial<TimeSlot>
| Partial<RecruitmentSession>;
type SubjectNames = "Person" | "Application" | "Availability" | "TimeSlot" | "RecruitmentSession";
export type Subjects = SubjectsTypes | SubjectNames;

whiitex marked this conversation as resolved.
Show resolved Hide resolved
export type AppAbility = PureAbility<[Action, Subjects]>;
Expand Down
82 changes: 82 additions & 0 deletions shared/src/recruitment-session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { AvailabilityState } from "availability";
import { Action, ApplyAbilities } from "./abilities";
import { Role } from "./person";
import DateExtension from "@joi/date";
import * as Joi from "joi";

const JoiDate = Joi.extend(DateExtension);


export enum RecruitmentSessionState {
Active = "active",
Concluded = "concluded",
}


// import BaseJoi from "joi";

export interface RecruitmentSession {
state: RecruitmentSessionState;
slotDuration: number;
interviewStart: Date;
interviewEnd: Date;
days: [Date];
createdAt: Date;
lastModified: Date;
}

/* Validation schemas */

export const createRecruitmentSessionSchema = Joi.object<RecruitmentSession> ({
state: Joi.string()
.valid("active", "concluded")
.required(),
slotDuration: Joi.number()
.integer(),
interviewStart: JoiDate.date().format("YYYY-MM-DD HH:mm").required(),
whiitex marked this conversation as resolved.
Show resolved Hide resolved
interviewEnd: JoiDate.date().format("YYYY-MM-DD HH:mm").required(),
// days:
lastModified: JoiDate.date().format("YYYY-MM-DD HH:mm").required()
}).options({
stripUnknown: true,
abortEarly: false,
presence: "required",
});

export const updateRecruitmentSessionSchema = Joi.object<RecruitmentSession> ({
state: Joi.string()
.valid("active", "concluded")
.optional(),
slotDuration: Joi.number()
.integer(),
interviewStart: JoiDate.date().format("YYYY-MM-DD HH:mm").optional(),
interviewEnd: JoiDate.date().format("YYYY-MM-DD HH:mm").optional(),
// days:
// .optional(),
createdAt: JoiDate.date().format("YYYY-MM-DD HH:mm").optional(),
lastModified: JoiDate.date().format("YYYY-MM-DD HH:mm").optional()
});



/* Abilities */

export const applyAbilitiesOnRecruitmentSession: ApplyAbilities = (
user,
{ can, cannot }
) => {
can(Action.Manage, "RecruitmentSession");
switch (user.role) {
case Role.Admin:
case Role.Supervisor:
can(Action.Manage, "RecruitmentSession");
break;
case Role.Clerk: // puo o non puo ??????
whiitex marked this conversation as resolved.
Show resolved Hide resolved
case Role.Member:
case Role.Applicant:
whiitex marked this conversation as resolved.
Show resolved Hide resolved
can(Action.Read, "RecruitmentSession");
break;
default:
cannot(Action.Manage, "RecruitmentSession");
}
};
Loading