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,
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<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 { 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],
})

View File

@ -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,15 +227,9 @@ export class ProjectService {
}
async exportToCsv(param: GetProjectParam): Promise<PassThrough> {
try {
const project = await this.projectRepository.findOne({
where: { uuid: param.projectUuid },
relations: [
'communities',
'communities.spaces',
'communities.spaces.parent',
'communities.spaces.tags',
],
relations: ['communities', 'communities.spaces'],
});
if (!project) {
@ -235,42 +239,94 @@ export class ProjectService {
);
}
const stream = new PassThrough();
const csvStream = format({ headers: true });
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 },
);
allCommunitySpaces.push(...(spaceHierarchy as SpaceEntity[]));
}
// 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(' > ');
}
}

View File

@ -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,