Skip to content

Commit

Permalink
Recruitment Session and Interview Slots (#28)
Browse files Browse the repository at this point in the history
- Added findAvailableTimeSlots method to retrieve time slots to hold interviews
- Recruitment Session Controller, Service, Repository & Entity
- CRUD operations for Recruitment Session
- Improved API code coverage
- Improved methods documentation
- Refactored fronted file and folder structure

---------

Co-authored-by: ozerodb <[email protected]>
Co-authored-by: whiteOFF <[email protected]>
Co-authored-by: Alberto Baroso <[email protected]>
Co-authored-by: Vincenzo Pellegrini <[email protected]>
Co-authored-by: Mugna0990 <[email protected]>
  • Loading branch information
6 people authored Jun 1, 2024
1 parent 6f0fa2e commit b97612e
Showing 54 changed files with 4,435 additions and 2,483 deletions.
8 changes: 1 addition & 7 deletions .github/workflows/fullstack.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
name: Test

on:
push:
branches:
- '*'
pull_request:
branches:
- '*'
on: [push, pull_request]

jobs:
tests:
86 changes: 43 additions & 43 deletions api/package.json
Original file line number Diff line number Diff line change
@@ -22,63 +22,63 @@
"clean": "rimraf dist"
},
"dependencies": {
"@casl/ability": "^6.3.3",
"@fastify/static": "^6.6.0",
"@casl/ability": "^6.7.1",
"@fastify/static": "^6.12.0",
"@hkrecruitment/shared": "workspace:*",
"@joi/date": "^2.1.0",
"@nestjs/common": "^9.0.0",
"@nestjs/config": "^2.2.0",
"@nestjs/core": "^9.0.0",
"@nestjs/passport": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/swagger": "^6.1.3",
"@joi/date": "^2.1.1",
"@nestjs/common": "^9.4.3",
"@nestjs/config": "^2.3.4",
"@nestjs/core": "^9.4.3",
"@nestjs/passport": "^9.0.3",
"@nestjs/platform-express": "^9.4.3",
"@nestjs/swagger": "^6.3.0",
"@nestjs/typeorm": "^9.0.1",
"@types/js-yaml": "^4.0.5",
"@types/passport-jwt": "^3.0.7",
"@types/js-yaml": "^4.0.9",
"@types/passport-jwt": "^3.0.13",
"class-transformer": "^0.5.1",
"dotenv": "^16.0.3",
"google-auth-library": "^8.7.0",
"dotenv": "^16.4.5",
"google-auth-library": "^8.9.0",
"googleapis": "^118.0.0",
"jest-mock-extended": "^3.0.5",
"joi": "^17.7.0",
"jest-mock-extended": "^3.0.7",
"joi": "^17.13.1",
"js-yaml": "^4.1.0",
"jwks-rsa": "^3.0.0",
"jwks-rsa": "^3.1.0",
"passport": "^0.6.0",
"passport-jwt": "^4.0.0",
"pg": "^8.4.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0",
"passport-jwt": "^4.0.1",
"pg": "^8.11.5",
"reflect-metadata": "^0.1.14",
"rxjs": "^7.8.1",
"typeorm": "0.3.11",
"webpack": "^5.75.0"
"webpack": "^5.91.0"
},
"devDependencies": {
"@automock/jest": "^1.0.1",
"@golevelup/ts-jest": "^0.3.6",
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@swc/core": "^1.3.56",
"@swc/jest": "^0.2.26",
"@types/express": "^4.17.14",
"@automock/jest": "^1.4.0",
"@golevelup/ts-jest": "^0.3.8",
"@nestjs/cli": "^9.5.0",
"@nestjs/schematics": "^9.2.0",
"@nestjs/testing": "^9.4.3",
"@swc/core": "^1.5.5",
"@swc/jest": "^0.2.36",
"@types/express": "^4.17.21",
"@types/jest": "28.1.8",
"@types/multer": "^1.4.7",
"@types/node": "^16.11.10",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"@types/multer": "^1.4.11",
"@types/node": "^16.18.97",
"@types/supertest": "^2.0.16",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-prettier": "^4.2.1",
"jest": "28.1.3",
"jose": "^4.14.4",
"jose": "^4.15.5",
"nodemon": "^2.0.22",
"prettier": "^2.8.5",
"prettier": "^2.8.8",
"rimraf": "^3.0.2",
"source-map-support": "^0.5.20",
"sqlite3": "^5.1.6",
"supertest": "^6.1.3",
"source-map-support": "^0.5.21",
"sqlite3": "^5.1.7",
"supertest": "^6.3.4",
"ts-jest": "28.0.8",
"ts-loader": "^9.2.3",
"ts-loader": "^9.5.1",
"ts-node": "10.7.0",
"tsconfig-paths": "4.1.0",
"typescript": "4.5.2"
2 changes: 0 additions & 2 deletions api/src/availability/availability.entity.ts
Original file line number Diff line number Diff line change
@@ -11,7 +11,6 @@ import {
} from '../../../shared/src/availability';
import { User } from 'src/users/user.entity';
import { TimeSlot } from 'src/timeslots/timeslot.entity';
import { DbAwareColumn } from 'src/utils/db-aware-column';

@Entity()
export class Availability implements AvailabilityInterface {
@@ -24,7 +23,6 @@ export class Availability implements AvailabilityInterface {
@Column({ name: 'last_modified' })
lastModified: Date;

@DbAwareColumn(() => TimeSlot, { name: 'time_slot' })
@ManyToOne(() => TimeSlot, (timeSlot) => timeSlot.availabilities)
timeSlot: Relation<TimeSlot>;

29 changes: 29 additions & 0 deletions api/src/availability/availability.service.spec.ts
Original file line number Diff line number Diff line change
@@ -78,6 +78,28 @@ describe('AvailabilityService', () => {
});
});

describe('updateAvailability', () => {
it('should update the availability with the specified id', async () => {
const mockUpdatedAvailability = {
...mockAvailability,
state: AvailabilityState.Interviewing,
};
jest
.spyOn(mockedRepository, 'save')
.mockResolvedValue(mockUpdatedAvailability);
const result = await service.updateAvailability(
mockAvailability.id,
mockUpdatedAvailability,
);
expect(result).toEqual(mockUpdatedAvailability);
expect(mockedRepository.save).toHaveBeenCalledTimes(1);
expect(mockedRepository.save).toHaveBeenCalledWith({
...mockUpdatedAvailability,
id: mockAvailability.id,
});
});
});

describe('deleteAvailability', () => {
it('should remove the specified availability from the database', async () => {
const mockAvailabilityRepository = {
@@ -142,6 +164,13 @@ describe('AvailabilityService', () => {
expect(result).toEqual(mockAvailability);
expect(mockedRepository.findBy).toHaveBeenCalledTimes(1);
});

it('should return null if no availability is found', async () => {
jest.spyOn(mockedRepository, 'findBy').mockResolvedValue([]);
const result = await service.findById(mockAvailability.id);
expect(result).toBeNull();
expect(mockedRepository.findBy).toHaveBeenCalledTimes(1);
});
});

describe('findByUserAndTimeSlot', () => {
35 changes: 33 additions & 2 deletions api/src/availability/availability.service.ts
Original file line number Diff line number Diff line change
@@ -16,29 +16,54 @@ export class AvailabilityService {
private dataSource: DataSource,
) {}

/**
* List all availabilities
* @returns {Promise<Availability[]>} - List of availabilities
*/
async listAvailabilities(): Promise<Availability[]> {
return await this.availabilityRepository.find();
}

/**
* Find all availabilities for a given user
* @param user - User to find availabilities for
* @returns {Promise<Availability[]>} - List of availabilities for the user
*/
async findById(id: number): Promise<Availability> {
const matches = await this.availabilityRepository.findBy({
id: id,
});
return matches.length > 0 ? matches[0] : null;
}

/**
* Find all availabilities for a given user
* @param user - User to find availabilities for
* @returns {Promise<Availability[]>} - List of availabilities for the user
*/
async findByUserAndTimeSlot(user: User, timeSlot: TimeSlot) {
const matches = await this.availabilityRepository.findBy({
user: user,
timeSlot: timeSlot,
user: user as any,
timeSlot: timeSlot as any,
});
return matches.length > 0 ? matches[0] : null;
}

/**
* Create an availability
* @param availability - Availability to create
* @returns {Promise<Availability>} - Created availability
*/
async createAvailability(availability: Availability): Promise<Availability> {
return await this.availabilityRepository.save(availability);
}

/**
* Update an availability
* @param oldAvailabilityId - ID of the
* @param newAvailability - New availability
* @returns {Promise<Availability>} - Updated availability
*/
async updateAvailability(
oldAvailabilityId: number,
newAvailability: Availability,
@@ -49,6 +74,12 @@ export class AvailabilityService {
});
}

/**
* Delete an availability
* @param availabilityId - ID of the availability to delete
* @returns {Promise<Availability>} - Deleted availability
* @throws {ConflictException} - If availability is in use
*/
async deleteAvailability(availabilityId: number): Promise<Availability> {
return await transaction(
this.dataSource,
90 changes: 83 additions & 7 deletions api/src/mocks/data.ts
Original file line number Diff line number Diff line change
@@ -28,13 +28,6 @@ export const testDateTime1Hour = new Date(2023, 0, 1, 11, 30, 0);
export const testDateTime3Hours = new Date(2023, 0, 1, 13, 30, 0);
export const testDateTimeEnd = new Date(2023, 0, 1, 11, 30, 0);

export const mockTimeSlot = {
start: testDateTimeStart,
end: testDateTimeEnd,
id: 1,
availabilities: [],
} as TimeSlot & { availabilities: any[] };

export const testInterviewStart = '11:55' as unknown as Date;
export const testInterviewEnd = '20:35' as unknown as Date;
export const testDay1 = '2024-10-20' as unknown as Date;
@@ -54,6 +47,14 @@ export const mockRecruitmentSession = {
lastModified: testDateLastModified,
};

export const mockTimeSlot = {
recruitmentSession: mockRecruitmentSession,
start: testDateTimeStart,
end: testDateTimeEnd,
id: 1,
availabilities: [],
} as TimeSlot & { availabilities: any[] };

export const mockCreateRecruitmentSessionDto = {
slotDuration: 50,
interviewStart: testInterviewStart,
@@ -228,3 +229,78 @@ export const mockAvailability = {
export const mockCreateAvailabilityDto = {
timeSlotId: mockTimeSlot.id,
} as CreateAvailabilityDto;

export const mockTimeSlotsJoined = [
{
start: new Date('2022-01-01T09:00:00'),
end: new Date('2022-01-01T10:00:00'),
id: 1,
recruitmentSession: 3,
availabilities: [
{
state: AvailabilityState.Interviewing,
user: {
role: Role.None,
is_board: false,
is_expert: true,
},
},
{
state: AvailabilityState.Free,
user: {
role: Role.Member,
is_board: true,
is_expert: false,
},
},
{
state: AvailabilityState.Free,
user: {
role: Role.Applicant,
is_board: false,
is_expert: false,
},
},
{
state: AvailabilityState.Free,
user: {
role: Role.Member,
is_board: false,
is_expert: true,
},
},
{
state: AvailabilityState.Interviewing,
user: {
role: Role.Member,
is_board: false,
is_expert: true,
},
},
{
state: AvailabilityState.Interviewing,
user: {
role: Role.Admin,
is_board: false,
is_expert: true,
},
},
],
},
{
start: new Date('2022-01-01T9:00:00'),
end: new Date('2022-01-01T20:00:00'),
id: 2,
recruitmentSession: 3,
availabilities: [
{
state: AvailabilityState.Free,
user: {
role: Role.Member,
is_board: true,
is_expert: true,
},
},
],
},
];
Loading

0 comments on commit b97612e

Please sign in to comment.