Skip to content

Commit

Permalink
Created availability module
Browse files Browse the repository at this point in the history
  • Loading branch information
markdeluk committed Dec 9, 2023
1 parent 27f282f commit 1d62900
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 8 deletions.
118 changes: 118 additions & 0 deletions api/src/availability/availability.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { Body, Controller, Delete, Param, Patch, Post } from '@nestjs/common';
import { AvailabilityService } from './availability.service';
import {
Action,
insertAvailabilitySchema,
updateAvailabilitySchema,
} from '@hkrecruitment/shared';
import { JoiValidate } from '../joi-validation/joi-validate.decorator';
import {
ApiBadRequestResponse,
ApiBearerAuth,
ApiForbiddenResponse,
ApiNotFoundResponse,
ApiCreatedResponse,
ApiOkResponse,
ApiTags,
ApiConflictResponse,
ApiNoContentResponse,
ApiBadGatewayResponse,
} from '@nestjs/swagger';
import { CheckPolicies } from 'src/authorization/check-policies.decorator';
import { Availability } from './availability.entity';
import Joi from 'joi';

@ApiBearerAuth()
@ApiTags('timeslots')
@Controller('timeslots')
export class AvailabilityController {
constructor(private readonly availabilityService: AvailabilityService) {}

@ApiBadRequestResponse()
@ApiForbiddenResponse()
@ApiNotFoundResponse()
@ApiOkResponse()
@ApiNoContentResponse()
@ApiBadGatewayResponse()
async listAvailabilities(): Promise<Availability[]> {
const matches = await this.availabilityService.listAvailabilities();
return matches;
}

@ApiBadRequestResponse()
@ApiForbiddenResponse()
@ApiNotFoundResponse()
@ApiOkResponse()
@ApiNoContentResponse()
@ApiBadGatewayResponse()
@JoiValidate({
param: Joi.number().positive().integer().required(),
})
async findAvailabilityById(id: number): Promise<Availability> {
const matches = await this.availabilityService.findAvailabilityById(id);
return matches;
}

@ApiBadRequestResponse()
@ApiForbiddenResponse()
@ApiNotFoundResponse()
@ApiOkResponse()
@ApiNoContentResponse()
@ApiBadGatewayResponse()
@ApiConflictResponse()
@ApiCreatedResponse()
@CheckPolicies((ability) => ability.can(Action.Create, 'Availability'))
@Post()
@JoiValidate({
body: insertAvailabilitySchema,
})
async createAvailability(@Body() body: Availability): Promise<boolean> {
const res = await this.availabilityService.createAvailability(body);
if (res.identifiers.length > 0) {
return true;
}
return false;
}

@ApiBadRequestResponse()
@ApiForbiddenResponse()
@ApiNotFoundResponse()
@ApiOkResponse()
@ApiNoContentResponse()
@ApiBadGatewayResponse()
@ApiConflictResponse()
@ApiCreatedResponse()
@CheckPolicies((ability) => ability.can(Action.Create, 'Availability'))
@Patch()
@JoiValidate({
body: updateAvailabilitySchema,
})
async updateAvailability(@Body() body: Availability): Promise<boolean> {
const res = await this.availabilityService.updateAvailability(body);
if (res.affected != undefined && res.affected != null && res.affected > 0) {
return true;
}
return false;
}

@ApiBadRequestResponse()
@ApiForbiddenResponse()
@ApiNotFoundResponse()
@ApiOkResponse()
@ApiNoContentResponse()
@ApiBadGatewayResponse()
@ApiConflictResponse()
@ApiCreatedResponse()
@CheckPolicies((ability) => ability.can(Action.Create, 'Availability'))
@Delete()
@JoiValidate({
param: Joi.number().positive().integer().required(),
})
async deleteAvailability(@Param() id: number): Promise<boolean> {
const res = await this.availabilityService.deleteAvailability(id);
if (res.affected != undefined && res.affected != null && res.affected > 0) {
return true;
}
return false;
}
}
28 changes: 28 additions & 0 deletions api/src/availability/availability.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
Column,
Entity,
JoinColumn,
OneToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import {
Availability as AvailabilityInterface,
AvailabilityState,
} from '@hkrecruitment/shared/availability';
import { User } from 'src/users/user.entity';
import { TimeSlot } from 'src/timeslots/timeslot.entity';

