From 74428b408e5d4ab05c482d9fa35689d1b5df232d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 28 Oct 2024 09:53:47 +0400 Subject: [PATCH] add subspace controller --- libs/common/src/constants/controller-route.ts | 26 +++ .../services/space.permission.service.ts | 3 - libs/common/src/models/typeOrmCustom.model.ts | 4 +- .../space/repositories/space.repository.ts | 9 +- .../src/util/buildTypeORMIncludeQuery.ts | 3 + .../src/util/buildTypeORMWhereClause.ts | 33 ++- src/space/controllers/index.ts | 1 + src/space/controllers/subspace.controller.ts | 91 ++++++++ src/space/dtos/index.ts | 1 + src/space/dtos/subspace/add.subspace.dto.ts | 12 ++ src/space/dtos/subspace/get.subspace.param.ts | 12 ++ src/space/dtos/subspace/index.ts | 2 + src/space/services/index.ts | 1 + src/space/services/subspace.service.ts | 203 ++++++++++++++++++ src/space/space.module.ts | 18 +- 15 files changed, 401 insertions(+), 18 deletions(-) create mode 100644 src/space/controllers/subspace.controller.ts create mode 100644 src/space/dtos/subspace/add.subspace.dto.ts create mode 100644 src/space/dtos/subspace/get.subspace.param.ts create mode 100644 src/space/dtos/subspace/index.ts create mode 100644 src/space/services/subspace.service.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 78aa262..3095819 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -129,4 +129,30 @@ export class ControllerRoute { 'Disassociates a user from a space by removing the existing association.'; }; }; + + static SUBSPACE = class { + public static readonly ROUTE = + '/communities/:communityUuid/spaces/:spaceUuid/subspaces'; + static ACTIONS = class { + public static readonly CREATE_SUBSPACE_SUMMARY = 'Create Subspace'; + public static readonly CREATE_SUBSPACE_DESCRIPTION = + 'Creates a new subspace within a specific space and community.'; + + public static readonly LIST_SUBSPACES_SUMMARY = 'List Subspaces'; + public static readonly LIST_SUBSPACES_DESCRIPTION = + 'Retrieves a list of subspaces within a specified space and community.'; + + public static readonly GET_SUBSPACE_SUMMARY = 'Get Subspace'; + public static readonly GET_SUBSPACE_DESCRIPTION = + 'Fetches a specific subspace by UUID within a given space and community'; + + public static readonly UPDATE_SUBSPACE_SUMMARY = 'Update Subspace'; + public static readonly UPDATE_SUBSPACE_DESCRIPTION = + 'Updates a specific subspace within a given space and community.'; + + public static readonly DELETE_SUBSPACE_SUMMARY = 'Delete Subspace'; + public static readonly DELETE_SUBSPACE_DESCRIPTION = + 'Deletes a specific subspace within a given space and community.'; + }; + }; } diff --git a/libs/common/src/helper/services/space.permission.service.ts b/libs/common/src/helper/services/space.permission.service.ts index 0f11cbc..6733aa2 100644 --- a/libs/common/src/helper/services/space.permission.service.ts +++ b/libs/common/src/helper/services/space.permission.service.ts @@ -15,9 +15,6 @@ export class SpacePermissionService { const spaceData = await this.spaceRepository.findOne({ where: { uuid: spaceUuid, - spaceType: { - type: type, - }, userSpaces: { user: { uuid: userUuid, diff --git a/libs/common/src/models/typeOrmCustom.model.ts b/libs/common/src/models/typeOrmCustom.model.ts index 72a818d..0f3abbc 100644 --- a/libs/common/src/models/typeOrmCustom.model.ts +++ b/libs/common/src/models/typeOrmCustom.model.ts @@ -97,7 +97,7 @@ export function TypeORMCustomModel(repository: Repository) { // Use the where clause directly, without wrapping it under 'where' const whereClause = buildTypeORMWhereClause({ where }); - console.log('Where clause after building:', whereClause); + console.log('Final where clause:', whereClause); // Ensure the whereClause is passed directly to findAndCount const [data, count] = await repository.findAndCount({ @@ -112,7 +112,7 @@ export function TypeORMCustomModel(repository: Repository) { const paginationResponseDto = getPaginationResponseDto(count, page, size); const baseResponseDto: BaseResponseDto = { data, - message: getResponseMessage(modelName, { where: whereClause }), + message: getResponseMessage(modelName, { where }), }; return { baseResponseDto, paginationResponseDto }; diff --git a/libs/common/src/modules/space/repositories/space.repository.ts b/libs/common/src/modules/space/repositories/space.repository.ts index ea11a6e..5e0962c 100644 --- a/libs/common/src/modules/space/repositories/space.repository.ts +++ b/libs/common/src/modules/space/repositories/space.repository.ts @@ -1,6 +1,6 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; -import { SpaceEntity, SpaceTypeEntity } from '../entities'; +import { SpaceEntity, SpaceTypeEntity, SubspaceEntity } from '../entities'; @Injectable() export class SpaceRepository extends Repository { @@ -15,3 +15,10 @@ export class SpaceTypeRepository extends Repository { super(SpaceTypeEntity, dataSource.createEntityManager()); } } + +@Injectable() +export class SubspaceRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SubspaceEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/util/buildTypeORMIncludeQuery.ts b/libs/common/src/util/buildTypeORMIncludeQuery.ts index 50583cc..dda60df 100644 --- a/libs/common/src/util/buildTypeORMIncludeQuery.ts +++ b/libs/common/src/util/buildTypeORMIncludeQuery.ts @@ -13,6 +13,9 @@ const mappingInclude: { [key: string]: any } = { space: { space: true, }, + subspace: { + subspace: true, + }, }; export function buildTypeORMIncludeQuery( diff --git a/libs/common/src/util/buildTypeORMWhereClause.ts b/libs/common/src/util/buildTypeORMWhereClause.ts index 6eda8aa..99119eb 100644 --- a/libs/common/src/util/buildTypeORMWhereClause.ts +++ b/libs/common/src/util/buildTypeORMWhereClause.ts @@ -1,10 +1,29 @@ import { FindOptionsWhere } from 'typeorm'; -export function buildTypeORMWhereClause({ - where, -}: { - where?: FindOptionsWhere | FindOptionsWhere[]; // Accepts both object and array formats -}): FindOptionsWhere | FindOptionsWhere[] { - // Return the 'where' clause directly, without wrapping - return where || {}; // If 'where' is undefined, return an empty object +export function buildTypeORMWhereClause({ where }) { + if (!where) return {}; + + // Remove extra nesting if `where` is wrapped within an additional `where` property + const condition = where.where ? where.where : where; + + console.log(condition); + const convertToNestedObject = (condition: any): any => { + const result = {}; + for (const [key, value] of Object.entries(condition)) { + if (key.includes('.')) { + const [parentKey, childKey] = key.split('.'); + result[parentKey] = { + ...(result[parentKey] || {}), + [childKey]: value, + }; + } else { + result[key] = value; + } + } + return result; + }; + + return Array.isArray(condition) + ? condition.map((item) => convertToNestedObject(item)) + : convertToNestedObject(condition); } diff --git a/src/space/controllers/index.ts b/src/space/controllers/index.ts index fed3986..0a5bc08 100644 --- a/src/space/controllers/index.ts +++ b/src/space/controllers/index.ts @@ -1,2 +1,3 @@ export * from './space.controller'; export * from './space-user.controller'; +export * from './subspace.controller'; diff --git a/src/space/controllers/subspace.controller.ts b/src/space/controllers/subspace.controller.ts new file mode 100644 index 0000000..025140f --- /dev/null +++ b/src/space/controllers/subspace.controller.ts @@ -0,0 +1,91 @@ +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { + Body, + Controller, + Delete, + Get, + Param, + Post, + Put, + Query, + UseGuards, +} from '@nestjs/common'; +import { SubSpaceService } from '../services'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../dtos'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; + +@ApiTags('Space Module') +@Controller({ + version: '1', + path: ControllerRoute.SUBSPACE.ROUTE, +}) +export class SubSpaceController { + constructor(private readonly subSpaceService: SubSpaceService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post() + @ApiOperation({ + summary: ControllerRoute.SUBSPACE.ACTIONS.CREATE_SUBSPACE_SUMMARY, + description: ControllerRoute.SUBSPACE.ACTIONS.CREATE_SUBSPACE_DESCRIPTION, + }) + async createSubspace( + @Param() params: GetSpaceParam, + @Body() addSubspaceDto: AddSubspaceDto, + ): Promise { + return this.subSpaceService.createSubspace(addSubspaceDto, params); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SUBSPACE.ACTIONS.LIST_SUBSPACES_SUMMARY, + description: ControllerRoute.SUBSPACE.ACTIONS.LIST_SUBSPACES_DESCRIPTION, + }) + @Get() + async list( + @Param() params: GetSpaceParam, + @Query() query: PaginationRequestGetListDto, + ): Promise { + return this.subSpaceService.list(params, query); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SUBSPACE.ACTIONS.GET_SUBSPACE_SUMMARY, + description: ControllerRoute.SUBSPACE.ACTIONS.GET_SUBSPACE_DESCRIPTION, + }) + @Get(':subSpaceUuid') + async findOne(@Param() params: GetSubSpaceParam): Promise { + return this.subSpaceService.findOne(params); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SUBSPACE.ACTIONS.UPDATE_SUBSPACE_SUMMARY, + description: ControllerRoute.SUBSPACE.ACTIONS.UPDATE_SUBSPACE_DESCRIPTION, + }) + @Put(':subSpaceUuid') + async updateSubspace( + @Param() params: GetSubSpaceParam, + @Body() updateSubSpaceDto: AddSubspaceDto, + ): Promise { + return this.subSpaceService.updateSubSpace(params, updateSubSpaceDto); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SUBSPACE.ACTIONS.DELETE_SUBSPACE_SUMMARY, + description: ControllerRoute.SUBSPACE.ACTIONS.DELETE_SUBSPACE_DESCRIPTION, + }) + @Delete(':subSpaceUuid') + async delete(@Param() params: GetSubSpaceParam): Promise { + return this.subSpaceService.delete(params); + } +} diff --git a/src/space/dtos/index.ts b/src/space/dtos/index.ts index b622787..2cebe7b 100644 --- a/src/space/dtos/index.ts +++ b/src/space/dtos/index.ts @@ -2,3 +2,4 @@ export * from './add.space.dto'; export * from './community-space.param'; export * from './get.space.param'; export * from './user-space.param'; +export * from './subspace'; diff --git a/src/space/dtos/subspace/add.subspace.dto.ts b/src/space/dtos/subspace/add.subspace.dto.ts new file mode 100644 index 0000000..a2b12e2 --- /dev/null +++ b/src/space/dtos/subspace/add.subspace.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class AddSubspaceDto { + @ApiProperty({ + example: 'Meeting Room 1', + description: 'Name of the subspace', + }) + @IsNotEmpty() + @IsString() + subspaceName: string; +} diff --git a/src/space/dtos/subspace/get.subspace.param.ts b/src/space/dtos/subspace/get.subspace.param.ts new file mode 100644 index 0000000..e1c63be --- /dev/null +++ b/src/space/dtos/subspace/get.subspace.param.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; +import { GetSpaceParam } from '../get.space.param'; + +export class GetSubSpaceParam extends GetSpaceParam { + @ApiProperty({ + description: 'UUID of the sub space', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + subSpaceUuid: string; +} diff --git a/src/space/dtos/subspace/index.ts b/src/space/dtos/subspace/index.ts new file mode 100644 index 0000000..5d284f4 --- /dev/null +++ b/src/space/dtos/subspace/index.ts @@ -0,0 +1,2 @@ +export * from './add.subspace.dto'; +export * from './get.subspace.param'; diff --git a/src/space/services/index.ts b/src/space/services/index.ts index 6baa092..8f12a9b 100644 --- a/src/space/services/index.ts +++ b/src/space/services/index.ts @@ -1,2 +1,3 @@ export * from './space.service'; export * from './space-user.service'; +export * from './subspace.service'; diff --git a/src/space/services/subspace.service.ts b/src/space/services/subspace.service.ts new file mode 100644 index 0000000..a8b951f --- /dev/null +++ b/src/space/services/subspace.service.ts @@ -0,0 +1,203 @@ +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { + SpaceRepository, + SubspaceRepository, +} from '@app/common/modules/space/repositories'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../dtos'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { + TypeORMCustomModel, + TypeORMCustomModelFindAllQuery, +} from '@app/common/models/typeOrmCustom.model'; +import { PageResponse } from '@app/common/dto/pagination.response.dto'; +import { SubspaceDto } from '@app/common/modules/space/dtos'; + +@Injectable() +export class SubSpaceService { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly communityRepository: CommunityRepository, + private readonly subspaceRepository: SubspaceRepository, + ) {} + + async createSubspace( + addSubspaceDto: AddSubspaceDto, + params: GetSpaceParam, + ): Promise { + const { communityUuid, spaceUuid } = params; + const space = await this.validateCommunityAndSpace( + communityUuid, + spaceUuid, + ); + + try { + const newSubspace = this.subspaceRepository.create({ + ...addSubspaceDto, + space, + }); + + await this.subspaceRepository.save(newSubspace); + + return new SuccessResponseDto({ + statusCode: HttpStatus.CREATED, + data: newSubspace, + message: 'Subspace created successfully', + }); + } catch (error) { + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + async list( + params: GetSpaceParam, + pageable: Partial, + ): Promise { + const { communityUuid, spaceUuid } = params; + await this.validateCommunityAndSpace(communityUuid, spaceUuid); + + try { + pageable.modelName = 'subspace'; + pageable.where = { space: { uuid: spaceUuid } }; + const customModel = TypeORMCustomModel(this.subspaceRepository); + + const { baseResponseDto, paginationResponseDto } = + await customModel.findAll(pageable); + return new PageResponse( + baseResponseDto, + paginationResponseDto, + ); + } catch (error) { + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + async findOne(params: GetSubSpaceParam): Promise { + const { communityUuid, subSpaceUuid, spaceUuid } = params; + await this.validateCommunityAndSpace(communityUuid, spaceUuid); + try { + const subSpace = await this.subspaceRepository.findOne({ + where: { + uuid: subSpaceUuid, + }, + }); + + // If space is not found, throw a NotFoundException + if (!subSpace) { + throw new HttpException( + `Sub Space with UUID ${subSpaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + return new SuccessResponseDto({ + message: `Subspace with ID ${subSpaceUuid} successfully fetched`, + data: subSpace, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; // If it's an HttpException, rethrow it + } else { + throw new HttpException( + 'An error occurred while deleting the subspace', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + + async updateSubSpace( + params: GetSubSpaceParam, + updateSubSpaceDto: AddSubspaceDto, + ): Promise { + const { spaceUuid, communityUuid, subSpaceUuid } = params; + await this.validateCommunityAndSpace(communityUuid, spaceUuid); + + const subSpace = await this.subspaceRepository.findOne({ + where: { uuid: subSpaceUuid }, + }); + + if (!subSpace) { + throw new HttpException( + `SubSpace with ID ${subSpaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + try { + Object.assign(subSpace, updateSubSpaceDto); + + await this.subspaceRepository.save(subSpace); + + return new SuccessResponseDto({ + message: `Subspace with ID ${subSpaceUuid} successfully updated`, + data: subSpace, + statusCode: HttpStatus.OK, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( + 'An error occurred while updating the subspace', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async delete(params: GetSubSpaceParam): Promise { + const { spaceUuid, communityUuid, subSpaceUuid } = params; + await this.validateCommunityAndSpace(communityUuid, spaceUuid); + + const subspace = await this.subspaceRepository.findOne({ + where: { uuid: subSpaceUuid }, + }); + + if (!subspace) { + throw new HttpException( + `Subspace with ID ${subSpaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + try { + await this.subspaceRepository.remove(subspace); + + return new SuccessResponseDto({ + message: `Subspace with ID ${subSpaceUuid} successfully deleted`, + statusCode: HttpStatus.OK, + }); + } catch (error) { + throw new HttpException( + 'An error occurred while deleting the subspace', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private async validateCommunityAndSpace( + communityUuid: string, + spaceUuid: string, + ) { + const community = await this.communityRepository.findOne({ + where: { uuid: communityUuid }, + }); + if (!community) { + throw new HttpException( + `Community with ID ${communityUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + const space = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid, community: { uuid: communityUuid } }, + }); + if (!space) { + throw new HttpException( + `Space with ID ${spaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + return space; + } +} diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 45c1f02..0de73a9 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -1,11 +1,17 @@ import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { SpaceController } from './controllers'; -import { SpaceService, SpaceUserService } from './services'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { + SpaceController, + SpaceUserController, + SubSpaceController, +} from './controllers'; +import { SpaceService, SpaceUserService, SubSpaceService } from './services'; +import { + SpaceRepository, + SubspaceRepository, +} from '@app/common/modules/space/repositories'; import { CommunityRepository } from '@app/common/modules/community/repositories'; -import { SpaceUserController } from './controllers/space-user.controller'; import { UserRepository, UserSpaceRepository, @@ -13,11 +19,13 @@ import { @Module({ imports: [ConfigModule, SpaceRepositoryModule], - controllers: [SpaceController, SpaceUserController], + controllers: [SpaceController, SpaceUserController, SubSpaceController], providers: [ SpaceService, + SubSpaceService, SpaceRepository, CommunityRepository, + SubspaceRepository, UserSpaceRepository, UserRepository, SpaceUserService,