mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-10 23:27:31 +00:00
export
This commit is contained in:
@ -43,6 +43,11 @@ export class ControllerRoute {
|
|||||||
'Get user by uuid in project';
|
'Get user by uuid in project';
|
||||||
public static readonly GET_USER_BY_UUID_IN_PROJECT_DESCRIPTION =
|
public static readonly GET_USER_BY_UUID_IN_PROJECT_DESCRIPTION =
|
||||||
'This endpoint retrieves a user by their unique identifier (UUID) associated with a specific project.';
|
'This endpoint retrieves a user by their unique identifier (UUID) associated with a specific project.';
|
||||||
|
|
||||||
|
public static readonly EXPORT_STRUCTURE_CSV_SUMMARY =
|
||||||
|
'Export project with their full structure to a CSV file';
|
||||||
|
public static readonly EXPORT_STRUCTURE_CSV_DESCRIPTION =
|
||||||
|
'This endpoint exports project along with their associated communities, spaces, and nested space hierarchy into a downloadable CSV file. Useful for backups, reports, or audits';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
static PROJECT_USER = class {
|
static PROJECT_USER = class {
|
||||||
|
39
package-lock.json
generated
39
package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fast-csv/format": "^5.0.2",
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/config": "^3.2.0",
|
"@nestjs/config": "^3.2.0",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
@ -900,6 +901,19 @@
|
|||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fast-csv/format": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-fRYcWvI8vs0Zxa/8fXd/QlmQYWWkJqKZPAXM+vksnplb3owQFKTPPh9JqOtD0L3flQw/AZjjXdPkD7Kp/uHm8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash.escaperegexp": "^4.1.2",
|
||||||
|
"lodash.isboolean": "^3.0.3",
|
||||||
|
"lodash.isequal": "^4.5.0",
|
||||||
|
"lodash.isfunction": "^3.0.9",
|
||||||
|
"lodash.isnil": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@firebase/analytics": {
|
"node_modules/@firebase/analytics": {
|
||||||
"version": "0.10.8",
|
"version": "0.10.8",
|
||||||
"resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.8.tgz",
|
"resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.8.tgz",
|
||||||
@ -9354,6 +9368,12 @@
|
|||||||
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
|
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.escaperegexp": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.includes": {
|
"node_modules/lodash.includes": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||||
@ -9372,12 +9392,31 @@
|
|||||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.isequal": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
||||||
|
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isfunction": {
|
||||||
|
"version": "3.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz",
|
||||||
|
"integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.isinteger": {
|
"node_modules/lodash.isinteger": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
|
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.isnil": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.isnumber": {
|
"node_modules/lodash.isnumber": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"test:e2e": "jest --config ./apps/backend/test/jest-e2e.json"
|
"test:e2e": "jest --config ./apps/backend/test/jest-e2e.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fast-csv/format": "^5.0.2",
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/config": "^3.2.0",
|
"@nestjs/config": "^3.2.0",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
|
@ -4,13 +4,20 @@ import {
|
|||||||
Controller,
|
Controller,
|
||||||
Delete,
|
Delete,
|
||||||
Get,
|
Get,
|
||||||
|
Header,
|
||||||
Param,
|
Param,
|
||||||
Post,
|
Post,
|
||||||
Put,
|
Put,
|
||||||
Query,
|
Query,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
import {
|
||||||
|
ApiBearerAuth,
|
||||||
|
ApiOperation,
|
||||||
|
ApiProduces,
|
||||||
|
ApiResponse,
|
||||||
|
ApiTags,
|
||||||
|
} from '@nestjs/swagger';
|
||||||
import { ProjectService } from '../services';
|
import { ProjectService } from '../services';
|
||||||
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';
|
||||||
@ -86,4 +93,24 @@ export class ProjectController {
|
|||||||
async findOne(@Param() params: GetProjectParam): Promise<BaseResponseDto> {
|
async findOne(@Param() params: GetProjectParam): Promise<BaseResponseDto> {
|
||||||
return this.projectService.getProject(params.projectUuid);
|
return this.projectService.getProject(params.projectUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@ApiOperation({
|
||||||
|
summary: ControllerRoute.PROJECT.ACTIONS.EXPORT_STRUCTURE_CSV_SUMMARY,
|
||||||
|
description:
|
||||||
|
ControllerRoute.PROJECT.ACTIONS.EXPORT_STRUCTURE_CSV_DESCRIPTION,
|
||||||
|
})
|
||||||
|
@Get(':projectUuid/structure/export-csv')
|
||||||
|
@ApiProduces('text/csv')
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description:
|
||||||
|
'A CSV file containing project details and their structural hierarchy (Project, Community, Space, Parent Space).',
|
||||||
|
})
|
||||||
|
@Header('Content-Type', 'text/csv')
|
||||||
|
@Header('Content-Disposition', 'attachment; filename=project-structure.csv')
|
||||||
|
async exportStructures(@Param() params: GetProjectParam) {
|
||||||
|
return this.projectService.exportToCsv(params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
import { CreateProjectDto } 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';
|
||||||
import { ProjectEntity } from '@app/common/modules/project/entities';
|
import { ProjectEntity } from '@app/common/modules/project/entities';
|
||||||
@ -13,6 +13,8 @@ import { PageResponse } from '@app/common/dto/pagination.response.dto';
|
|||||||
import { CommandBus } from '@nestjs/cqrs';
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
import { CreateOrphanSpaceCommand } from '../command/create-orphan-space-command';
|
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 { PassThrough } from 'stream';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProjectService {
|
export class ProjectService {
|
||||||
@ -213,4 +215,62 @@ export class ProjectService {
|
|||||||
async validate(name: string): Promise<boolean> {
|
async validate(name: string): Promise<boolean> {
|
||||||
return await this.projectRepository.exists({ where: { name } });
|
return await this.projectRepository.exists({ where: { name } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!project) {
|
||||||
|
throw new HttpException(
|
||||||
|
`Project with UUID ${param.projectUuid} not found`,
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = new PassThrough();
|
||||||
|
const csvStream = format({ headers: true });
|
||||||
|
|
||||||
|
csvStream.pipe(stream);
|
||||||
|
|
||||||
|
for (const community of project.communities || []) {
|
||||||
|
for (const space of community.spaces || []) {
|
||||||
|
const tagNames = space.tags?.map((tag) => tag.tag).join(', ') || '';
|
||||||
|
|
||||||
|
csvStream.write({
|
||||||
|
'Project Name': project.name,
|
||||||
|
'Project UUID': project.uuid,
|
||||||
|
'Community Name': community.name,
|
||||||
|
'Community UUID': community.uuid,
|
||||||
|
'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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
csvStream.end();
|
||||||
|
return stream;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof HttpException) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new HttpException(
|
||||||
|
`Failed to export project structure for UUID ${param.projectUuid}: ${error.message}`,
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user