@Entity()
export class Availability implements AvailabilityInterface {
@PrimaryGeneratedColumn('increment')
id: number;
@Column()
state: AvailabilityState;
@Column()
lastModified: Date;
@OneToOne(() => TimeSlot)
timeSlot: TimeSlot;
@OneToOne(() => User)
@JoinColumn()
user: User;
}
14 changes: 14 additions & 0 deletions api/src/availability/availability.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { AvailabilityService } from './availability.service';
import { AvailabilityController } from './availability.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Availability } from './availability.entity';
import { UsersModule } from 'src/users/users.module';

@Module({
imports: [TypeOrmModule.forFeature([Availability]), UsersModule],
providers: [AvailabilityService],
controllers: [AvailabilityController],
exports: [AvailabilityService],
})
export class AvailabilityModule {}
38 changes: 38 additions & 0 deletions api/src/availability/availability.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DeleteResult, InsertResult, Repository, UpdateResult } from 'typeorm';
import { Availability } from './availability.entity';

@Injectable()
export class AvailabilityService {
constructor(
@InjectRepository(Availability)
private readonly availabilityRepository: Repository<Availability>,
) {}

async listAvailabilities(): Promise<Availability[]> {
return await this.availabilityRepository.find();
}

async findAvailabilityById(id: number): Promise<Availability> {
const matches = await this.availabilityRepository.findBy({
id: id,
});
return matches.length > 0 ? matches[0] : null;
}

async createAvailability(availability: Availability): Promise<InsertResult> {
return await this.availabilityRepository.insert(availability);
}

async updateAvailability(availability: Availability): Promise<UpdateResult> {
return await this.availabilityRepository.update(
availability.id,
availability,
);
}

async deleteAvailability(id: number): Promise<DeleteResult> {
return await this.availabilityRepository.delete(id);
}
}
27 changes: 20 additions & 7 deletions shared/src/availability.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TimeSlot, createTimeSlotSchema } from "timeslot";
import { Action, ApplyAbilities } from "./abilities";
import { Person, Role } from "./person";
import { Person, Role, createUserSchema } from "./person";
import * as Joi from "joi";

export enum AvailabilityState {
Expand All @@ -14,21 +15,33 @@ export enum AvailabilityType {
}

export interface Availability {
id: number;
state: AvailabilityState;
timeSlotId: number;
member: Person;
// assignedAt?: Date;
// confirmedAt?: Date;
// cancelledAt?: Date;
lastModified: Date;
timeSlot: TimeSlot;
user: Person;
}

/* Validation schemas */

export const insertAvailabilitySchema = Joi.object<Availability>({
state: Joi.string()
.valid(...Object.values(AvailabilityType))
.required(),
lastModified: Joi.date().required(),
timeSlot: createTimeSlotSchema.required(),
user: createUserSchema.required(),
}).options({
stripUnknown: true,
abortEarly: false,
presence: "required",
});

export const updateAvailabilitySchema = Joi.object<Availability>({
id: Joi.number().positive().integer().required(),
state: Joi.string()
.valid(...Object.values(AvailabilityType))
.required(),
timeSlotId: Joi.number().positive().required(),
}).options({
stripUnknown: true,
abortEarly: false,
Expand Down
1 change: 0 additions & 1 deletion shared/src/timeslot.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Action, ApplyAbilities } from "./abilities";
import { Role } from "./person";
import DateExtension from "@joi/date";
import * as Joi from "joi";
const JoiDate = Joi.extend(DateExtension);
Expand Down

0 comments on commit 1d62900

Please sign in to comment.