Files
backend/src/project/services/project.service.ts
ZaydSkaff 689a38ee0c Revamp/space management (#409)
* task: add getCommunitiesV2

* task: update getOneSpace API to match revamp structure

* refactor: implement modifications to pace management APIs

* refactor: remove space link
2025-06-18 10:34:29 +03:00

348 lines
10 KiB
TypeScript

import { ORPHAN_COMMUNITY_NAME } from '@app/common/constants/orphan-constant';
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { PageResponse } from '@app/common/dto/pagination.response.dto';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import {
TypeORMCustomModel,
TypeORMCustomModelFindAllQuery,
} from '@app/common/models/typeOrmCustom.model';
import { ProjectDto } from '@app/common/modules/project/dtos';
import { ProjectEntity } from '@app/common/modules/project/entities';
import { ProjectRepository } from '@app/common/modules/project/repositiories';
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
import { UserRepository } from '@app/common/modules/user/repositories';
import { format } from '@fast-csv/format';
import {
forwardRef,
HttpException,
HttpStatus,
Inject,
Injectable,
} from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs';
import { SpaceService } from 'src/space/services';
import { PassThrough } from 'stream';
import { CreateOrphanSpaceCommand } from '../command/create-orphan-space-command';
import { CreateProjectDto, GetProjectParam } from '../dto';
import { QueryRunner } from 'typeorm';
@Injectable()
export class ProjectService {
constructor(
private readonly projectRepository: ProjectRepository,
private readonly userRepository: UserRepository,
private commandBus: CommandBus,
@Inject(forwardRef(() => SpaceService))
private readonly spaceService: SpaceService,
) {}
async createProject(
createProjectDto: CreateProjectDto,
): Promise<BaseResponseDto> {
const { name, userUuid } = createProjectDto;
try {
// Check if the project name already exists
const isNameExist = await this.validate(name);
if (isNameExist) {
throw new HttpException(
`Project with name ${name} already exists`,
HttpStatus.CONFLICT,
);
}
// Check if user exists
const user = await this.userRepository.findOne({
where: { uuid: userUuid },
});
if (!user) {
throw new HttpException(
`User with UUID ${userUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
const newProject = this.projectRepository.create(createProjectDto);
const savedProject = await this.projectRepository.save(newProject);
user.project = savedProject;
await this.userRepository.save(user);
await this.commandBus.execute(new CreateOrphanSpaceCommand(savedProject));
return new SuccessResponseDto({
message: `Project with ID ${savedProject.uuid} successfully created`,
data: {
...savedProject,
rootUser: { uuid: user.uuid, email: user.email },
},
statusCode: HttpStatus.CREATED,
});
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
`An error occurred while creating the project. Please try again later. ${error}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async deleteProject(uuid: string): Promise<BaseResponseDto> {
try {
// Find the project by UUID
const project = await this.findOne(uuid);
if (!project) {
throw new HttpException(
`Project with ID ${uuid} not found`,
HttpStatus.NOT_FOUND,
);
}
// Delete the project
await this.projectRepository.delete({ uuid });
return new SuccessResponseDto({
message: `Project with ID ${uuid} successfully deleted`,
statusCode: HttpStatus.OK,
});
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
`An error occurred while deleting the project ${uuid}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async listProjects(
pageable: Partial<TypeORMCustomModelFindAllQuery>,
): Promise<BaseResponseDto> {
try {
pageable.modelName = 'project';
const customModel = TypeORMCustomModel(this.projectRepository);
const { baseResponseDto, paginationResponseDto } =
await customModel.findAll(pageable);
return new PageResponse<ProjectDto>(
baseResponseDto,
paginationResponseDto,
);
} catch (error) {
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
async getProject(uuid: string): Promise<BaseResponseDto> {
try {
// Find the project by UUID
const project = await this.findOne(uuid);
if (!project) {
throw new HttpException(
`Project with ID ${uuid} not found`,
HttpStatus.NOT_FOUND,
);
}
return new SuccessResponseDto({
message: `Project with ID ${uuid} retrieved successfully`,
data: project,
statusCode: HttpStatus.OK,
});
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
`An error occurred while retrieving the project with id ${uuid}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async updateProject(
uuid: string,
updateProjectDto: CreateProjectDto,
): Promise<BaseResponseDto> {
try {
// Find the project by UUID
const project = await this.findOne(uuid);
if (!project) {
throw new HttpException(
`Project with ID ${uuid} not found`,
HttpStatus.NOT_FOUND,
);
}
if (updateProjectDto.name && updateProjectDto.name !== project.name) {
const isNameExist = await this.validate(updateProjectDto.name);
if (isNameExist) {
throw new HttpException(
`Project with name ${updateProjectDto.name} already exists`,
HttpStatus.CONFLICT,
);
}
}
// Update the project details
Object.assign(project, updateProjectDto);
const updatedProject = await this.projectRepository.save(project);
return new SuccessResponseDto({
message: `Project with ID ${uuid} successfully updated`,
data: updatedProject,
statusCode: HttpStatus.OK,
});
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
`An error occurred while updating the project with ID ${uuid}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async findOne(
uuid: string,
queryRunner?: QueryRunner,
): Promise<ProjectEntity> {
const projectRepository = queryRunner
? queryRunner.manager.getRepository(ProjectEntity)
: this.projectRepository;
const project = await projectRepository.findOne({ where: { uuid } });
if (!project) {
throw new HttpException(
`Invalid project with uuid ${uuid}`,
HttpStatus.NOT_FOUND,
);
}
return project;
}
async validate(name: string): Promise<boolean> {
return await this.projectRepository.exists({ where: { name } });
}
async exportToCsv(param: GetProjectParam): Promise<PassThrough> {
const project = await this.projectRepository.findOne({
where: { uuid: param.projectUuid },
relations: [
'communities',
'communities.spaces',
'communities.spaces.parent',
'communities.spaces.productAllocations',
'communities.spaces.productAllocations.product',
'communities.spaces.productAllocations.tag',
'communities.spaces.subspaces',
'communities.spaces.subspaces.productAllocations',
'communities.spaces.subspaces.productAllocations.product',
'communities.spaces.subspaces.productAllocations.tag',
],
});
if (!project) {
throw new HttpException(
`Project with UUID ${param.projectUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
const stream = new PassThrough();
const csvStream = format({
headers: [
'Device ID',
'Community Name',
'Space Name',
'Space Location',
'Subspace Name',
'Tag',
'Product Name',
'Community UUID',
'Space UUID',
'Subspace UUID',
],
});
csvStream.pipe(stream);
const allSpaces: SpaceEntity[] = [];
for (const community of project.communities) {
if (community.name === `${ORPHAN_COMMUNITY_NAME}-${project.name}`)
continue;
for (const space of community.spaces) {
if (!space.disabled) {
space.community = community;
allSpaces.push(space);
}
}
}
const spaceMap = new Map<string, SpaceEntity>();
for (const space of allSpaces) {
spaceMap.set(space.uuid, space);
}
function buildSpaceLocation(space: SpaceEntity): string {
const path: string[] = [];
let current = space.parent;
while (current) {
path.unshift(current.spaceName);
current = current.parent ?? spaceMap.get(current.uuid)?.parent ?? null;
}
return path.join(' > ');
}
for (const space of allSpaces) {
const spaceLocation = buildSpaceLocation(space);
for (const subspace of space.subspaces || []) {
if (subspace.disabled) continue;
for (const productAllocation of subspace.productAllocations || []) {
csvStream.write({
'Device ID': '',
'Community Name': space.community?.name || '',
'Space Name': space.spaceName,
'Space Location': spaceLocation,
'Subspace Name': subspace.subspaceName || '',
Tag: productAllocation.tag.name,
'Product Name': productAllocation.product.name || '',
'Community UUID': space.community?.uuid || '',
'Space UUID': space.uuid,
'Subspace UUID': subspace.uuid,
});
}
}
for (const productAllocation of space.productAllocations || []) {
csvStream.write({
'Device ID': '',
'Community Name': space.community?.name || '',
'Space Name': space.spaceName,
'Space Location': spaceLocation,
'Subspace Name': '',
Tag: productAllocation.tag.name,
'Product Name': productAllocation.product.name || '',
'Community UUID': space.community?.uuid || '',
'Space UUID': space.uuid,
'Subspace UUID': '',
});
}
}
csvStream.end();
return stream;
}
}