import { ORPHAN_COMMUNITY_NAME } from '@app/common/constants/orphan-constant'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { PageResponse } from '@app/common/dto/pagination.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { TypeORMCustomModel, TypeORMCustomModelFindAllQuery, } from '@app/common/models/typeOrmCustom.model'; import { ProjectDto } from '@app/common/modules/project/dtos'; import { ProjectEntity } from '@app/common/modules/project/entities'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; import { UserRepository } from '@app/common/modules/user/repositories'; import { format } from '@fast-csv/format'; import { forwardRef, HttpException, HttpStatus, Inject, Injectable, } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; import { SpaceService } from 'src/space/services'; import { PassThrough } from 'stream'; import { CreateOrphanSpaceCommand } from '../command/create-orphan-space-command'; import { CreateProjectDto, GetProjectParam } from '../dto'; import { QueryRunner } from 'typeorm'; @Injectable() export class ProjectService { constructor( private readonly projectRepository: ProjectRepository, private readonly userRepository: UserRepository, private commandBus: CommandBus, @Inject(forwardRef(() => SpaceService)) private readonly spaceService: SpaceService, ) {} async createProject( createProjectDto: CreateProjectDto, ): Promise { const { name, userUuid } = createProjectDto; try { // Check if the project name already exists const isNameExist = await this.validate(name); if (isNameExist) { throw new HttpException( `Project with name ${name} already exists`, HttpStatus.CONFLICT, ); } // Check if user exists const user = await this.userRepository.findOne({ where: { uuid: userUuid }, }); if (!user) { throw new HttpException( `User with UUID ${userUuid} not found`, HttpStatus.NOT_FOUND, ); } const newProject = this.projectRepository.create(createProjectDto); const savedProject = await this.projectRepository.save(newProject); user.project = savedProject; await this.userRepository.save(user); await this.commandBus.execute(new CreateOrphanSpaceCommand(savedProject)); return new SuccessResponseDto({ message: `Project with ID ${savedProject.uuid} successfully created`, data: { ...savedProject, rootUser: { uuid: user.uuid, email: user.email }, }, statusCode: HttpStatus.CREATED, }); } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException( `An error occurred while creating the project. Please try again later. ${error}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } } async deleteProject(uuid: string): Promise { try { // Find the project by UUID const project = await this.findOne(uuid); if (!project) { throw new HttpException( `Project with ID ${uuid} not found`, HttpStatus.NOT_FOUND, ); } // Delete the project await this.projectRepository.delete({ uuid }); return new SuccessResponseDto({ message: `Project with ID ${uuid} successfully deleted`, statusCode: HttpStatus.OK, }); } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException( `An error occurred while deleting the project ${uuid}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } } async listProjects( pageable: Partial, ): Promise { try { pageable.modelName = 'project'; const customModel = TypeORMCustomModel(this.projectRepository); const { baseResponseDto, paginationResponseDto } = await customModel.findAll(pageable); return new PageResponse( baseResponseDto, paginationResponseDto, ); } catch (error) { throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); } } async getProject(uuid: string): Promise { try { // Find the project by UUID const project = await this.findOne(uuid); if (!project) { throw new HttpException( `Project with ID ${uuid} not found`, HttpStatus.NOT_FOUND, ); } return new SuccessResponseDto({ message: `Project with ID ${uuid} retrieved successfully`, data: project, statusCode: HttpStatus.OK, }); } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException( `An error occurred while retrieving the project with id ${uuid}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } } async updateProject( uuid: string, updateProjectDto: CreateProjectDto, ): Promise { try { // Find the project by UUID const project = await this.findOne(uuid); if (!project) { throw new HttpException( `Project with ID ${uuid} not found`, HttpStatus.NOT_FOUND, ); } if (updateProjectDto.name && updateProjectDto.name !== project.name) { const isNameExist = await this.validate(updateProjectDto.name); if (isNameExist) { throw new HttpException( `Project with name ${updateProjectDto.name} already exists`, HttpStatus.CONFLICT, ); } } // Update the project details Object.assign(project, updateProjectDto); const updatedProject = await this.projectRepository.save(project); return new SuccessResponseDto({ message: `Project with ID ${uuid} successfully updated`, data: updatedProject, statusCode: HttpStatus.OK, }); } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException( `An error occurred while updating the project with ID ${uuid}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } } async findOne( uuid: string, queryRunner?: QueryRunner, ): Promise { const projectRepository = queryRunner ? queryRunner.manager.getRepository(ProjectEntity) : this.projectRepository; const project = await projectRepository.findOne({ where: { uuid } }); if (!project) { throw new HttpException( `Invalid project with uuid ${uuid}`, HttpStatus.NOT_FOUND, ); } return project; } async validate(name: string): Promise { return await this.projectRepository.exists({ where: { name } }); } async exportToCsv(param: GetProjectParam): Promise { const project = await this.projectRepository.findOne({ where: { uuid: param.projectUuid }, relations: [ 'communities', 'communities.spaces', 'communities.spaces.parent', 'communities.spaces.productAllocations', 'communities.spaces.productAllocations.product', 'communities.spaces.productAllocations.tag', 'communities.spaces.subspaces', 'communities.spaces.subspaces.productAllocations', 'communities.spaces.subspaces.productAllocations.product', 'communities.spaces.subspaces.productAllocations.tag', ], }); if (!project) { throw new HttpException( `Project with UUID ${param.projectUuid} not found`, HttpStatus.NOT_FOUND, ); } const stream = new PassThrough(); const csvStream = format({ headers: [ 'Device ID', 'Community Name', 'Space Name', 'Space Location', 'Subspace Name', 'Tag', 'Product Name', 'Community UUID', 'Space UUID', 'Subspace UUID', ], }); csvStream.pipe(stream); const allSpaces: SpaceEntity[] = []; for (const community of project.communities) { if (community.name === `${ORPHAN_COMMUNITY_NAME}-${project.name}`) continue; for (const space of community.spaces) { if (!space.disabled) { space.community = community; allSpaces.push(space); } } } const spaceMap = new Map(); for (const space of allSpaces) { spaceMap.set(space.uuid, space); } function buildSpaceLocation(space: SpaceEntity): string { const path: string[] = []; let current = space.parent; while (current) { path.unshift(current.spaceName); current = current.parent ?? spaceMap.get(current.uuid)?.parent ?? null; } return path.join(' > '); } for (const space of allSpaces) { const spaceLocation = buildSpaceLocation(space); for (const subspace of space.subspaces || []) { if (subspace.disabled) continue; for (const productAllocation of subspace.productAllocations || []) { csvStream.write({ 'Device ID': '', 'Community Name': space.community?.name || '', 'Space Name': space.spaceName, 'Space Location': spaceLocation, 'Subspace Name': subspace.subspaceName || '', Tag: productAllocation.tag.name, 'Product Name': productAllocation.product.name || '', 'Community UUID': space.community?.uuid || '', 'Space UUID': space.uuid, 'Subspace UUID': subspace.uuid, }); } } for (const productAllocation of space.productAllocations || []) { csvStream.write({ 'Device ID': '', 'Community Name': space.community?.name || '', 'Space Name': space.spaceName, 'Space Location': spaceLocation, 'Subspace Name': '', Tag: productAllocation.tag.name, 'Product Name': productAllocation.product.name || '', 'Community UUID': space.community?.uuid || '', 'Space UUID': space.uuid, 'Subspace UUID': '', }); } } csvStream.end(); return stream; } }