From 4810cc6f35c42ae5ffe3d80a9227743e101c0a4e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 16 Apr 2025 10:28:57 +0400 Subject: [PATCH] added controller to export --- src/project/controllers/project.controller.ts | 9 +- src/project/project.module.ts | 84 ++++++++++- src/project/services/project.service.ts | 132 +++++++++++++----- .../services/subspace/subspace.service.ts | 17 --- 4 files changed, 184 insertions(+), 58 deletions(-) diff --git a/src/project/controllers/project.controller.ts b/src/project/controllers/project.controller.ts index b43647f..2394e47 100644 --- a/src/project/controllers/project.controller.ts +++ b/src/project/controllers/project.controller.ts @@ -9,6 +9,7 @@ import { Post, Put, Query, + Res, UseGuards, } from '@nestjs/common'; import { @@ -110,7 +111,11 @@ export class ProjectController { }) @Header('Content-Type', 'text/csv') @Header('Content-Disposition', 'attachment; filename=project-structure.csv') - async exportStructures(@Param() params: GetProjectParam) { - return this.projectService.exportToCsv(params); + async exportStructures( + @Param() params: GetProjectParam, + @Res() res: Response, + ): Promise { + const csvStream = await this.projectService.exportToCsv(params); + csvStream.pipe(res as unknown as NodeJS.WritableStream); } } diff --git a/src/project/project.module.ts b/src/project/project.module.ts index f16302f..6c8bef8 100644 --- a/src/project/project.module.ts +++ b/src/project/project.module.ts @@ -4,7 +4,13 @@ import { ProjectController } from './controllers'; import { ProjectService } from './services'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { CreateOrphanSpaceHandler } from './handler'; -import { SpaceRepository } from '@app/common/modules/space'; +import { + InviteSpaceRepository, + SpaceLinkRepository, + SpaceProductAllocationRepository, + SpaceRepository, + TagRepository, +} from '@app/common/modules/space'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; import { ProjectUserController } from './controllers/project-user.controller'; @@ -13,6 +19,47 @@ import { UserRepository, UserSpaceRepository, } from '@app/common/modules/user/repositories'; +import { + SpaceLinkService, + SpaceService, + SubspaceDeviceService, + SubSpaceService, + ValidationService, +} from 'src/space/services'; +import { TagService } from 'src/tags/services'; +import { + SpaceModelService, + SubSpaceModelService, +} from 'src/space-model/services'; +import { DeviceService } from 'src/device/services'; +import { SpaceProductAllocationService } from 'src/space/services/space-product-allocation.service'; +import { + SubspaceProductAllocationRepository, + SubspaceRepository, +} from '@app/common/modules/space/repositories/subspace.repository'; +import { SubspaceProductAllocationService } from 'src/space/services/subspace/subspace-product-allocation.service'; +import { CommunityService } from 'src/community/services'; +import { + SpaceModelProductAllocationRepoitory, + SpaceModelRepository, + SubspaceModelProductAllocationRepoitory, + SubspaceModelRepository, +} from '@app/common/modules/space-model'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { NewTagRepository } from '@app/common/modules/tag/repositories/tag-repository'; +import { ProductRepository } from '@app/common/modules/product/repositories'; +import { SpaceModelProductAllocationService } from 'src/space-model/services/space-model-product-allocation.service'; +import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; +import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service'; +import { SceneService } from 'src/scene/services'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; +import { SubspaceModelProductAllocationService } from 'src/space-model/services/subspace/subspace-model-product-allocation.service'; +import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories'; +import { + SceneIconRepository, + SceneRepository, +} from '@app/common/modules/scene/repositories'; +import { AutomationRepository } from '@app/common/modules/automation/repositories'; const CommandHandlers = [CreateOrphanSpaceHandler]; @@ -30,6 +77,41 @@ const CommandHandlers = [CreateOrphanSpaceHandler]; InviteUserRepository, UserSpaceRepository, UserRepository, + SpaceService, + InviteSpaceRepository, + SpaceLinkService, + SubSpaceService, + ValidationService, + TagService, + SpaceModelService, + DeviceService, + SpaceProductAllocationService, + SpaceLinkRepository, + SubspaceRepository, + SubspaceDeviceService, + SubspaceProductAllocationService, + CommunityService, + SpaceModelRepository, + DeviceRepository, + NewTagRepository, + ProductRepository, + SubSpaceModelService, + SpaceModelProductAllocationService, + SpaceProductAllocationRepository, + SubspaceProductAllocationRepository, + SceneDeviceRepository, + DeviceStatusFirebaseService, + SceneService, + TuyaService, + TagRepository, + SubspaceModelRepository, + SubspaceModelProductAllocationService, + SpaceModelProductAllocationRepoitory, + DeviceStatusLogRepository, + SceneIconRepository, + SceneRepository, + AutomationRepository, + SubspaceModelProductAllocationRepoitory, ], exports: [ProjectService, CqrsModule], }) diff --git a/src/project/services/project.service.ts b/src/project/services/project.service.ts index 4617726..c4a7fc3 100644 --- a/src/project/services/project.service.ts +++ b/src/project/services/project.service.ts @@ -1,5 +1,11 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories'; -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { + forwardRef, + HttpException, + HttpStatus, + Inject, + Injectable, +} from '@nestjs/common'; import { CreateProjectDto, GetProjectParam } from '../dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; @@ -15,6 +21,8 @@ import { CreateOrphanSpaceCommand } from '../command/create-orphan-space-command import { UserRepository } from '@app/common/modules/user/repositories'; import { format } from '@fast-csv/format'; import { PassThrough } from 'stream'; +import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; +import { SpaceService } from 'src/space/services'; @Injectable() export class ProjectService { @@ -22,6 +30,8 @@ export class ProjectService { private readonly projectRepository: ProjectRepository, private readonly userRepository: UserRepository, private commandBus: CommandBus, + @Inject(forwardRef(() => SpaceService)) + private readonly spaceService: SpaceService, ) {} async createProject( @@ -217,60 +227,106 @@ export class ProjectService { } async exportToCsv(param: GetProjectParam): Promise { - try { - const project = await this.projectRepository.findOne({ - where: { uuid: param.projectUuid }, - relations: [ - 'communities', - 'communities.spaces', - 'communities.spaces.parent', - 'communities.spaces.tags', - ], - }); + const project = await this.projectRepository.findOne({ + where: { uuid: param.projectUuid }, + relations: ['communities', 'communities.spaces'], + }); - if (!project) { - throw new HttpException( - `Project with UUID ${param.projectUuid} not found`, - HttpStatus.NOT_FOUND, + if (!project) { + throw new HttpException( + `Project with UUID ${param.projectUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + const allCommunitySpaces: SpaceEntity[] = []; + + // Step 1: Load the entire space hierarchy for each community + for (const community of project.communities || []) { + const { data: spaceHierarchy } = + await this.spaceService.getSpacesHierarchyForCommunity( + { communityUuid: community.uuid, projectUuid: project.uuid }, + { search: '', onlyWithDevices: false }, ); - } - const stream = new PassThrough(); - const csvStream = format({ headers: true }); + allCommunitySpaces.push(...(spaceHierarchy as SpaceEntity[])); + } - csvStream.pipe(stream); + // Step 2: Setup CSV + const stream = new PassThrough(); + const csvStream = format({ + headers: [ + 'Project Name', + 'Project UUID', + 'Space Location', + 'Space Name', + 'Space UUID', + 'Subspace Name', + 'Subspace UUID', + 'Tag', + 'Tag Product Name', + ], + }); + csvStream.pipe(stream); - for (const community of project.communities || []) { - for (const space of community.spaces || []) { - const tagNames = space.tags?.map((tag) => tag.tag).join(', ') || ''; + // Step 3: Recursive flattening + const writeSpaceData = (space: any, path: string[] = []) => { + const location = path.join(' > '); + // 1. Subspaces and their tags + for (const subspace of space.subspaces || []) { + for (const tag of subspace.tags || []) { csvStream.write({ 'Project Name': project.name, 'Project UUID': project.uuid, - 'Community Name': community.name, - 'Community UUID': community.uuid, + 'Space Location': location, 'Space Name': space.spaceName, 'Space UUID': space.uuid, - 'Parent Space UUID': space.parent?.uuid || '', - 'Space Tuya UUID': space.spaceTuyaUuid || '', - X: space.x, - Y: space.y, - Tags: tagNames, + 'Subspace Name': subspace.subspaceName || '', + 'Subspace UUID': subspace.uuid, + Tag: tag.tag, + 'Tag Product Name': tag.product?.productName || '', }); } } - csvStream.end(); - return stream; - } catch (error) { - if (error instanceof HttpException) { - throw error; + // 2. Tags directly on the space + for (const tag of space.tags || []) { + csvStream.write({ + 'Project Name': project.name, + 'Project UUID': project.uuid, + 'Space Location': location, + 'Space Name': space.spaceName, + 'Space UUID': space.uuid, + 'Subspace Name': '', + 'Subspace UUID': '', + Tag: tag.tag, + 'Tag Product Name': tag.product?.productName || '', + }); } - throw new HttpException( - `Failed to export project structure for UUID ${param.projectUuid}: ${error.message}`, - HttpStatus.INTERNAL_SERVER_ERROR, - ); + // 3. Recursively process children + for (const child of space.children || []) { + writeSpaceData(child, [...path, space.spaceName]); + } + }; + + // Step 4: Flatten the hierarchy starting from roots + for (const rootSpace of allCommunitySpaces) { + writeSpaceData(rootSpace, []); } + + csvStream.end(); + return stream; + } + + getSpaceLocation(space: SpaceEntity): string { + const names = []; + let current = space.parent; + while (current) { + names.unshift(current.spaceName); + current = current.parent; + } + return names.join(' > '); } } diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 73c28d4..d74d355 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -18,7 +18,6 @@ import { In, QueryRunner } from 'typeorm'; import { SubspaceModelEntity } from '@app/common/modules/space-model'; import { ValidationService } from '../space-validation.service'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; -import { TagService } from '../tag'; import { ModifyAction } from '@app/common/constants/modify-action.enum'; import { SubspaceDeviceService } from './subspace-device.service'; import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto'; @@ -34,7 +33,6 @@ export class SubSpaceService { constructor( private readonly subspaceRepository: SubspaceRepository, private readonly validationService: ValidationService, - private readonly tagService: TagService, private readonly newTagService: NewTagService, public readonly deviceService: SubspaceDeviceService, private readonly subspaceProductAllocationService: SubspaceProductAllocationService, @@ -481,21 +479,6 @@ export class SubSpaceService { { disabled: true }, ); - if (subspace.tags?.length) { - const modifyTagDtos: ProcessTagDto[] = subspace.tags.map((tag) => ({ - uuid: tag.uuid, - action: ModifyAction.ADD, - name: tag.tag, - productUuid: tag.product.uuid, - })); - await this.tagService.moveTags( - modifyTagDtos, - queryRunner, - subspace.space, - null, - ); - } - if (subspace.devices.length > 0) { await this.deviceService.deleteSubspaceDevices( subspace.devices,