import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException, HttpException, HttpStatus, Injectable, } from '@nestjs/common'; import { AddSpaceDto } from '../dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { SpaceEntity } from '@app/common/modules/space/entities'; import { generateRandomString } from '@app/common/helper/randomString'; import { SpaceLinkService } from './space-link'; import { SpaceProductService } from './space-products'; @Injectable() export class SpaceService { constructor( private readonly spaceRepository: SpaceRepository, private readonly communityRepository: CommunityRepository, private readonly spaceLinkService: SpaceLinkService, private readonly spaceProductService: SpaceProductService, ) {} async createSpace( addSpaceDto: AddSpaceDto, communityId: string, ): Promise { const { parentUuid, direction, products } = addSpaceDto; const community = await this.validateCommunity(communityId); const parent = parentUuid ? await this.validateSpace(parentUuid) : null; try { const newSpace = this.spaceRepository.create({ ...addSpaceDto, community, parent: parentUuid ? parent : null, }); await this.spaceRepository.save(newSpace); if (direction && parent) { await this.spaceLinkService.saveSpaceLink( parent.uuid, newSpace.uuid, direction, ); } if (products && products.length > 0) { await this.spaceProductService.assignProductsToSpace( newSpace, products, ); } return new SuccessResponseDto({ statusCode: HttpStatus.CREATED, data: newSpace, message: 'Space created successfully', }); } catch (error) { throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); } } async getSpacesHierarchyForCommunity( communityUuid: string, ): Promise { await this.validateCommunity(communityUuid); try { // Get all spaces related to the community, including the parent-child relations const spaces = await this.spaceRepository.find({ where: { community: { uuid: communityUuid } }, relations: [ 'parent', 'children', 'incomingConnections', 'spaceProducts', 'spaceProducts.product', ], // Include parent and children relations }); // Organize spaces into a hierarchical structure const spaceHierarchy = this.buildSpaceHierarchy(spaces); return new SuccessResponseDto({ message: `Spaces in community ${communityUuid} successfully fetched in hierarchy`, data: spaceHierarchy, statusCode: HttpStatus.OK, }); } catch (error) { throw new HttpException( 'An error occurred while fetching the spaces', HttpStatus.INTERNAL_SERVER_ERROR, ); } } async findOne(spaceUuid: string): Promise { try { const space = await this.validateSpace(spaceUuid); return new SuccessResponseDto({ message: `Space with ID ${spaceUuid} successfully fetched`, data: space, }); } 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 community', HttpStatus.INTERNAL_SERVER_ERROR, ); } } } async delete( spaceUuid: string, communityUuid: string, ): Promise { try { // First, check if the community exists const space = await this.validateCommunityAndSpace( communityUuid, spaceUuid, ); // Delete the space await this.spaceRepository.remove(space); return new SuccessResponseDto({ message: `Space with ID ${spaceUuid} successfully deleted`, statusCode: HttpStatus.OK, }); } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException( 'An error occurred while deleting the space', HttpStatus.INTERNAL_SERVER_ERROR, ); } } async updateSpace( spaceUuid: string, communityId: string, updateSpaceDto: AddSpaceDto, ): Promise { try { const space = await this.validateCommunityAndSpace( communityId, spaceUuid, ); // If a parentId is provided, check if the parent exists const { parentUuid, products } = updateSpaceDto; const parent = parentUuid ? await this.validateSpace(parentUuid) : null; // Update other space properties from updateSpaceDto Object.assign(space, updateSpaceDto, { parent }); // Save the updated space const updatedSpace = await this.spaceRepository.save(space); if (products && products.length > 0) { await this.spaceProductService.assignProductsToSpace( updatedSpace, products, ); } return new SuccessResponseDto({ message: `Space with ID ${spaceUuid} successfully updated`, data: space, statusCode: HttpStatus.OK, }); } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException( 'An error occurred while updating the space', HttpStatus.INTERNAL_SERVER_ERROR, ); } } async getSpacesHierarchyForSpace( spaceUuid: string, ): Promise { await this.validateSpace(spaceUuid); try { // Get all spaces that are children of the provided space, including the parent-child relations const spaces = await this.spaceRepository.find({ where: { parent: { uuid: spaceUuid } }, relations: ['parent', 'children'], // Include parent and children relations }); // Organize spaces into a hierarchical structure const spaceHierarchy = this.buildSpaceHierarchy(spaces); return new SuccessResponseDto({ message: `Spaces under space ${spaceUuid} successfully fetched in hierarchy`, data: spaceHierarchy, statusCode: HttpStatus.OK, }); } catch (error) { throw new HttpException( 'An error occurred while fetching the spaces under the space', HttpStatus.INTERNAL_SERVER_ERROR, ); } } async getSpaceInvitationCode(spaceUuid: string): Promise { try { const invitationCode = generateRandomString(6); const space = await this.validateSpace(spaceUuid); space.invitationCode = invitationCode; await this.spaceRepository.save(space); return new SuccessResponseDto({ message: `Invitation code has been successfuly added to the space`, data: space, }); } catch (err) { if (err instanceof BadRequestException) { throw err; } else { throw new HttpException('Space not found', HttpStatus.NOT_FOUND); } } } private buildSpaceHierarchy(spaces: SpaceEntity[]): SpaceEntity[] { const map = new Map(); // Step 1: Create a map of spaces by UUID spaces.forEach((space) => { map.set( space.uuid, this.spaceRepository.create({ ...space, children: [], // Add children if needed }), ); }); // Step 2: Organize the hierarchy const rootSpaces: SpaceEntity[] = []; spaces.forEach((space) => { if (space.parent && space.parent.uuid) { const parent = map.get(space.parent.uuid); parent?.children?.push(map.get(space.uuid)); } else { rootSpaces.push(map.get(space.uuid)); } }); return rootSpaces; } private async validateCommunity(communityId: string) { const community = await this.communityRepository.findOne({ where: { uuid: communityId }, }); if (!community) { throw new HttpException( `Community with ID ${communityId} not found`, HttpStatus.NOT_FOUND, ); } return community; } async validateCommunityAndSpace(communityUuid: string, spaceUuid: string) { const community = await this.validateCommunity(communityUuid); if (!community) { this.throwNotFound('Community', communityUuid); } const space = await this.validateSpace(spaceUuid); return space; } private async validateSpace(spaceUuid: string) { const space = await this.spaceRepository.findOne({ where: { uuid: spaceUuid }, }); if (!space) this.throwNotFound('Space', spaceUuid); return space; } private throwNotFound(entity: string, uuid: string) { throw new HttpException( `${entity} with ID ${uuid} not found`, HttpStatus.NOT_FOUND, ); } }