added controller to export

This commit is contained in:
hannathkadher
2025-04-16 10:28:57 +04:00
parent d24e7be4ea
commit 4810cc6f35
4 changed files with 184 additions and 58 deletions

View File

@ -9,6 +9,7 @@ import {
Post, Post,
Put, Put,
Query, Query,
Res,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { import {
@ -110,7 +111,11 @@ export class ProjectController {
}) })
@Header('Content-Type', 'text/csv') @Header('Content-Type', 'text/csv')
@Header('Content-Disposition', 'attachment; filename=project-structure.csv') @Header('Content-Disposition', 'attachment; filename=project-structure.csv')
async exportStructures(@Param() params: GetProjectParam) { async exportStructures(
return this.projectService.exportToCsv(params); @Param() params: GetProjectParam,
@Res() res: Response,
): Promise<void> {
const csvStream = await this.projectService.exportToCsv(params);
csvStream.pipe(res as unknown as NodeJS.WritableStream);
} }
} }

View File

@ -4,7 +4,13 @@ import { ProjectController } from './controllers';
import { ProjectService } from './services'; import { ProjectService } from './services';
import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProjectRepository } from '@app/common/modules/project/repositiories';
import { CreateOrphanSpaceHandler } from './handler'; 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 { CommunityRepository } from '@app/common/modules/community/repositories';
import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories';
import { ProjectUserController } from './controllers/project-user.controller'; import { ProjectUserController } from './controllers/project-user.controller';
@ -13,6 +19,47 @@ import {
UserRepository, UserRepository,
UserSpaceRepository, UserSpaceRepository,
} from '@app/common/modules/user/repositories'; } 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]; const CommandHandlers = [CreateOrphanSpaceHandler];
@ -30,6 +77,41 @@ const CommandHandlers = [CreateOrphanSpaceHandler];
InviteUserRepository, InviteUserRepository,
UserSpaceRepository, UserSpaceRepository,
UserRepository, 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], exports: [ProjectService, CqrsModule],
}) })

View File

@ -1,5 +1,11 @@
import { ProjectRepository } from '@app/common/modules/project/repositiories'; 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 { CreateProjectDto, GetProjectParam } from '../dto';
import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { SuccessResponseDto } from '@app/common/dto/success.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 { UserRepository } from '@app/common/modules/user/repositories';
import { format } from '@fast-csv/format'; import { format } from '@fast-csv/format';
import { PassThrough } from 'stream'; import { PassThrough } from 'stream';
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
import { SpaceService } from 'src/space/services';
@Injectable() @Injectable()
export class ProjectService { export class ProjectService {
@ -22,6 +30,8 @@ export class ProjectService {
private readonly projectRepository: ProjectRepository, private readonly projectRepository: ProjectRepository,
private readonly userRepository: UserRepository, private readonly userRepository: UserRepository,
private commandBus: CommandBus, private commandBus: CommandBus,
@Inject(forwardRef(() => SpaceService))
private readonly spaceService: SpaceService,
) {} ) {}
async createProject( async createProject(
@ -217,60 +227,106 @@ export class ProjectService {
} }
async exportToCsv(param: GetProjectParam): Promise<PassThrough> { async exportToCsv(param: GetProjectParam): Promise<PassThrough> {
try { const project = await this.projectRepository.findOne({
const project = await this.projectRepository.findOne({ where: { uuid: param.projectUuid },
where: { uuid: param.projectUuid }, relations: ['communities', 'communities.spaces'],
relations: [ });
'communities',
'communities.spaces',
'communities.spaces.parent',
'communities.spaces.tags',
],
});
if (!project) { if (!project) {
throw new HttpException( throw new HttpException(
`Project with UUID ${param.projectUuid} not found`, `Project with UUID ${param.projectUuid} not found`,
HttpStatus.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(); allCommunitySpaces.push(...(spaceHierarchy as SpaceEntity[]));
const csvStream = format({ headers: true }); }
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 || []) { // Step 3: Recursive flattening
for (const space of community.spaces || []) { const writeSpaceData = (space: any, path: string[] = []) => {
const tagNames = space.tags?.map((tag) => tag.tag).join(', ') || ''; const location = path.join(' > ');
// 1. Subspaces and their tags
for (const subspace of space.subspaces || []) {
for (const tag of subspace.tags || []) {
csvStream.write({ csvStream.write({
'Project Name': project.name, 'Project Name': project.name,
'Project UUID': project.uuid, 'Project UUID': project.uuid,
'Community Name': community.name, 'Space Location': location,
'Community UUID': community.uuid,
'Space Name': space.spaceName, 'Space Name': space.spaceName,
'Space UUID': space.uuid, 'Space UUID': space.uuid,
'Parent Space UUID': space.parent?.uuid || '', 'Subspace Name': subspace.subspaceName || '',
'Space Tuya UUID': space.spaceTuyaUuid || '', 'Subspace UUID': subspace.uuid,
X: space.x, Tag: tag.tag,
Y: space.y, 'Tag Product Name': tag.product?.productName || '',
Tags: tagNames,
}); });
} }
} }
csvStream.end(); // 2. Tags directly on the space
return stream; for (const tag of space.tags || []) {
} catch (error) { csvStream.write({
if (error instanceof HttpException) { 'Project Name': project.name,
throw error; '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( // 3. Recursively process children
`Failed to export project structure for UUID ${param.projectUuid}: ${error.message}`, for (const child of space.children || []) {
HttpStatus.INTERNAL_SERVER_ERROR, 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(' > ');
} }
} }

View File

@ -18,7 +18,6 @@ import { In, QueryRunner } from 'typeorm';
import { SubspaceModelEntity } from '@app/common/modules/space-model'; import { SubspaceModelEntity } from '@app/common/modules/space-model';
import { ValidationService } from '../space-validation.service'; import { ValidationService } from '../space-validation.service';
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
import { TagService } from '../tag';
import { ModifyAction } from '@app/common/constants/modify-action.enum'; import { ModifyAction } from '@app/common/constants/modify-action.enum';
import { SubspaceDeviceService } from './subspace-device.service'; import { SubspaceDeviceService } from './subspace-device.service';
import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto'; import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto';
@ -34,7 +33,6 @@ export class SubSpaceService {
constructor( constructor(
private readonly subspaceRepository: SubspaceRepository, private readonly subspaceRepository: SubspaceRepository,
private readonly validationService: ValidationService, private readonly validationService: ValidationService,
private readonly tagService: TagService,
private readonly newTagService: NewTagService, private readonly newTagService: NewTagService,
public readonly deviceService: SubspaceDeviceService, public readonly deviceService: SubspaceDeviceService,
private readonly subspaceProductAllocationService: SubspaceProductAllocationService, private readonly subspaceProductAllocationService: SubspaceProductAllocationService,
@ -481,21 +479,6 @@ export class SubSpaceService {
{ disabled: true }, { 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) { if (subspace.devices.length > 0) {
await this.deviceService.deleteSubspaceDevices( await this.deviceService.deleteSubspaceDevices(
subspace.devices, subspace.devices,