From a585eea3295348e0344b1c64494e493784f505cb Mon Sep 17 00:00:00 2001 From: Mhd Zayd Skaff Date: Thu, 10 Jul 2025 17:46:24 +0300 Subject: [PATCH] add get All api for dashboard & mobile --- libs/common/src/constants/controller-route.ts | 5 + src/booking/controllers/booking.controller.ts | 101 ++++++++++++------ src/booking/dtos/booking-request.dto.ts | 14 +++ src/booking/dtos/booking-response.dto.ts | 90 ++++++++++++++++ src/booking/dtos/my-booking-request.dto.ts | 14 +++ src/booking/services/booking.service.ts | 80 ++++++-------- 6 files changed, 224 insertions(+), 80 deletions(-) create mode 100644 src/booking/dtos/booking-request.dto.ts create mode 100644 src/booking/dtos/booking-response.dto.ts create mode 100644 src/booking/dtos/my-booking-request.dto.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 6ff5088..a9d4607 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -103,6 +103,11 @@ export class ControllerRoute { public static readonly GET_ALL_BOOKINGS_DESCRIPTION = 'This endpoint retrieves all bookings.'; + + public static readonly GET_MY_BOOKINGS_SUMMARY = 'Get my bookings'; + + public static readonly GET_MY_BOOKINGS_DESCRIPTION = + 'This endpoint retrieves all bookings for the authenticated user.'; }; }; static COMMUNITY = class { diff --git a/src/booking/controllers/booking.controller.ts b/src/booking/controllers/booking.controller.ts index bf22847..bdbe516 100644 --- a/src/booking/controllers/booking.controller.ts +++ b/src/booking/controllers/booking.controller.ts @@ -2,11 +2,24 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; -import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common'; +import { + Body, + Controller, + Get, + Post, + Query, + Req, + UseGuards, +} from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { plainToInstance } from 'class-transformer'; +import { AdminRoleGuard } from 'src/guards/admin.role.guard'; +import { BookingRequestDto } from '../dtos/booking-request.dto'; +import { BookingResponseDto } from '../dtos/booking-response.dto'; import { CreateBookingDto } from '../dtos/create-booking.dto'; +import { MyBookingRequestDto } from '../dtos/my-booking-request.dto'; import { BookingService } from '../services/booking.service'; @ApiTags('Booking Module') @@ -39,38 +52,56 @@ export class BookingController { }); } - // @ApiBearerAuth() - // @UseGuards(JwtAuthGuard) - // @Get() - // @ApiOperation({ - // summary: - // ControllerRoute.BOOKABLE_SPACES.ACTIONS.GET_ALL_BOOKABLE_SPACES_SUMMARY, - // description: - // ControllerRoute.BOOKABLE_SPACES.ACTIONS - // .GET_ALL_BOOKABLE_SPACES_DESCRIPTION, - // }) - // async findAll( - // @Query() query: BookableSpaceRequestDto, - // @Req() req: Request, - // ): Promise> { - // const project = req['user']?.project?.uuid; - // if (!project) { - // throw new Error('Project UUID is required in the request'); - // } - // const { data, pagination } = await this.bookableSpaceService.findAll( - // query, - // project, - // ); - // return new PageResponse( - // { - // data: data.map((space) => - // plainToInstance(BookableSpaceResponseDto, space, { - // excludeExtraneousValues: true, - // }), - // ), - // message: 'Successfully fetched all bookable spaces', - // }, - // pagination, - // ); - // } + @ApiBearerAuth() + @UseGuards(AdminRoleGuard) + @Get() + @ApiOperation({ + summary: ControllerRoute.BOOKING.ACTIONS.GET_ALL_BOOKINGS_SUMMARY, + description: ControllerRoute.BOOKING.ACTIONS.GET_ALL_BOOKINGS_DESCRIPTION, + }) + async findAll( + @Query() query: BookingRequestDto, + @Req() req: Request, + ): Promise { + const project = req['user']?.project?.uuid; + if (!project) { + throw new Error('Project UUID is required in the request'); + } + const result = await this.bookingService.findAll(query, project); + return new SuccessResponseDto({ + data: plainToInstance(BookingResponseDto, result, { + excludeExtraneousValues: true, + }), + message: 'Successfully fetched all bookings', + }); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('my-bookings') + @ApiOperation({ + summary: ControllerRoute.BOOKING.ACTIONS.GET_MY_BOOKINGS_SUMMARY, + description: ControllerRoute.BOOKING.ACTIONS.GET_MY_BOOKINGS_DESCRIPTION, + }) + async findMyBookings( + @Query() query: MyBookingRequestDto, + @Req() req: Request, + ): Promise { + const userUuid = req['user']?.uuid; + const project = req['user']?.project?.uuid; + if (!project) { + throw new Error('Project UUID is required in the request'); + } + const result = await this.bookingService.findMyBookings( + query, + userUuid, + project, + ); + return new SuccessResponseDto({ + data: plainToInstance(BookingResponseDto, result, { + excludeExtraneousValues: true, + }), + message: 'Successfully fetched all bookings', + }); + } } diff --git a/src/booking/dtos/booking-request.dto.ts b/src/booking/dtos/booking-request.dto.ts new file mode 100644 index 0000000..dd61d14 --- /dev/null +++ b/src/booking/dtos/booking-request.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, Matches } from 'class-validator'; + +export class BookingRequestDto { + @ApiProperty({ + description: 'Month in MM/YYYY format', + example: '07/2025', + }) + @IsNotEmpty() + @Matches(/^(0[1-9]|1[0-2])\/\d{4}$/, { + message: 'Date must be in MM/YYYY format', + }) + month: string; +} diff --git a/src/booking/dtos/booking-response.dto.ts b/src/booking/dtos/booking-response.dto.ts new file mode 100644 index 0000000..62c37f9 --- /dev/null +++ b/src/booking/dtos/booking-response.dto.ts @@ -0,0 +1,90 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Expose, Transform, Type } from 'class-transformer'; + +export class BookingUserResponseDto { + @ApiProperty() + @Expose() + uuid: string; + + @ApiProperty() + @Expose() + firstName: string; + + @ApiProperty() + @Expose() + lastName: string; + + @ApiProperty({ + type: String, + nullable: true, + }) + @Expose() + email: string; + + @ApiProperty({ + type: String, + nullable: true, + }) + @Expose() + @Transform(({ obj }) => { + return { + companyName: obj.inviteUser?.companyName || null, + }; + }) + @ApiProperty({ + type: String, + nullable: true, + }) + @Expose() + phoneNumber: string; +} + +export class BookingSpaceResponseDto { + @ApiProperty() + @Expose() + uuid: string; + + @ApiProperty() + @Expose() + spaceName: string; +} + +export class BookingResponseDto { + @ApiProperty() + @Expose() + uuid: string; + + @ApiProperty({ + type: Date, + }) + @Expose() + date: Date; + + @ApiProperty() + @Expose() + startTime: string; + + @ApiProperty() + @Expose() + endTime: string; + + @ApiProperty({ + type: Number, + }) + @Expose() + cost: number; + + @ApiProperty({ + type: BookingUserResponseDto, + }) + @Type(() => BookingUserResponseDto) + @Expose() + user: BookingUserResponseDto; + + @ApiProperty({ + type: BookingSpaceResponseDto, + }) + @Type(() => BookingSpaceResponseDto) + @Expose() + space: BookingSpaceResponseDto; +} diff --git a/src/booking/dtos/my-booking-request.dto.ts b/src/booking/dtos/my-booking-request.dto.ts new file mode 100644 index 0000000..402aa36 --- /dev/null +++ b/src/booking/dtos/my-booking-request.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsIn, IsOptional } from 'class-validator'; + +export class MyBookingRequestDto { + @ApiProperty({ + description: 'Filter bookings by time period', + example: 'past', + enum: ['past', 'future'], + required: false, + }) + @IsOptional() + @IsIn(['past', 'future']) + when?: 'past' | 'future'; +} diff --git a/src/booking/services/booking.service.ts b/src/booking/services/booking.service.ts index c1854c9..acdcdb5 100644 --- a/src/booking/services/booking.service.ts +++ b/src/booking/services/booking.service.ts @@ -11,7 +11,10 @@ import { Injectable, NotFoundException, } from '@nestjs/common'; +import { Between } from 'typeorm/find-options/operator/Between'; +import { BookingRequestDto } from '../dtos/booking-request.dto'; import { CreateBookingDto } from '../dtos/create-booking.dto'; +import { MyBookingRequestDto } from '../dtos/my-booking-request.dto'; @Injectable() export class BookingService { @@ -46,52 +49,39 @@ export class BookingService { return this.createBookings(space, userUuid, dto); } - // async findAll( - // { active, page, size, configured, search }: BookingRequestDto, - // project: string, - // ): Promise<{ - // data: BaseResponseDto['data']; - // pagination: PageResponseDto; - // }> { - // let qb = this.spaceRepository - // .createQueryBuilder('space') - // .leftJoinAndSelect('space.parent', 'parentSpace') - // .leftJoinAndSelect('space.community', 'community') - // .where('community.project = :project', { project }); + async findAll({ month }: BookingRequestDto, project: string) { + const [monthNumber, year] = month.split('/').map(Number); + const fromDate = new Date(year, monthNumber - 1, 1); + const toDate = new Date(year, monthNumber, 0, 23, 59, 59); + return this.bookingEntityRepository.find({ + where: { + space: { community: { project: { uuid: project } } }, + date: Between(fromDate, toDate), + }, + relations: ['space', 'user', 'user.inviteUser'], + order: { date: 'DESC' }, + }); + } - // if (search) { - // qb = qb.andWhere( - // 'space.spaceName ILIKE :search OR community.name ILIKE :search OR parentSpace.spaceName ILIKE :search', - // { search: `%${search}%` }, - // ); - // } - // if (configured) { - // qb = qb - // .leftJoinAndSelect('space.bookableConfig', 'bookableConfig') - // .andWhere('bookableConfig.uuid IS NOT NULL'); - // if (active !== undefined) { - // qb = qb.andWhere('bookableConfig.active = :active', { active }); - // } - // } else { - // qb = qb - // .leftJoinAndSelect('space.bookableConfig', 'bookableConfig') - // .andWhere('bookableConfig.uuid IS NULL'); - // } - - // const customModel = TypeORMCustomModel(this.spaceRepository); - - // const { baseResponseDto, paginationResponseDto } = - // await customModel.findAll({ page, size, modelName: 'space' }, qb); - // return { - // data: baseResponseDto.data.map((space) => { - // return { - // ...space, - // virtualLocation: `${space.community?.name} - ${space.parent ? space.parent?.spaceName + ' - ' : ''}${space.spaceName}`, - // }; - // }), - // pagination: paginationResponseDto, - // }; - // } + async findMyBookings( + { when }: MyBookingRequestDto, + userUuid: string, + project: string, + ) { + return this.bookingEntityRepository.find({ + where: { + user: { uuid: userUuid }, + space: { community: { project: { uuid: project } } }, + // date: when + // ? when === 'past' + // ? LessThanOrEqual(new Date()) + // : MoreThanOrEqual(new Date()) + // : undefined, + }, + relations: ['space', 'user'], + order: { date: 'DESC' }, + }); + } /** * Fetch space by UUID and throw an error if not found or if not configured for booking