mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-15 10:25:23 +00:00
added controller to export
This commit is contained in:
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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],
|
||||
})
|
||||
|
@ -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<PassThrough> {
|
||||
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(' > ');
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
Reference in New Issue
Block a user