Skip to content

Commit

Permalink
feat: controller, Service, Entity, Module, for interview
Browse files Browse the repository at this point in the history
  • Loading branch information
none authored and none committed Dec 15, 2023
1 parent 4dc60a1 commit a9eb49f
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 3 deletions.
24 changes: 24 additions & 0 deletions api/src/interview/create-interview.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Interview } from '@hkrecruitment/shared';
import { User } from '../users/user.entity'
import { ApiProperty } from '@nestjs/swagger';


export class CreateInterviewDto implements Partial<Interview> {
@ApiProperty()
notes: string;

@ApiProperty()
created_at: Date;

@ApiProperty()
id_timeslot: number;

@ApiProperty()
interviewer_1: User;

@ApiProperty()
interviewer_2: User;

@ApiProperty({ required: false })
optional_interviewer?: User;
}
124 changes: 124 additions & 0 deletions api/src/interview/interview.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
Get,
Param,
ForbiddenException,
NotFoundException,
Controller,
Patch,
Body,
Post,
Req,
Delete
} from '@nestjs/common';
import { Interview } from './interview.entity';
import { InterviewService } from './interview.Service';
import { CreateInterviewDto } from './create-interview.dto';
import { UpdateInterviewDto } from './update-interview.dto';
import {
Action,
AppAbility,
checkAbility,
createInterviewSchema,
updateInterviewSchema,
} from '@hkrecruitment/shared';
import { JoiValidate } from 'src/joi-validation/joi-validate.decorator';
import {
ApiBadRequestResponse,
ApiBearerAuth,
ApiNotFoundResponse,
ApiTags,
} from '@nestjs/swagger';
import { AuthenticatedRequest } from 'src/authorization/authenticated-request.types';
import * as Joi from 'joi';
import { CheckPolicies } from 'src/authorization/check-policies.decorator';
import { Ability } from 'src/authorization/ability.decorator';

@ApiBearerAuth()
@ApiTags('interview')
@Controller('interview')

export class InterviewController {
constructor(private readonly interviewService: InterviewService) {}

@ApiNotFoundResponse()
@ApiBadRequestResponse()
@Get(':Id')
@JoiValidate({
param: Joi.number().required(),
})
@CheckPolicies((ability) => ability.can(Action.Read, 'Interview'))
async findById(
@Param('Id') Id: number,
@Ability() ability: AppAbility,
): Promise<Interview> {
const interview = await this.interviewService.findById(Id);
if (interview === null) {
throw new NotFoundException();
}
if (!checkAbility(ability, Action.Read, interview, 'Interview')) {
throw new ForbiddenException();
}
return interview;
}

@ApiNotFoundResponse()
@ApiBadRequestResponse()
@Delete(':Id')
@JoiValidate({
param: Joi.number().required(),
})
@CheckPolicies((ability) => ability.can(Action.Delete, 'Interview'))
async delete(
@Param('Id') Id: number,
@Ability() ability: AppAbility,
): Promise<Interview> {
const interview = await this.interviewService.findById(Id);
if (interview === null) {
throw new NotFoundException();
}
return this.interviewService.delete(interview);
}

@ApiNotFoundResponse()
@ApiBadRequestResponse()
@Patch(':Id')
@JoiValidate({
body: updateInterviewSchema,
param: Joi.number().required(),
})
@CheckPolicies((ability) => ability.can(Action.Update, 'Interview'))
async update(
@Param('Id') Id: number,
@Body() updateInterview: UpdateInterviewDto,
@Ability() ability: AppAbility,
@Req() req: AuthenticatedRequest,
): Promise<Interview> {
const interview = await this.interviewService.findById(Id);
if (interview === null) {
throw new NotFoundException();
}
return this.interviewService.update({
...interview,
...updateInterview,
});
}

@ApiNotFoundResponse()
@ApiBadRequestResponse()
@Post()
@JoiValidate({
body: createInterviewSchema,
})
async create(
@Body() interview: CreateInterviewDto,
@Ability() ability: AppAbility,
@Req() req: AuthenticatedRequest,
): Promise<Interview> {
if (!checkAbility(ability, Action.Create, interview, 'Interview')){
throw new ForbiddenException()};
return this.interviewService.create({

});
}
}

32 changes: 32 additions & 0 deletions api/src/interview/interview.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import {Interview as InterviewSlot} from '@hkrecruitment/shared';
import {User} from '../users/user.entity';
import { TimeSlot } from '../timeslots/timeslot.entity';
import { Application } from '../application/application.entity';

@Entity()
export class Interview implements InterviewSlot {
@PrimaryGeneratedColumn('increment')
id: number;

@Column()
notes: string;

@Column()
created_at: Date;

@Column()
timeslot: TimeSlot;

@Column()
application: Application;

@Column()
interviewer_1: User;

@Column()
interviewer_2: User;

@Column({nullable: true})
optional_interviewer?: User;
}
13 changes: 13 additions & 0 deletions api/src/interview/interview.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { InterviewService } from './interview.service';
import { InterviewController } from './interview.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Interview } from './interview.entity';

