mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-11-26 10:14:54 +00:00
refactor: streamline Booking module and service by removing unused imports and consolidating space validation logic
This commit is contained in:
@ -1,16 +1,17 @@
|
|||||||
import { Global, Module } from '@nestjs/common';
|
import { Global, Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { BookableSpaceController } from './controllers';
|
import { BookableSpaceController } from './controllers';
|
||||||
import { BookableSpaceService } from './services';
|
import { BookableSpaceService } from './services';
|
||||||
import { BookableSpaceEntityRepository } from '@app/common/modules/booking/repositories';
|
import { BookableSpaceEntityRepository } from '@app/common/modules/booking/repositories';
|
||||||
import { BookableSpaceEntity } from '@app/common/modules/booking/entities';
|
import { SpaceRepository } from '@app/common/modules/space';
|
||||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
|
||||||
|
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([BookableSpaceEntity, SpaceEntity])],
|
|
||||||
controllers: [BookableSpaceController],
|
controllers: [BookableSpaceController],
|
||||||
providers: [BookableSpaceService, BookableSpaceEntityRepository],
|
providers: [
|
||||||
|
BookableSpaceService,
|
||||||
|
BookableSpaceEntityRepository,
|
||||||
|
SpaceRepository,
|
||||||
|
],
|
||||||
exports: [BookableSpaceService],
|
exports: [BookableSpaceService],
|
||||||
})
|
})
|
||||||
export class BookingModule {}
|
export class BookingModule {}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
// dtos/bookable-space.dto.ts
|
// dtos/bookable-space.dto.ts
|
||||||
import { DaysEnum } from '@app/common/constants/days.enum';
|
import { DaysEnum } from '@app/common/constants/days.enum';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Type } from 'class-transformer';
|
|
||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsEnum,
|
IsEnum,
|
||||||
@ -9,15 +8,26 @@ import {
|
|||||||
IsString,
|
IsString,
|
||||||
IsUUID,
|
IsUUID,
|
||||||
IsInt,
|
IsInt,
|
||||||
ValidateNested,
|
|
||||||
ArrayMinSize,
|
ArrayMinSize,
|
||||||
ArrayMaxSize,
|
|
||||||
Min,
|
|
||||||
Max,
|
Max,
|
||||||
|
Min,
|
||||||
Matches,
|
Matches,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
|
||||||
export class BookingSlotDto {
|
export class CreateBookableSpaceDto {
|
||||||
|
@ApiProperty({
|
||||||
|
type: 'string',
|
||||||
|
isArray: true,
|
||||||
|
example: [
|
||||||
|
'3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
||||||
|
'4fa85f64-5717-4562-b3fc-2c963f66afa7',
|
||||||
|
],
|
||||||
|
})
|
||||||
|
@IsArray()
|
||||||
|
@ArrayMinSize(1, { message: 'At least one space must be selected' })
|
||||||
|
@IsUUID('all', { each: true, message: 'Invalid space UUID provided' })
|
||||||
|
spaceUuids: string[];
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
enum: DaysEnum,
|
enum: DaysEnum,
|
||||||
isArray: true,
|
isArray: true,
|
||||||
@ -25,7 +35,6 @@ export class BookingSlotDto {
|
|||||||
})
|
})
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ArrayMinSize(1, { message: 'At least one day must be selected' })
|
@ArrayMinSize(1, { message: 'At least one day must be selected' })
|
||||||
@ArrayMaxSize(7, { message: 'Cannot select more than 7 days' })
|
|
||||||
@IsEnum(DaysEnum, { each: true, message: 'Invalid day provided' })
|
@IsEnum(DaysEnum, { each: true, message: 'Invalid day provided' })
|
||||||
daysAvailable: DaysEnum[];
|
daysAvailable: DaysEnum[];
|
||||||
|
|
||||||
@ -45,31 +54,9 @@ export class BookingSlotDto {
|
|||||||
})
|
})
|
||||||
endTime: string;
|
endTime: string;
|
||||||
|
|
||||||
@ApiProperty({ example: 10, minimum: 0 })
|
@ApiProperty({ example: 10 })
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(0, { message: 'Points cannot be negative' })
|
@Min(0, { message: 'Points cannot be negative' })
|
||||||
@Max(1000, { message: 'Points cannot exceed 1000' })
|
@Max(1000, { message: 'Points cannot exceed 1000' })
|
||||||
points: number;
|
points: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreateBookableSpaceDto {
|
|
||||||
@ApiProperty({
|
|
||||||
type: 'string',
|
|
||||||
isArray: true,
|
|
||||||
example: [
|
|
||||||
'3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
|
||||||
'4fa85f64-5717-4562-b3fc-2c963f66afa7',
|
|
||||||
],
|
|
||||||
})
|
|
||||||
@IsArray()
|
|
||||||
@ArrayMinSize(1, { message: 'At least one space must be selected' })
|
|
||||||
@IsUUID('all', { each: true, message: 'Invalid space UUID provided' })
|
|
||||||
spaceUuids: string[];
|
|
||||||
|
|
||||||
@ApiProperty({ type: BookingSlotDto, isArray: true })
|
|
||||||
@IsArray()
|
|
||||||
@ArrayMinSize(1, { message: 'At least one booking slot must be provided' })
|
|
||||||
@ValidateNested({ each: true })
|
|
||||||
@Type(() => BookingSlotDto)
|
|
||||||
slots: BookingSlotDto[];
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,50 +1,46 @@
|
|||||||
// services/bookable-space.service.ts
|
|
||||||
import {
|
import {
|
||||||
Injectable,
|
Injectable,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
ConflictException,
|
ConflictException,
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { CreateBookableSpaceDto } from '../dtos';
|
||||||
import { Repository, In } from 'typeorm';
|
|
||||||
import { BookingSlotDto, CreateBookableSpaceDto } from '../dtos';
|
|
||||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||||
import { BookableSpaceEntity } from '@app/common/modules/booking/entities';
|
|
||||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||||
|
import { BookableSpaceEntityRepository } from '@app/common/modules/booking/repositories';
|
||||||
|
import { SpaceRepository } from '@app/common/modules/space/repositories/space.repository';
|
||||||
|
import { In } from 'typeorm';
|
||||||
import { timeToMinutes } from '@app/common/helper/timeToMinutes';
|
import { timeToMinutes } from '@app/common/helper/timeToMinutes';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BookableSpaceService {
|
export class BookableSpaceService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(BookableSpaceEntity)
|
private readonly bookableSpaceEntityRepository: BookableSpaceEntityRepository,
|
||||||
private bookableRepo: Repository<BookableSpaceEntity>,
|
private readonly spaceRepository: SpaceRepository,
|
||||||
|
|
||||||
@InjectRepository(SpaceEntity)
|
|
||||||
private spaceRepo: Repository<SpaceEntity>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async create(dto: CreateBookableSpaceDto): Promise<BaseResponseDto> {
|
async create(dto: CreateBookableSpaceDto): Promise<BaseResponseDto> {
|
||||||
// Validate time slots first
|
// Validate time slots first
|
||||||
this.validateTimeSlots(dto.slots);
|
this.validateTimeSlot(dto.startTime, dto.endTime);
|
||||||
|
|
||||||
// Validate spaces exist
|
// fetch spaces exist
|
||||||
const spaces = await this.validateSpacesExist(dto.spaceUuids);
|
const spaces = await this.getSpacesOrFindMissing(dto.spaceUuids);
|
||||||
|
|
||||||
// Validate no duplicate bookable configurations
|
// Validate no duplicate bookable configurations
|
||||||
await this.validateNoDuplicateBookableConfigs(dto.spaceUuids);
|
await this.validateNoDuplicateBookableConfigs(dto.spaceUuids);
|
||||||
|
|
||||||
// Create and save bookable spaces
|
// Create and save bookable spaces
|
||||||
return this.createBookableSpaces(spaces, dto.slots);
|
return this.createBookableSpaces(spaces, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that all specified spaces exist
|
* Fetch spaces by UUIDs and throw an error if any are missing
|
||||||
*/
|
*/
|
||||||
private async validateSpacesExist(
|
private async getSpacesOrFindMissing(
|
||||||
spaceUuids: string[],
|
spaceUuids: string[],
|
||||||
): Promise<SpaceEntity[]> {
|
): Promise<SpaceEntity[]> {
|
||||||
const spaces = await this.spaceRepo.find({
|
const spaces = await this.spaceRepository.find({
|
||||||
where: { uuid: In(spaceUuids) },
|
where: { uuid: In(spaceUuids) },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -67,7 +63,7 @@ export class BookableSpaceService {
|
|||||||
private async validateNoDuplicateBookableConfigs(
|
private async validateNoDuplicateBookableConfigs(
|
||||||
spaceUuids: string[],
|
spaceUuids: string[],
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const existingBookables = await this.bookableRepo.find({
|
const existingBookables = await this.bookableSpaceEntityRepository.find({
|
||||||
where: { space: { uuid: In(spaceUuids) } },
|
where: { space: { uuid: In(spaceUuids) } },
|
||||||
relations: ['space'],
|
relations: ['space'],
|
||||||
});
|
});
|
||||||
@ -83,19 +79,17 @@ export class BookableSpaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate time slots have valid format and logical times
|
* Ensure the slot start time is before the end time
|
||||||
*/
|
*/
|
||||||
private validateTimeSlots(slots: BookingSlotDto[]): void {
|
private validateTimeSlot(startTime: string, endTime: string): void {
|
||||||
slots.forEach((slot) => {
|
const start = timeToMinutes(startTime);
|
||||||
const start = timeToMinutes(slot.startTime);
|
const end = timeToMinutes(endTime);
|
||||||
const end = timeToMinutes(slot.endTime);
|
|
||||||
|
|
||||||
if (start >= end) {
|
if (start >= end) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
`End time must be after start time for slot: ${slot.startTime}-${slot.endTime}`,
|
`End time must be after start time for slot: ${startTime}-${endTime}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,22 +97,20 @@ export class BookableSpaceService {
|
|||||||
*/
|
*/
|
||||||
private async createBookableSpaces(
|
private async createBookableSpaces(
|
||||||
spaces: SpaceEntity[],
|
spaces: SpaceEntity[],
|
||||||
slots: BookingSlotDto[],
|
dto: CreateBookableSpaceDto,
|
||||||
): Promise<BaseResponseDto> {
|
): Promise<BaseResponseDto> {
|
||||||
try {
|
try {
|
||||||
const entries = spaces.flatMap((space) =>
|
const entries = spaces.map((space) =>
|
||||||
slots.map((slot) =>
|
this.bookableSpaceEntityRepository.create({
|
||||||
this.bookableRepo.create({
|
space,
|
||||||
space,
|
daysAvailable: dto.daysAvailable,
|
||||||
daysAvailable: slot.daysAvailable,
|
startTime: dto.startTime,
|
||||||
startTime: slot.startTime,
|
endTime: dto.endTime,
|
||||||
endTime: slot.endTime,
|
points: dto.points,
|
||||||
points: slot.points,
|
}),
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = await this.bookableRepo.save(entries);
|
const data = await this.bookableSpaceEntityRepository.save(entries);
|
||||||
return new SuccessResponseDto({
|
return new SuccessResponseDto({
|
||||||
data,
|
data,
|
||||||
message: 'Successfully added new bookable spaces',
|
message: 'Successfully added new bookable spaces',
|
||||||
|
|||||||
Reference in New Issue
Block a user