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,
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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],
|
||||||
})
|
})
|
||||||
|
@ -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(' > ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
Reference in New Issue
Block a user