@Module({
imports: [TypeOrmModule.forFeature([Interview])],
providers: [InterviewService],
controllers: [InterviewController],
exports: [InterviewService],
})
export class InterviewModule {}
32 changes: 32 additions & 0 deletions api/src/interview/interview.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Interview } from "./interview.entity"
import { Application } from "../application/application.entity";
import { TimeSlot } from "../timeslots/timeslot.entity";
import { InjectRepository } from '@nestjs/typeorm';
import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { CreateInterviewDto } from "./create-interview.dto";

@Injectable()
export class InterviewService {
constructor(
@InjectRepository(Interview)
private readonly interviewRepository: Repository<Interview>,
) {}

async findById(id: number): Promise<Interview | null> {
return this.interviewRepository.findOne({ where: { id } });
}

async delete(interview: Interview): Promise<Interview> {
return await this.interviewRepository.remove(interview);
}

async create(interview: CreateInterviewDto, application: Application, timeslot: TimeSlot): Promise<Interview> {
return await this.interviewRepository.save(interview);
}

async update(interview: Interview): Promise<Interview> {
return await this.interviewRepository.save(interview);
}
}

28 changes: 28 additions & 0 deletions api/src/interview/update-interview.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ApiProperty, PartialType } from '@nestjs/swagger';
import { Interview } from '@hkrecruitment/shared';
import { User } from '../users/user.entity'
import { TimeSlot } from '../timeslots/timeslot.entity';
import { Application } from '../application/application.entity';

export class UpdateInterviewDto implements Partial<Interview>{
@ApiProperty({required: false})
notes: string;

@ApiProperty({required: false})
created_at: Date;

@ApiProperty({required: false})
timeslot: TimeSlot;

@ApiProperty({required: false})
application: Application;

@ApiProperty({required: false})
interviewer_1: User;

@ApiProperty({required: false})
interviewer_2: User;

@ApiProperty({ required: false })
optional_interviewer?: User;
}
9 changes: 6 additions & 3 deletions shared/src/abilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
import { applyAbilitiesForPerson, Person, Role } from "./person";
import { Application, applyAbilitiesOnApplication } from "./application";
import { applyAbilitiesOnAvailability, Availability } from "./availability";
import { TimeSlot } from "./timeslot";
import { TimeSlot } from "./timeslot"
import { applyAbilitiesOnInterview, Interview } from "./interview";

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<Interview>;
type SubjectNames = "Person" | "Application" | "Availability" | "TimeSlot" | "Interview";
export type Subjects = SubjectsTypes | SubjectNames;

export type AppAbility = PureAbility<[Action, Subjects]>;
Expand All @@ -44,6 +46,7 @@ export const abilityForUser = (user: UserAuth): AppAbility => {
applyAbilitiesForPerson(user, builder);
applyAbilitiesOnApplication(user, builder);
applyAbilitiesOnAvailability(user, builder);
applyAbilitiesOnInterview(user, builder);

const { build } = builder;
return build();
Expand Down
1 change: 1 addition & 0 deletions shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from "./application";
export * from "./availability";
export * from "./timeslot";
export * from "./slot";
export * from "./interview"
47 changes: 47 additions & 0 deletions shared/src/interview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Person, createUserSchema, updateUserSchema, Role } from "./person";
import { TimeSlot, createTimeSlotSchema } from "timeslot";
import { Application, createApplicationSchema, updateApplicationSchema } from "application";
import { Action, ApplyAbilities } from "./abilities";
import * as Joi from "joi";

export interface Interview {
id: number;
notes: string;
created_at: Date;
timeslot: TimeSlot;
application: Application;
interviewer_1: Person;
interviewer_2: Person;
optional_interviewer?: Person;
};

export const createInterviewSchema = Joi.object<Interview>({
notes: Joi.string().required(),
created_at: Joi.date().required(),
interviewer_1: createUserSchema.required(),
interviewer_2: createUserSchema.required(),
optional_interviewer: createUserSchema.optional(),
});

export const updateInterviewSchema = Joi.object<Interview>({
notes: Joi.string().optional(),
created_at: Joi.date().optional(),
timeslot: createTimeSlotSchema.optional(),
application: updateApplicationSchema.optional(),
interviewer_1: updateUserSchema.optional(),
interviewer_2: updateUserSchema.optional(),
optional_interviewer: updateUserSchema.optional()
});

export const applyAbilitiesOnInterview: ApplyAbilities = (
user,
{ can, cannot }
) => {
if (user.role === Role.Admin || user.role === Role.Supervisor) {
can(Action.Manage, "Interview");
}
else{
cannot(Action.Manage, "Interview")
}
};

0 comments on commit a9eb49f

Please sign in to comment.