mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-10 15:17:41 +00:00
424 lines
13 KiB
TypeScript
424 lines
13 KiB
TypeScript
import {
|
|
ORPHAN_COMMUNITY_NAME,
|
|
ORPHAN_SPACE_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 { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
|
import {
|
|
ExtendedTypeORMCustomModelFindAllQuery,
|
|
TypeORMCustomModel,
|
|
} from '@app/common/models/typeOrmCustom.model';
|
|
import { CommunityDto } from '@app/common/modules/community/dtos';
|
|
import { CommunityEntity } from '@app/common/modules/community/entities';
|
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
|
import { DeviceEntity } from '@app/common/modules/device/entities';
|
|
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
|
import { SpaceRepository } from '@app/common/modules/space';
|
|
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
|
import { addSpaceUuidToDevices } from '@app/common/util/device-utils';
|
|
import {
|
|
HttpException,
|
|
HttpStatus,
|
|
Injectable,
|
|
NotFoundException,
|
|
} from '@nestjs/common';
|
|
import { SpaceService } from 'src/space/services';
|
|
import { QueryRunner, SelectQueryBuilder } from 'typeorm';
|
|
import { AddCommunityDto, GetCommunityParams, ProjectParam } from '../dtos';
|
|
import { UpdateCommunityNameDto } from '../dtos/update.community.dto';
|
|
|
|
@Injectable()
|
|
export class CommunityService {
|
|
constructor(
|
|
private readonly communityRepository: CommunityRepository,
|
|
private readonly projectRepository: ProjectRepository,
|
|
private readonly spaceService: SpaceService,
|
|
private readonly tuyaService: TuyaService,
|
|
private readonly spaceRepository: SpaceRepository,
|
|
) {}
|
|
|
|
async createCommunity(
|
|
param: ProjectParam,
|
|
dto: AddCommunityDto,
|
|
): Promise<BaseResponseDto> {
|
|
const { name, description } = dto;
|
|
|
|
const project = await this.validateProject(param.projectUuid);
|
|
|
|
await this.validateName(name);
|
|
|
|
// Create the new community entity
|
|
const community = this.communityRepository.create({
|
|
name: name,
|
|
description: description,
|
|
project: project,
|
|
});
|
|
|
|
// Save the community to the database
|
|
try {
|
|
const externalId = await this.createTuyaSpace(name);
|
|
community.externalId = externalId;
|
|
await this.communityRepository.save(community);
|
|
return new SuccessResponseDto({
|
|
statusCode: HttpStatus.CREATED,
|
|
success: true,
|
|
data: community,
|
|
message: 'Community created successfully',
|
|
});
|
|
} catch (error) {
|
|
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
async getCommunityById(
|
|
params: GetCommunityParams,
|
|
queryRunner?: QueryRunner,
|
|
): Promise<BaseResponseDto> {
|
|
const { communityUuid, projectUuid } = params;
|
|
|
|
await this.validateProject(projectUuid);
|
|
|
|
const communityRepository =
|
|
queryRunner?.manager.getRepository(CommunityEntity) ||
|
|
this.communityRepository;
|
|
const community = await communityRepository.findOneBy({
|
|
uuid: communityUuid,
|
|
});
|
|
|
|
// If the community is not found, throw a 404 NotFoundException
|
|
if (!community) {
|
|
throw new HttpException(
|
|
`Community with ID ${communityUuid} not found.`,
|
|
HttpStatus.NOT_FOUND,
|
|
);
|
|
}
|
|
|
|
// Return a success response
|
|
return new SuccessResponseDto({
|
|
data: community,
|
|
message: 'Community fetched successfully',
|
|
});
|
|
}
|
|
|
|
async getCommunities(
|
|
{ projectUuid }: ProjectParam,
|
|
pageable: Partial<ExtendedTypeORMCustomModelFindAllQuery>,
|
|
): Promise<BaseResponseDto> {
|
|
try {
|
|
const project = await this.validateProject(projectUuid);
|
|
|
|
/**
|
|
* TODO: removing this breaks the code (should be fixed when refactoring @see TypeORMCustomModel
|
|
*/
|
|
pageable.where = {};
|
|
let qb: undefined | SelectQueryBuilder<CommunityEntity> = undefined;
|
|
|
|
qb = this.communityRepository
|
|
.createQueryBuilder('c')
|
|
.leftJoin('c.spaces', 's', 's.disabled = false')
|
|
.where('c.project = :projectUuid', { projectUuid })
|
|
.andWhere(`c.name != '${ORPHAN_COMMUNITY_NAME}-${project.name}'`)
|
|
.orderBy('c.createdAt', 'DESC')
|
|
.distinct(true);
|
|
if (pageable.search) {
|
|
qb.andWhere(
|
|
`c.name ILIKE '%${pageable.search}%' OR s.space_name ILIKE '%${pageable.search}%'`,
|
|
);
|
|
}
|
|
|
|
const customModel = TypeORMCustomModel(this.communityRepository);
|
|
|
|
const { baseResponseDto, paginationResponseDto } =
|
|
await customModel.findAll({ ...pageable, modelName: 'community' }, qb);
|
|
|
|
// todo: refactor this to minimize the number of queries
|
|
if (pageable.includeSpaces) {
|
|
const communitiesWithSpaces = await Promise.all(
|
|
baseResponseDto.data.map(async (community: CommunityDto) => {
|
|
const spaces =
|
|
await this.spaceService.getSpacesHierarchyForCommunity(
|
|
{
|
|
communityUuid: community.uuid,
|
|
projectUuid: projectUuid,
|
|
},
|
|
{
|
|
onlyWithDevices: false,
|
|
},
|
|
);
|
|
|
|
return {
|
|
...community,
|
|
spaces: spaces.data,
|
|
};
|
|
}),
|
|
);
|
|
|
|
baseResponseDto.data = communitiesWithSpaces;
|
|
}
|
|
|
|
return new PageResponse<CommunityDto>(
|
|
baseResponseDto,
|
|
paginationResponseDto,
|
|
);
|
|
} catch (error) {
|
|
// Generic error handling
|
|
throw new HttpException(
|
|
error.message || 'An error occurred while fetching communities.',
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
async getCommunitiesV2(
|
|
{ projectUuid }: ProjectParam,
|
|
{
|
|
search,
|
|
includeSpaces,
|
|
...pageable
|
|
}: Partial<ExtendedTypeORMCustomModelFindAllQuery>,
|
|
) {
|
|
try {
|
|
const project = await this.validateProject(projectUuid);
|
|
|
|
let qb: undefined | SelectQueryBuilder<CommunityEntity> = undefined;
|
|
|
|
qb = this.communityRepository
|
|
.createQueryBuilder('c')
|
|
.where('c.project = :projectUuid', { projectUuid })
|
|
.andWhere(`c.name != '${ORPHAN_COMMUNITY_NAME}-${project.name}'`)
|
|
.distinct(true);
|
|
|
|
if (includeSpaces) {
|
|
qb.leftJoinAndSelect(
|
|
'c.spaces',
|
|
'space',
|
|
'space.disabled = :disabled AND space.spaceName != :orphanSpaceName',
|
|
{ disabled: false, orphanSpaceName: ORPHAN_SPACE_NAME },
|
|
)
|
|
.leftJoinAndSelect('space.parent', 'parent')
|
|
.leftJoinAndSelect(
|
|
'space.children',
|
|
'children',
|
|
'children.disabled = :disabled',
|
|
{ disabled: false },
|
|
);
|
|
// .leftJoinAndSelect('space.spaceModel', 'spaceModel')
|
|
}
|
|
|
|
if (search) {
|
|
qb.andWhere(
|
|
`c.name ILIKE :search ${includeSpaces ? 'OR space.space_name ILIKE :search' : ''}`,
|
|
{ search },
|
|
);
|
|
}
|
|
|
|
const customModel = TypeORMCustomModel(this.communityRepository);
|
|
|
|
const { baseResponseDto, paginationResponseDto } =
|
|
await customModel.findAll({ ...pageable, modelName: 'community' }, qb);
|
|
if (includeSpaces) {
|
|
baseResponseDto.data = baseResponseDto.data.map((community) => ({
|
|
...community,
|
|
spaces: this.spaceService.buildSpaceHierarchy(community.spaces || []),
|
|
}));
|
|
}
|
|
return new PageResponse<CommunityDto>(
|
|
baseResponseDto,
|
|
paginationResponseDto,
|
|
);
|
|
} catch (error) {
|
|
// Generic error handling
|
|
if (error instanceof HttpException) {
|
|
throw error;
|
|
}
|
|
throw new HttpException(
|
|
error.message || 'An error occurred while fetching communities.',
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
async updateCommunity(
|
|
params: GetCommunityParams,
|
|
updateCommunityDto: UpdateCommunityNameDto,
|
|
): Promise<BaseResponseDto> {
|
|
const { communityUuid, projectUuid } = params;
|
|
|
|
const project = await this.validateProject(projectUuid);
|
|
|
|
const community = await this.communityRepository.findOne({
|
|
where: { uuid: communityUuid },
|
|
});
|
|
|
|
// If the community doesn't exist, throw a 404 error
|
|
if (!community) {
|
|
throw new HttpException(
|
|
`Community with ID ${communityUuid} not found`,
|
|
HttpStatus.NOT_FOUND,
|
|
);
|
|
}
|
|
|
|
if (community.name === `${ORPHAN_COMMUNITY_NAME}-${project.name}`) {
|
|
throw new HttpException(
|
|
`Community with ID ${communityUuid} cannot be updated`,
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
|
|
try {
|
|
const { name } = updateCommunityDto;
|
|
if (name != community.name) await this.validateName(name);
|
|
|
|
community.name = name;
|
|
|
|
const updatedCommunity = await this.communityRepository.save(community);
|
|
|
|
return new SuccessResponseDto<CommunityDto>({
|
|
message: 'Success update Community',
|
|
data: updatedCommunity,
|
|
});
|
|
} catch (err) {
|
|
// Catch and handle any errors
|
|
if (err instanceof HttpException) {
|
|
throw err; // If it's an HttpException, rethrow it
|
|
} else {
|
|
// Throw a generic 404 error if anything else goes wrong
|
|
throw new HttpException(
|
|
`An Internal exception has been occured ${err}`,
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
async deleteCommunity(params: GetCommunityParams): Promise<BaseResponseDto> {
|
|
const { communityUuid, projectUuid } = params;
|
|
|
|
const project = await this.validateProject(projectUuid);
|
|
|
|
const community = await this.communityRepository.findOne({
|
|
where: { uuid: communityUuid },
|
|
});
|
|
|
|
// If the community is not found, throw an error
|
|
if (!community) {
|
|
throw new HttpException(
|
|
`Community with ID ${communityUuid} not found`,
|
|
HttpStatus.NOT_FOUND,
|
|
);
|
|
}
|
|
|
|
if (community.name === `${ORPHAN_COMMUNITY_NAME}-${project.name}`) {
|
|
throw new HttpException(
|
|
`Community with ID ${communityUuid} cannot be deleted`,
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
|
|
try {
|
|
await this.communityRepository.remove(community);
|
|
|
|
return new SuccessResponseDto({
|
|
message: `Community with ID ${communityUuid} has been successfully deleted`,
|
|
});
|
|
} catch (err) {
|
|
if (err instanceof HttpException) {
|
|
throw err;
|
|
} else {
|
|
throw new HttpException(
|
|
'An error occurred while deleting the community',
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async createTuyaSpace(name: string): Promise<string> {
|
|
try {
|
|
const response = await this.tuyaService.createSpace({ name });
|
|
return response;
|
|
} catch (error) {
|
|
throw new HttpException(
|
|
'Failed to create a Tuya space',
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
async validateProject(uuid: string) {
|
|
const project = await this.projectRepository.findOne({
|
|
where: { uuid },
|
|
});
|
|
if (!project) {
|
|
throw new HttpException(
|
|
`A project with the uuid '${uuid}' doesn't exists.`,
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
return project;
|
|
}
|
|
|
|
private async validateName(name: string) {
|
|
const existingCommunity = await this.communityRepository.findOneBy({
|
|
name,
|
|
});
|
|
if (existingCommunity) {
|
|
throw new HttpException(
|
|
`A community with the name '${name}' already exists.`,
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
}
|
|
async getAllDevicesByCommunity(
|
|
communityUuid: string,
|
|
): Promise<DeviceEntity[]> {
|
|
const community = await this.communityRepository.findOne({
|
|
where: { uuid: communityUuid },
|
|
relations: [
|
|
'spaces',
|
|
'spaces.children',
|
|
'spaces.devices',
|
|
'spaces.devices.productDevice',
|
|
],
|
|
});
|
|
|
|
if (!community) {
|
|
throw new NotFoundException('Community not found');
|
|
}
|
|
|
|
const allDevices: DeviceEntity[] = [];
|
|
const visitedSpaceUuids = new Set<string>();
|
|
|
|
// Recursive fetch function with visited check
|
|
const fetchSpaceDevices = async (space: SpaceEntity) => {
|
|
if (visitedSpaceUuids.has(space.uuid)) return;
|
|
visitedSpaceUuids.add(space.uuid);
|
|
|
|
if (space.devices?.length) {
|
|
allDevices.push(...addSpaceUuidToDevices(space.devices, space.uuid));
|
|
}
|
|
|
|
if (space.children?.length) {
|
|
for (const child of space.children) {
|
|
const fullChild = await this.spaceRepository.findOne({
|
|
where: { uuid: child.uuid },
|
|
relations: ['children', 'devices', 'devices.productDevice'],
|
|
});
|
|
|
|
if (fullChild) {
|
|
await fetchSpaceDevices(fullChild);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
for (const space of community.spaces) {
|
|
await fetchSpaceDevices(space);
|
|
}
|
|
|
|
return allDevices;
|
|
}
|
|
}
|